initial commit
[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/fight';
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 `<p>You are all signed up! You can go ahead and log in</p>`;
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.get<{}, string>('/city', async req => {
68         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
69         const city = await cityRepo.FindOne({ owner: account.id });
70
71
72         const buildQueues = await cityRepo.getBuildQueues(account.id);
73         const unitTrainingQueues = await cityRepo.getUnitTrainingQueues(account.id);
74
75         const buildings = await cityRepo.buildingRepository.list();
76         const units = await cityRepo.unitRepository.list();
77
78         let html = `
79         <h2>Kingom Overview</h2>
80         ${renderKingomOverview(city, account)}
81         <hr>
82         <h2>Land Development</h2>
83         ${renderLandDevelopment(city, buildings, buildQueues)}
84         <h2>Unit Training</h2>
85         ${renderUnitTraining(city, units, unitTrainingQueues)},
86         <h2>Map</h2>
87         ${renderOverworldMap(await cityRepo.FindAll(), city)}
88         <h2>Mail</h2>
89         ${renderMailroom(await mailRepo.listReceivedMessages(account.id))}
90         `;
91         return html;
92 });
93
94 server.post<{body: {
95         soldiers: number,
96         attackers: number,
97         defenders: number,
98         sp_attackers: number,
99         sp_defenders: number
100   }}, string>('/attack-power', async req => {
101         const power = await cityRepo.power(req.body);
102
103         return power.toLocaleString();
104
105 });
106
107 server.get<{params: { cityId: string }}, string>('/city/:cityId', async req => {
108         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
109         const yourCity = await cityRepo.FindOne({ owner: account.id });
110         const city = await cityRepo.FindOne({ id: req.params.cityId });
111         const acct = await accountRepo.FindOne({id: city.owner});
112
113
114         return await launchOffensive(city, acct || {
115                 id: '-',
116                 username: 'Rebels',
117                 password: ''
118         }, yourCity, account);
119 });
120
121 server.get<{}, string>('/poll/overview', async req => {
122         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
123         const city = await cityRepo.FindOne({ owner: account.id });
124
125         return renderKingomOverview(city, account);
126 });
127
128 server.get<{}, string>('/queue/construction', async req => {
129         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
130         const city = await cityRepo.FindOne({ owner: account.id });
131         const buildings = await cityRepo.buildingRepository.list();
132
133         const buildQueues = await cityRepo.getBuildQueues(account.id);
134         return renderLandDevelopment(city, buildings, buildQueues);
135 });
136
137 server.get<{}, string>('/queue/units', async req => {
138         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
139         const city = await cityRepo.FindOne({ owner: account.id });
140
141         const unitTrainingQueues = await cityRepo.getUnitTrainingQueues(account.id);
142         const units = await cityRepo.unitRepository.list();
143
144         return renderUnitTraining(city, units, unitTrainingQueues);
145 });
146
147
148 server.post<{
149         body: {
150                 amount: string,
151                 building_type: string
152         }
153 }, string>('/cost/construction', async req => {
154         const amount = parseInt(req.body.amount, 10);
155         const building = await cityRepo.buildingRepository.findBySlug(req.body.building_type);
156
157         if(!building) {
158                 throw new NotFoundError(`Invalid building type ${req.body.building_type}`, ERROR_CODE.INVALID_BUILDING);
159         }
160
161         return JSON.stringify({
162                 gold: building.gold * amount,
163                 ore: building.ore * amount,
164                 logs: building.logs * amount,
165                 land: building.land * amount,
166                 time: building.time
167         });
168 });
169
170 server.post<{
171         body: {
172                 amount: string;
173                 type: string;
174         }
175 }, string>('/cost/training', async req => {
176         const amount = parseInt(req.body.amount, 10);
177         const unit = await cityRepo.unitRepository.findBySlug(req.body.type);
178
179         if(!unit) {
180                 throw new NotFoundError(`Invalid unit type ${req.body.type}`, ERROR_CODE.INVALID_UNIT);
181         }
182
183         return JSON.stringify({
184                 population: unit.population * amount,
185                 soldiers: unit.soldiers * amount,
186                 attackers: unit.attackers * amount,
187                 defenders: unit.defenders * amount,
188                 gold: unit.gold * amount,
189                 bushels: unit.bushels * amount
190         });
191 });
192
193 server.post<{
194         body: {
195                 amount: string,
196                 building_type: string,
197         }
198 }, void>('/build', async req => {
199         const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
200         const city = await cityRepo.getUsersCity(account.id);
201
202         const amount = parseInt(req.body.amount, 10);
203         const building = await cityRepo.buildingRepository.findBySlug(req.body.building_type);
204
205         if(!building) {
206                 throw new NotFoundError(`Invalid building type ${req.body.building_type}`, ERROR_CODE.INVALID_BUILDING);
207         }
208
209         const queueData = await cityRepo.buildBuilding(building, amount ,city);
210
211         construction.trigger(queueData, { delay: queueData.due });
212 }, 'reload-construction-queue');
213
214 server.post<{
215                 body: {
216                         amount: string,
217                         type: string
218                 }
219         },
220         void
221         >('/units', async req => {
222         const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
223         const city = await cityRepo.getUsersCity(acct.id);
224
225         const amount  = parseInt(req.body.amount, 10);
226         const unit = await cityRepo.unitRepository.findBySlug(req.body.type);
227
228         if(!unit) {
229                 throw new NotFoundError(`Invalid unit type ${req.body.type}`, ERROR_CODE.INVALID_UNIT);
230         }
231
232         const queueData = await cityRepo.train(unit, amount, city);
233         unitTraining.trigger(queueData, { delay: queueData.due });
234
235 }, 'reload-unit-training');
236
237 server.post<{
238         body: {
239                 city: string,
240                 soldiers: string,
241                 attackers: string,
242                 defenders: string,
243                 sp_attackers: string,
244                 sp_defenders: string
245         }
246         }, 
247         void
248         >('/attack', async req => {
249                 const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
250                 const city = await cityRepo.getUsersCity(acct.id);
251                 const attackedCity = await cityRepo.FindOne({id: req.body.city});
252
253                 const army = {
254                         soldiers: parseInt(req.body.soldiers),
255                         attackers: parseInt(req.body.attackers),
256                         defenders: parseInt(req.body.defenders),
257                         sp_attackers: parseInt(req.body.sp_attackers),
258                         sp_defenders: parseInt(req.body.sp_defenders)
259                 };
260
261                 const armyQueue = await cityRepo.attack(city, attackedCity, army);
262
263                 fight.trigger(armyQueue, {
264                         delay: armyQueue.due - Date.now()
265                 });
266         }, 'reload-outgoing-attacks');
267
268 server.get<void, string>('/messages', async req => {
269         const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
270         const msgs = await mailRepo.listReceivedMessages(acct.id);
271
272         return JSON.stringify(msgs);
273 });
274
275 server.get<{params: {id: string}}, string>('/messages/:id', async req => {
276         const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
277         const msg = await mailRepo.getMessage(req.params.id, acct.id);
278
279         if(!msg) {
280                 throw new NotFoundError('No such message', ERROR_CODE.DUPLICATE_CACHE_KEY);
281         }
282
283         await mailRepo.markAsRead(msg.id, msg.to_account);
284
285         return renderMessage(msg);
286 });
287
288 server.get<void, string>('/attacks/outgoing', async req => {
289         const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
290         const city = await cityRepo.getUsersCity(acct.id);
291         const attacks = await cityRepo.armyRepository.listOutgoing(city.id);
292
293         return listOperations(attacks);
294 });
295
296
297 server.start();
298
299 tick.trigger({lastTickAt: 0, lastTick: 0});