153 lines
4.7 KiB
TypeScript
153 lines
4.7 KiB
TypeScript
import type Log from 'bunyan';
|
|
import type { PrismaClient, Want, User, Listing, Offer, } from '@prisma/client';
|
|
import assert from 'assert/strict';
|
|
import groupBy from 'lodash/groupBy';
|
|
import sortBy from 'lodash/sortBy';
|
|
import flatMap from 'lodash/flatMap';
|
|
import { Context } from 'koa';
|
|
|
|
import { logAxiosError, wantDetails } from './utils';
|
|
import { Pg } from './pg';
|
|
|
|
export enum Status {
|
|
Complete = 'Complete',
|
|
Confirmed = 'Confirmed',
|
|
Accepted = 'Accepted',
|
|
Pending = 'Pending',
|
|
Declined = 'Declined',
|
|
}
|
|
|
|
function sortOffers(o) {
|
|
return Object.values(Status).indexOf(o.status);
|
|
}
|
|
|
|
export default function createOffers(log: Log, pg: Pg) {
|
|
async function generate() {
|
|
const listings = flatMap(await pg.interestingListings());
|
|
log.debug('generateOffers', { listings: listings.length });
|
|
|
|
const shipping = 7.5; // yolt
|
|
|
|
const offers = listings.map(l => ({
|
|
userId: l.want.user.id,
|
|
listingId: l.id,
|
|
wantId: l.want.id,
|
|
|
|
status: Status.Pending,
|
|
|
|
condition: l.data.condition,
|
|
|
|
item: l.data.price.value,
|
|
shipping,
|
|
total: l.data.price.value + shipping,
|
|
}))
|
|
|
|
// we must sort the offers by price,
|
|
// sometimes a seller has multiples of a want
|
|
// to deal with this we just ignore duplicates on insert
|
|
// and ensure the cheapest one goes in first
|
|
const sorted = sortBy(offers, ['item']);
|
|
|
|
// TODO probably need to take one of each condition of record from a store
|
|
// eg. they have a VG+ and a M we should make offers for each
|
|
|
|
log.trace('generated offers', sorted);
|
|
const inserts = await pg.offersUpdate(sorted);
|
|
|
|
return inserts;
|
|
}
|
|
|
|
async function accept(ctx: Context) {
|
|
const { id } = ctx.request.body;
|
|
ctx.assert(id, 401, 'no id supplied');
|
|
|
|
// check the user actually has this offer
|
|
const offer = await pg.userOfferFind(ctx.state.user, id);
|
|
return pg.offerStatusSet(offer.id, Status.Accepted);
|
|
}
|
|
|
|
async function statusSet(user: User, id: UUID, status: Status) {
|
|
log.info({ status, id }, 'setting offer status');
|
|
|
|
const offer = await pg.offerFindId(id);
|
|
const update = await pg.offerStatusSet(offer.id, status);
|
|
|
|
if (status === Status.Complete) {
|
|
log.info({ id, status }, 'completed offer, removing want...');
|
|
await pg.wantRm(offer.wantId);
|
|
}
|
|
|
|
return update;
|
|
}
|
|
|
|
// converts offers into groups based on seller
|
|
// includes some bits of info about shipping etc
|
|
async function acpOffers() {
|
|
const offers = await pg.offersAdmin();
|
|
|
|
const grouped = groupBy(offers, o => o.listing.data.seller.id);
|
|
|
|
const sellerOffers = 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: sortBy(offers, sortOffers),
|
|
mentionsAustralia: shipping.toLowerCase().includes('australia'),
|
|
mentionsWorldwide: shipping.toLowerCase().includes('world'),
|
|
}
|
|
|
|
return bundle;
|
|
}), bundle => -bundle.offers.length);
|
|
|
|
return sellerOffers;
|
|
}
|
|
|
|
// also groups offers by seller for users
|
|
// but mostly just munges the structure so the details
|
|
// are easily available
|
|
async function userOffers(user: User) {
|
|
const offers = await pg.userOffers(user);
|
|
const groups = Object.values(groupBy(offers, (o) => o.wantId))
|
|
.map(g => {
|
|
// the offer status is just the most advanced status
|
|
// based on sort
|
|
const offers = sortBy(g, sortOffers);
|
|
const status = offers[0].status;
|
|
|
|
// now actually sort them by price
|
|
offers.sort((a, b) => a.total - b.total);
|
|
|
|
const details = offers[0].want.data;
|
|
|
|
return {
|
|
details,
|
|
offers,
|
|
status,
|
|
};
|
|
});
|
|
|
|
return groups;
|
|
}
|
|
|
|
async function samples() {
|
|
const user = await pg.userFindEmail('ntr@mnml.gg');
|
|
const offerGroups = await userOffers(user);
|
|
const i = Math.floor(Math.random() * offerGroups.length / 2);
|
|
return offerGroups[i];
|
|
}
|
|
|
|
return {
|
|
generate,
|
|
accept,
|
|
statusSet,
|
|
acpOffers,
|
|
userOffers,
|
|
samples,
|
|
};
|
|
}
|
|
|
|
export type Offers = ReturnType<typeof createOffers>;
|