339 lines
8.9 KiB
TypeScript
339 lines
8.9 KiB
TypeScript
import bcrypt from 'bcrypt';
|
|
import type Log from 'bunyan';
|
|
import type { PrismaClient, Want, User, Listing, Offer, Prisma } from '@prisma/client';
|
|
import * as Bluebird from 'bluebird';
|
|
import _ from 'lodash';
|
|
|
|
import { Status } from './offers';
|
|
import { wantDetails } from './utils';
|
|
|
|
export interface UserCreateParams {
|
|
email: string,
|
|
password: string,
|
|
}
|
|
|
|
export default function createPg(log: Log, prisma: PrismaClient) {
|
|
async function userFindId(id: UUID) {
|
|
return prisma.user.findUnique({ where: { id }});
|
|
}
|
|
|
|
async function userFindEmail(email: Email) {
|
|
return prisma.user.findUnique({ where: { email }});
|
|
}
|
|
|
|
async function userWants(user: User) {
|
|
const wants = await prisma.want.findMany({
|
|
where: { userId: user.id },
|
|
include: { _count: { select: { offers: true }} },
|
|
})
|
|
|
|
return _.sortBy(wants, w => `${w.data.artists_sort} - ${w.data.title}`);
|
|
}
|
|
|
|
async function userOffers(user: User) {
|
|
return prisma.offer.findMany({
|
|
where: {
|
|
userId: user.id,
|
|
status: { not: Status.Declined }
|
|
},
|
|
include: {
|
|
want: true,
|
|
},
|
|
orderBy: [
|
|
{ updatedAt: 'asc' },
|
|
{ total: 'asc' },
|
|
{ listingId: 'asc' },
|
|
],
|
|
});
|
|
}
|
|
|
|
async function offerFindId(id: UUID) {
|
|
return prisma.offer.findFirstOrThrow({
|
|
where: { id },
|
|
include: {
|
|
want: true,
|
|
}
|
|
});
|
|
}
|
|
|
|
async function userOfferFind(user: User, id: UUID) {
|
|
return prisma.offer.findFirstOrThrow({
|
|
where: {
|
|
userId: user.id,
|
|
id,
|
|
},
|
|
include: {
|
|
want: true,
|
|
}
|
|
});
|
|
}
|
|
|
|
async function userCreate(params: UserCreateParams) {
|
|
const hashRounds = 10;
|
|
const password = await bcrypt.hash(params.password, hashRounds);
|
|
|
|
const data = {
|
|
email: params.email,
|
|
password,
|
|
};
|
|
|
|
return prisma.user.create({
|
|
data,
|
|
});
|
|
}
|
|
|
|
// userUpdate({ email: 'jen@vdk.com', password: 'jen' });
|
|
// userUpdate({ email: 'james@mcd', password: 'james' });
|
|
|
|
async function userUpdate(params: UserCreateParams) {
|
|
const hashRounds = 10;
|
|
const password = await bcrypt.hash(params.password, hashRounds);
|
|
|
|
const data = {
|
|
email: params.email,
|
|
password,
|
|
};
|
|
|
|
return prisma.user.update({
|
|
where: { email: params.email },
|
|
data,
|
|
});
|
|
}
|
|
|
|
async function wantFindId(id: UUID) {
|
|
return prisma.want.findFirstOrThrow({
|
|
where: { id },
|
|
});
|
|
}
|
|
|
|
async function wantList(user: User) {
|
|
return prisma.want.findMany({
|
|
where: { userId: user.id },
|
|
});
|
|
}
|
|
|
|
async function wantsToSync() {
|
|
return prisma.want.findMany({
|
|
orderBy: { updatedAt: 'asc' },
|
|
where: {
|
|
updatedAt: { lte: new Date(Date.now() - 1000 * 60 * 15) }
|
|
}
|
|
});
|
|
}
|
|
|
|
async function wantRm(id: UUID) {
|
|
await prisma.want.delete({
|
|
where: { id },
|
|
});
|
|
}
|
|
|
|
async function wantCreate(discogsRelease, user) {
|
|
const data: { userId: UUID, release: DiscogsListing, data: any } = {
|
|
data: discogsRelease,
|
|
release: discogsRelease.id,
|
|
userId: user.id,
|
|
};
|
|
|
|
return prisma.want.create({
|
|
data,
|
|
});
|
|
}
|
|
|
|
async function wantSynced(want: Want) {
|
|
return prisma.want.update({
|
|
where: { id: want.id },
|
|
data: { updatedAt: new Date() },
|
|
});
|
|
}
|
|
|
|
async function listingRmUnlisted(want: Want, currentListingIds: DiscogsListing[]) {
|
|
return prisma.$transaction(async (prisma) => {
|
|
log.debug({ currentListingIds });
|
|
|
|
const protectedOfferStatuses = [Status.Confirmed, Status.Complete];
|
|
|
|
// delete all the offers where we don't want to keep them
|
|
// for historical reasons
|
|
const { count: offersRemoved } = await prisma.offer.deleteMany({
|
|
where: {
|
|
wantId: want.id,
|
|
listingId: {
|
|
notIn: currentListingIds,
|
|
},
|
|
status: {
|
|
notIn: protectedOfferStatuses,
|
|
},
|
|
}
|
|
});
|
|
|
|
if (offersRemoved > 0) log.info({ offersRemoved, ...wantDetails(want) }, 'pending offers removed');
|
|
|
|
// now we have to filter listingIds with confirmed offers
|
|
// and get their listingIds to filter out
|
|
const confirmedListingIds = (await prisma.offer.findMany({
|
|
where: {
|
|
wantId: want.id,
|
|
status: {
|
|
in: protectedOfferStatuses,
|
|
},
|
|
},
|
|
select: {
|
|
listingId: true,
|
|
},
|
|
})).map(r => r.listingId);
|
|
const removalIds = currentListingIds.filter(id => confirmedListingIds.includes(id));
|
|
|
|
if (removalIds.length) log.info({ removalIds });
|
|
|
|
// finally we delete listings that aren't related
|
|
// to a historical offer
|
|
const { count } = await prisma.listing.deleteMany({
|
|
where: {
|
|
id: { in: removalIds },
|
|
},
|
|
});
|
|
|
|
if (count) log.info({ count }, 'second stage open offers removed');
|
|
|
|
return count;
|
|
});
|
|
}
|
|
|
|
async function listingUpsert(listing, want) {
|
|
const values = {
|
|
id: listing.id,
|
|
wantId: want.id,
|
|
seller: listing.seller.id,
|
|
data: listing,
|
|
};
|
|
|
|
return prisma.listing.upsert({
|
|
where: {
|
|
id: listing.id
|
|
},
|
|
update: values,
|
|
create: values,
|
|
});
|
|
}
|
|
|
|
async function listingFind(id: DiscogsListing) {
|
|
return prisma.listing.findUnique({
|
|
where: {
|
|
id,
|
|
},
|
|
include: {
|
|
want: {
|
|
include: { user: true },
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async function listingByIds(ids: DiscogsListing[]) {
|
|
return prisma.listing.findMany({
|
|
where: {
|
|
id: { in: ids },
|
|
},
|
|
include: {
|
|
want: {
|
|
include: { user: true },
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async function interestingListings() {
|
|
// this basically just needs to return the listingIds to generate offers
|
|
// but for ease of abstraction we get the listings themselves
|
|
// grouped by seller
|
|
const candidates = await prisma.$queryRaw`
|
|
WITH groups AS (
|
|
select
|
|
seller, count(distinct("wantId")), array_agg(id) as listings
|
|
from
|
|
listings
|
|
where
|
|
seller not in (3709203)
|
|
group by
|
|
seller
|
|
order by
|
|
count desc
|
|
)
|
|
select *
|
|
from groups
|
|
where count >= 5;
|
|
` as BundleCandidate[];
|
|
|
|
return Promise.all(candidates.map(async c => prisma.listing.findMany({
|
|
where: {
|
|
id: { in: c.listings }
|
|
},
|
|
include: {
|
|
want: {
|
|
include: { user: true },
|
|
}
|
|
},
|
|
distinct: ['wantId'],
|
|
})));
|
|
}
|
|
|
|
async function offersUpdate(offers) {
|
|
return await prisma.$transaction(
|
|
offers.map(offer => prisma.offer.upsert({
|
|
where: {
|
|
listingId: offer.listingId,
|
|
},
|
|
create: offer,
|
|
update: _.omit(offer, ['status', 'id']),
|
|
}))
|
|
);
|
|
}
|
|
|
|
async function offerStatusSet(ids: UUID[], status: Status) {
|
|
return prisma.offer.updateMany({
|
|
where: { id: { in: ids } },
|
|
data: { status },
|
|
});
|
|
}
|
|
|
|
// effectively all offers
|
|
async function offersAdmin() {
|
|
return prisma.offer.findMany({
|
|
include: {
|
|
listing: true,
|
|
user: true,
|
|
want: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
return {
|
|
userFindId,
|
|
userFindEmail,
|
|
userCreate,
|
|
userWants,
|
|
userOffers,
|
|
userOfferFind,
|
|
|
|
wantCreate,
|
|
wantList,
|
|
wantsToSync,
|
|
wantSynced,
|
|
wantRm,
|
|
|
|
listingUpsert,
|
|
listingFind,
|
|
listingRmUnlisted,
|
|
listingByIds,
|
|
|
|
interestingListings,
|
|
|
|
offersUpdate,
|
|
offerStatusSet,
|
|
offersAdmin,
|
|
offerFindId,
|
|
};
|
|
}
|
|
|
|
export type Pg = ReturnType<typeof createPg>;
|