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';
20 const server = new HttpServer(config.API_PORT);
22 const accountRepo = new AccountRepository();
23 const cityRepo = new CityRepository();
24 const mailRepo = new MailRepository();
28 new BullAdapter(tick.queue),
29 new BullAdapter(construction.queue),
30 new BullAdapter(unitTraining.queue),
31 new BullAdapter(fight.queue)
33 serverAdapter: server.bullAdapter,
37 body: {username: string, password: string}},
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);
45 const acct = await accountRepo.create(username, password);
47 // lets create the city!
48 await cityRepo.create(acct.id);
50 return `<p>You are all signed up! You can go ahead and log in</p>`;
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);
58 const {account, session} = await accountRepo.login(username, password);
60 throw new NotFoundError('Invalid username or password', ERROR_CODE.USER_NOT_FOUND);
63 res.setHeader('hx-redirect', `/game.html?token=${session.id}&id=${session.account_id}`);
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 });
72 const buildQueues = await cityRepo.getBuildQueues(account.id);
73 const unitTrainingQueues = await cityRepo.getUnitTrainingQueues(account.id);
75 const buildings = await cityRepo.buildingRepository.list();
76 const units = await cityRepo.unitRepository.list();
79 <h2>Kingom Overview</h2>
80 ${renderKingomOverview(city, account)}
82 <h2>Land Development</h2>
83 ${renderLandDevelopment(city, buildings, buildQueues)}
84 <h2>Unit Training</h2>
85 ${renderUnitTraining(city, units, unitTrainingQueues)},
87 ${renderOverworldMap(await cityRepo.FindAll(), city)}
89 ${renderMailroom(await mailRepo.listReceivedMessages(account.id))}
100 }}, string>('/attack-power', async req => {
101 const power = await cityRepo.power(req.body);
103 return power.toLocaleString();
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});
114 return await launchOffensive(city, acct || {
118 }, yourCity, account);
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 });
125 return renderKingomOverview(city, account);
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();
133 const buildQueues = await cityRepo.getBuildQueues(account.id);
134 return renderLandDevelopment(city, buildings, buildQueues);
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 });
141 const unitTrainingQueues = await cityRepo.getUnitTrainingQueues(account.id);
142 const units = await cityRepo.unitRepository.list();
144 return renderUnitTraining(city, units, unitTrainingQueues);
151 building_type: string
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);
158 throw new NotFoundError(`Invalid building type ${req.body.building_type}`, ERROR_CODE.INVALID_BUILDING);
161 return JSON.stringify({
162 gold: building.gold * amount,
163 ore: building.ore * amount,
164 logs: building.logs * amount,
165 land: building.land * amount,
175 }, string>('/cost/training', async req => {
176 const amount = parseInt(req.body.amount, 10);
177 const unit = await cityRepo.unitRepository.findBySlug(req.body.type);
180 throw new NotFoundError(`Invalid unit type ${req.body.type}`, ERROR_CODE.INVALID_UNIT);
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
196 building_type: string,
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);
202 const amount = parseInt(req.body.amount, 10);
203 const building = await cityRepo.buildingRepository.findBySlug(req.body.building_type);
206 throw new NotFoundError(`Invalid building type ${req.body.building_type}`, ERROR_CODE.INVALID_BUILDING);
209 const queueData = await cityRepo.buildBuilding(building, amount ,city);
211 construction.trigger(queueData, { delay: queueData.due });
212 }, 'reload-construction-queue');
221 >('/units', async req => {
222 const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
223 const city = await cityRepo.getUsersCity(acct.id);
225 const amount = parseInt(req.body.amount, 10);
226 const unit = await cityRepo.unitRepository.findBySlug(req.body.type);
229 throw new NotFoundError(`Invalid unit type ${req.body.type}`, ERROR_CODE.INVALID_UNIT);
232 const queueData = await cityRepo.train(unit, amount, city);
233 unitTraining.trigger(queueData, { delay: queueData.due });
235 }, 'reload-unit-training');
243 sp_attackers: string,
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});
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)
261 const armyQueue = await cityRepo.attack(city, attackedCity, army);
263 fight.trigger(armyQueue, {
264 delay: armyQueue.due - Date.now()
266 }, 'reload-outgoing-attacks');
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);
272 return JSON.stringify(msgs);
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);
280 throw new NotFoundError('No such message', ERROR_CODE.DUPLICATE_CACHE_KEY);
283 await mailRepo.markAsRead(msg.id, msg.to_account);
285 return renderMessage(msg);
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);
293 return listOperations(attacks);
299 tick.trigger({lastTickAt: 0, lastTick: 0});