187 lines
6.1 KiB
TypeScript
187 lines
6.1 KiB
TypeScript
import http from 'http';
|
|
|
|
import koa from 'koa';
|
|
import koaWebsocket from 'koa-websocket';
|
|
import bodyParser from 'koa-body';
|
|
import session from 'koa-session';
|
|
import koaRouter from '@koa/router'
|
|
import cors from '@koa/cors';
|
|
|
|
import { groupBy, sortBy } from 'lodash';
|
|
import Bluebird from 'bluebird';
|
|
|
|
import type Log from 'bunyan';
|
|
import type { Listing, User, Want, Prisma } from '@prisma/client';
|
|
|
|
import { Pg } from './pg';
|
|
import routerAuth from './routers/router.auth';
|
|
import { Events } from './events';
|
|
import createRpc, { RpcMessageType } from './rpc';
|
|
|
|
export default function createApi(log: Log, pg: Pg, discogs, offers, events) {
|
|
const PORT = 41337;
|
|
const WS_PORT = 40055;
|
|
const SECRET = 'ohja4Eif9eidah3aj5ta2haa4iemooth';
|
|
|
|
const app = koaWebsocket(new koa());
|
|
|
|
app.use(async (ctx, next) => {
|
|
try {
|
|
await next();
|
|
} catch (err) {
|
|
log.error(err);
|
|
ctx.status = err.statusCode || err.status || 500;
|
|
ctx.body = {
|
|
error: err.message
|
|
};
|
|
}
|
|
});
|
|
|
|
const router = koaRouter();
|
|
|
|
function listen() {
|
|
const server = app.listen(PORT);
|
|
log.info({ PORT }, 'listening');
|
|
}
|
|
|
|
app.use(cors({ credentials: true }));
|
|
app.use(bodyParser())
|
|
|
|
app.keys = [SECRET];
|
|
app.use(session(app));
|
|
|
|
const { authRouter, passport } = routerAuth(log, pg);
|
|
app.use(passport.initialize());
|
|
app.use(passport.session())
|
|
app.use(authRouter.routes());
|
|
|
|
router.get('/health', async function health(ctx) {
|
|
ctx.status = 200;
|
|
});
|
|
|
|
router.get('/acp/bundles', async function bundlesGet(ctx, next) {
|
|
const bundles = await offers.bundles();
|
|
ctx.response.body = bundles;
|
|
})
|
|
|
|
router.get('/acp/offers', async function acpOffers(ctx, next) {
|
|
const offers = await pg.offersAdmin();
|
|
|
|
const grouped = groupBy(offers, o => o.listing.data.seller.id);
|
|
|
|
const bundles = sortBy(Object.values(grouped).map(offers => {
|
|
const shipping = offers[0]?.listing.data.seller.shipping;
|
|
const bundle = {
|
|
username: offers[0]?.listing.data.seller.username,
|
|
seller: offers[0]?.listing.data.seller.id,
|
|
shipsFrom: offers[0]?.listing.data.ships_from,
|
|
shipping,
|
|
offers,
|
|
mentionsAustralia: shipping.toLowerCase().includes('australia'),
|
|
mentionsWorldwide: shipping.toLowerCase().includes('world'),
|
|
}
|
|
|
|
return bundle;
|
|
}), bundle => -bundle.offers.length);
|
|
|
|
ctx.response.body = bundles;
|
|
})
|
|
|
|
router.post('/offers', async function postOffers(ctx, next) {
|
|
const { listingIds, shipping, baseShipping } = ctx.request.body;
|
|
|
|
ctx.assert(listingIds.length, 400, 'listingIds required');
|
|
ctx.assert([listingIds, shipping, baseShipping].every(i => i), 400, 'listingIds, shipping, baseShipping required');
|
|
|
|
ctx.response.body = await offers.generate(listingIds, shipping, baseShipping);
|
|
})
|
|
|
|
router.post('/offer/accept', async function postOffers(ctx, next) {
|
|
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
|
ctx.response.body = await offers.accept(ctx);
|
|
})
|
|
|
|
router.get('/user/offers', async function userOffersGet(ctx, next) {
|
|
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
|
const user = ctx.state.user;
|
|
const offers = await pg.userOffers(user);
|
|
ctx.response.body = offers;
|
|
});
|
|
|
|
router.post('/want', async function wantPost(ctx, next) {
|
|
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
|
const { releaseId } = ctx.request.body;
|
|
const user = await pg.userFindId(ctx.state.user.id);
|
|
const release = await discogs.release(releaseId);
|
|
const want = await pg.wantCreate(release, user);
|
|
events.emit(Events.WantAdded, want);
|
|
ctx.response.body = [want];
|
|
});
|
|
|
|
router.post('/want/discogs', async function wantListPost(ctx, next) {
|
|
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
|
const { username } = ctx.request.body;
|
|
const user = await pg.userFindId(ctx.state.user.id);
|
|
const wants = await discogs.wantListSync(username, user);
|
|
wants.forEach(w => events.emit(Events.WantAdded, w));
|
|
ctx.response.body = wants;
|
|
});
|
|
|
|
router.get('/want/all', async function wantGet(ctx, next) {
|
|
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
|
const user = ctx.state.user;
|
|
const wants = await pg.wantList(user);
|
|
|
|
ctx.response.body = wants;
|
|
});
|
|
|
|
router.post('/want/search', async function wantPost(ctx, next) {
|
|
const { artist, title } = ctx.request.body;
|
|
const results = await discogs.search(artist, title);
|
|
ctx.response.body = results;
|
|
});
|
|
|
|
router.post('/auth/logout', async (ctx, next) => {
|
|
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
|
ctx.logout();
|
|
ctx.status = 200;
|
|
ctx.body = { success: true };
|
|
});
|
|
|
|
app.use(router.routes());
|
|
|
|
app.ws.use(passport.initialize());
|
|
app.ws.use(passport.session());
|
|
app.ws.use(async (ctx, next) => {
|
|
log.info('new ws connection', { user: ctx.state.user });
|
|
|
|
// log.child...
|
|
|
|
const ws = ctx.websocket;
|
|
const rpc = createRpc(log, pg, ctx.state.user, ws, discogs, offers);
|
|
|
|
if (ctx.state.user) events.emit(Events.Connection, { ws, id: ctx.state.user.id });
|
|
ws.on('message', async function incoming(message) {
|
|
try {
|
|
const parsed = JSON.parse(message.toString());
|
|
const response = await rpc.receive(parsed);
|
|
rpc.send(response);
|
|
} catch (e) {
|
|
log.warn(e);
|
|
log.warn(message.toString());
|
|
rpc.send([RpcMessageType.Error, e.message || e.msg]);
|
|
}
|
|
return true;
|
|
});
|
|
|
|
ws.on('close', function onClose() {
|
|
log.info('ws disconnected', { user: ctx.state.user });
|
|
});
|
|
});
|
|
|
|
return {
|
|
app,
|
|
listen,
|
|
};
|
|
}
|