Files
crates/api/src/api.ts
T
2022-08-30 23:02:34 +10:00

204 lines
6.5 KiB
TypeScript

import http from 'http';
import koa from 'koa';
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';
export default function createApi(log: Log, pg: Pg, discogs, offers, events) {
const PORT = 41337;
const SECRET = 'ohja4Eif9eidah3aj5ta2haa4iemooth';
const app = new koa();
const server = http.createServer(app.callback());
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();
interface ListingData extends Listing {
data: any,
want: { user: User },
};
async function getBundles() {
const bundles = await pg.bundleCandidates();
return bundles.map((b: ListingData[]) => {
const total = b.reduce((acc, l) => acc + l.data.price.value, 0);
const items = b.map(l => ({
id: l.data.id,
description: l.data.release.description,
originalPrice: l.data.original_price.value,
originalCurrency: l.data.original_price.curr_abbr,
price: l.data.price.value,
currency: l.data.price.currency,
condition: l.data.condition,
uri: l.data.uri,
user: l.want.user,
}));
const shipping = b[0]?.data.seller.shipping;
const bundle = {
username: b[0]?.data.seller.username,
seller: b[0]?.data.seller.id,
shipsFrom: b[0]?.data.ships_from,
total,
currency: b[0]?.data.price.currency,
shipping: '...',
items,
mentionsAustralia: shipping.toLowerCase().includes('australia'),
mentionsWorldwide: shipping.toLowerCase().includes('world'),
}
return bundle;
});
}
function listen() {
return new Promise((resolve, reject) => {
app.on('error', err => {
reject(err);
})
server.listen(PORT, () => {
log.info('listening', { PORT });
});
});
}
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 getBundles();
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.WantsAdded, [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);
events.emit(Events.WantsAdded, wants);
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());
return {
app,
server,
listen,
};
}