aba677b1f72ad8c1e6f6dc59a2ca8471481441e5
[browser-rts.git] / src / api.ts
1 import { HttpServer } from './lib/server';
2 import * as config from './config';
3 import { AccountRepository } from './repository/accounts';
4 import { CityRepository } from './repository/city';
5 import { MailRepository } from './repository/mail';
6 import {BadInputError, ERROR_CODE, NotFoundError} from './errors';
7 import { renderKingomOverview } from './render/kingdom-overview';
8 import { renderLandDevelopment } from './render/land-development';
9 import { tick } from './tasks/tick';
10 import { construction } from './tasks/construction';
11 import { unitTraining } from './tasks/unit-training';
12 import { fight } from './tasks/fight';
13 import { renderUnitTraining } from './render/unit-training';
14 import { launchOffensive, listOperations, renderOverworldMap } from './render/map';
15 import { createBullBoard } from '@bull-board/api';
16 import { BullAdapter } from '@bull-board/api/bullAdapter';
17 import _ from 'lodash';
18 import { renderMailroom, renderMessage } from './render/mail';
19
20 const server = new HttpServer(config.API_PORT);
21
22 const accountRepo = new AccountRepository();
23 const cityRepo = new CityRepository();
24 const mailRepo = new MailRepository();
25
26 createBullBoard({
27         queues: [
28                 new BullAdapter(tick.queue),
29                 new BullAdapter(construction.queue),
30                 new BullAdapter(unitTraining.queue),
31                 new BullAdapter(fight.queue)
32         ],
33         serverAdapter: server.bullAdapter,
34 });
35
36 server.post<{
37         body: {username: string, password: string}}, 
38         string
39         >
40         ('/accounts', async (req) => {
41         const { username, password} = req.body;
42         if(!username || !password || username.length < 3 || password.length < 3) {
43                 throw new BadInputError('Invalid username or password', ERROR_CODE.INVALID_USERNAME);
44         }
45         const acct = await accountRepo.create(username, password);
46
47         // lets create the city!
48         await cityRepo.create(acct.id);
49
50         return `<div class="alert success">You are all signed up! You can go ahead and log in</div>`;
51 });
52
53 server.post<{body: {username: string, password: string}}, void>('/login', async (req, raw, res) => {
54         const { username, password} = req.body;
55         if(!username || !password || username.length < 3 || password.length < 3) {
56                 throw new BadInputError('Invalid username or password', ERROR_CODE.INVALID_USERNAME);
57         }
58         const {account, session} = await accountRepo.login(username, password);
59         if(!account) {
60                 throw new NotFoundError('Invalid username or password', ERROR_CODE.USER_NOT_FOUND);
61         }
62
63         res.setHeader('hx-redirect', `/game.html?token=${session.id}&id=${session.account_id}`);
64
65 });
66
67 server.post<{body: {
68         soldiers: number,
69         attackers: number,
70         defenders: number,
71         sp_attackers: number,
72         sp_defenders: number
73   }}, string>('/attack-power', async req => {
74         const power = await cityRepo.power(req.body);
75
76         return power.toLocaleString();
77
78 });
79
80 server.get<{params: { cityId: string }}, string>('/city/:cityId', async req => {
81         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
82         const yourCity = await cityRepo.getUsersCity(account.id);
83         const city = await cityRepo.findById(req.params.cityId);
84         const acct = await accountRepo.FindOne({id: city.owner});
85
86
87         return await launchOffensive(city, acct || {
88                 id: '-',
89                 username: 'Rebels',
90                 password: ''
91         }, yourCity, account);
92 });
93
94 server.get<{}, string>('/poll/overview', async req => {
95         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
96         const city = await cityRepo.getUsersCity(account.id);
97
98         return renderKingomOverview(city, account);
99 });
100
101 server.get<{}, string>('/poll/construction', async req => {
102         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
103         const city = await cityRepo.getUsersCity(account.id);
104         const buildings = await cityRepo.buildingRepository.list();
105
106         const buildQueues = await cityRepo.getBuildQueues(account.id);
107         return renderLandDevelopment(city, buildings, buildQueues);
108 });
109
110 server.get<{}, string>('/poll/unit-training', async req => {
111         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
112         const city = await cityRepo.getUsersCity(account.id);
113
114         const unitTrainingQueues = await cityRepo.getUnitTrainingQueues(account.id);
115         const units = await cityRepo.unitRepository.list();
116
117         return renderUnitTraining(city, units, unitTrainingQueues);
118 });
119
120 server.post<{body: {sector: string}}, string>('/poll/map', async req => {
121         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
122         const city = await cityRepo.getUsersCity(account.id);
123
124   let sector = city.sector_id;
125   if(req.body.sector) {
126     try {
127       sector = parseInt(req.body.sector);
128     }
129     catch(e) {
130       sector = city.sector_id;
131     }
132   }
133
134   console.log('Checking cities in sector', sector);
135
136         return renderOverworldMap(await cityRepo.findAllInSector(sector), city, sector);
137 });
138
139 server.get<{}, string>('/poll/mailroom', async req => {
140         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
141
142         return renderMailroom(await mailRepo.listReceivedMessages(account.id));
143 });
144
145
146 server.post<{
147         body: {
148                 amount: string,
149                 building_type: string
150         }
151 }, string>('/cost/construction', async req => {
152         const amount = parseInt(req.body.amount.trim(), 10);
153         console.log('checking amount', amount);
154
155         if(isNaN(amount) || amount < 1) {
156                 return '';
157         }
158         const building = await cityRepo.buildingRepository.findBySlug(req.body.building_type);
159
160         if(!building) {
161                 throw new NotFoundError(`Invalid building type ${req.body.building_type}`, ERROR_CODE.INVALID_BUILDING);
162         }
163
164         return JSON.stringify({
165                 gold: building.gold * amount,
166                 ore: building.ore * amount,
167                 logs: building.logs * amount,
168                 land: building.land * amount,
169                 time: building.time
170         });
171 });
172
173 server.post<{
174         body: {
175                 amount: string;
176                 type: string;
177         }
178 }, string>('/cost/training', async req => {
179         const amount = parseInt(req.body.amount, 10);
180         const unit = await cityRepo.unitRepository.findBySlug(req.body.type);
181
182         if(!unit) {
183                 throw new NotFoundError(`Invalid unit type ${req.body.type}`, ERROR_CODE.INVALID_UNIT);
184         }
185
186         return JSON.stringify({
187                 population: unit.population * amount,
188                 soldiers: unit.soldiers * amount,
189                 attackers: unit.attackers * amount,
190                 defenders: unit.defenders * amount,
191                 gold: unit.gold * amount,
192                 bushels: unit.bushels * amount
193         });
194 });
195
196 server.post<{
197         body: {
198                 amount: string,
199                 building_type: string,
200         }
201 }, void>('/build', async req => {
202         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
203         const city = await cityRepo.getUsersCity(account.id);
204
205         const amount = parseInt(req.body.amount, 10);
206         const building = await cityRepo.buildingRepository.findBySlug(req.body.building_type);
207
208         if(!building) {
209                 throw new NotFoundError(`Invalid building type ${req.body.building_type}`, ERROR_CODE.INVALID_BUILDING);
210         }
211
212         const queueData = await cityRepo.buildBuilding(building, amount ,city);
213
214         construction.trigger(queueData, { delay: queueData.due });
215 }, 'reload-construction-queue');
216
217 server.post<{
218                 body: {
219                         amount: string,
220                         type: string
221                 }
222         },
223         void
224         >('/units', async req => {
225         const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
226         const city = await cityRepo.getUsersCity(acct.id);
227
228         const amount  = parseInt(req.body.amount, 10);
229         const unit = await cityRepo.unitRepository.findBySlug(req.body.type);
230
231         if(!unit) {
232                 throw new NotFoundError(`Invalid unit type ${req.body.type}`, ERROR_CODE.INVALID_UNIT);
233         }
234
235         const queueData = await cityRepo.train(unit, amount, city);
236         unitTraining.trigger(queueData, { delay: queueData.due });
237
238 }, 'reload-unit-training');
239
240 server.post<{
241         body: {
242                 city: string,
243                 soldiers: string,
244                 attackers: string,
245                 defenders: string,
246                 sp_attackers: string,
247                 sp_defenders: string
248         }
249         }, 
250         void
251         >('/attack', async req => {
252                 const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
253                 const city = await cityRepo.getUsersCity(acct.id);
254                 const attackedCity = await cityRepo.findById(req.body.city);
255
256                 const army = {
257                         soldiers: parseInt(req.body.soldiers),
258                         attackers: parseInt(req.body.attackers),
259                         defenders: parseInt(req.body.defenders),
260                         sp_attackers: parseInt(req.body.sp_attackers),
261                         sp_defenders: parseInt(req.body.sp_defenders)
262                 };
263
264                 const armyQueue = await cityRepo.attack(city, attackedCity, army);
265
266                 fight.trigger(armyQueue, {
267                         delay: armyQueue.due - Date.now()
268                 });
269         }, 'reload-outgoing-attacks');
270
271 server.get<void, string>('/messages', async req => {
272         const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
273         const msgs = await mailRepo.listReceivedMessages(acct.id);
274
275         return JSON.stringify(msgs);
276 });
277
278 server.get<{params: {id: string}}, string>('/messages/:id', async req => {
279         const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
280         const msg = await mailRepo.getMessage(req.params.id, acct.id);
281
282         if(!msg) {
283                 throw new NotFoundError('No such message', ERROR_CODE.DUPLICATE_CACHE_KEY);
284         }
285
286         await mailRepo.markAsRead(msg.id, msg.to_account);
287
288         return renderMessage(msg);
289 });
290
291 server.get<void, string>('/attacks/outgoing', async req => {
292         const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
293         const city = await cityRepo.getUsersCity(acct.id);
294         const attacks = await cityRepo.armyRepository.listOutgoing(city.id);
295
296         return listOperations(attacks);
297 });
298
299
300 server.start();