129 lines
4.0 KiB
TypeScript
129 lines
4.0 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 { Context } from 'koa';
|
|
|
|
import { logAxiosError, wantDetails } from './utils';
|
|
import { Pg } from './pg';
|
|
|
|
export enum Status {
|
|
Pending = 'Pending',
|
|
Accepted = 'Accepted',
|
|
Declined = 'Declined',
|
|
Ordered = 'Ordered',
|
|
Complete = 'Complete',
|
|
}
|
|
|
|
export default function createOffers(log: Log, pg: Pg) {
|
|
async function generate(listingIds: DiscogsListing[], bulkShipping: number, baseShipping: number) {
|
|
log.info('generateOffers', { listingIds, bulkShipping, baseShipping });
|
|
const listings = await pg.listingByIds(listingIds);
|
|
|
|
// log.info({ listings }, 'creating offers for listings')
|
|
|
|
const shipping = 10 + Math.random(); // yolt
|
|
|
|
// const shipping = bulkShipping / listings.length;
|
|
// how to deal with users who don't want all items in a bundle...
|
|
|
|
const offers = listings.map(l => ({
|
|
userId: l.want.user.id,
|
|
listingId: l.id,
|
|
wantId: l.want.id,
|
|
|
|
// sellerId: l.seller,
|
|
|
|
status: Status.Pending,
|
|
|
|
item: l.data.price.value,
|
|
shipping,
|
|
total: l.data.price.value + shipping,
|
|
|
|
// without mcrates...
|
|
comparison: l.data.price.value + baseShipping,
|
|
}))
|
|
|
|
// 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']);
|
|
const inserts = await pg.offersCreate(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;
|
|
}
|
|
|
|
async function bundles() {
|
|
const bundles = await pg.bundleCandidates();
|
|
|
|
interface ListingData extends Listing {
|
|
data: any,
|
|
want: { user: User },
|
|
};
|
|
|
|
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;
|
|
});
|
|
}
|
|
|
|
|
|
return {
|
|
generate,
|
|
accept,
|
|
bundles,
|
|
statusSet,
|
|
};
|
|
}
|