diff --git a/TODO.md b/TODO.md index 102f2cc..265835f 100644 --- a/TODO.md +++ b/TODO.md @@ -3,9 +3,9 @@ push offers add last synced time to want -contact options on landing page +go over and reorganise event logic -move want add to rpc +contact options on landing page skip unofficial release diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index a336aa9..2098e28 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -11,6 +11,11 @@ datasource db { } model Listing { + + // example listingId 2148851036 + // works fine as a JS number + // but JS numbers are cast to int4 for pg by prisma + // becomes -2146116260 id BigInt @id seller BigInt data Json diff --git a/api/src/api.ts b/api/src/api.ts index 77fbc40..f4bc9d9 100644 --- a/api/src/api.ts +++ b/api/src/api.ts @@ -63,16 +63,6 @@ export default function createApi(log: Log, pg: Pg, discogs: Discogs, offers: Of ctx.status = 200; }); - 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; diff --git a/api/src/events.ts b/api/src/events.ts index d3d901d..8d1c1e5 100644 --- a/api/src/events.ts +++ b/api/src/events.ts @@ -12,11 +12,11 @@ import routerAuth from './routers/router.auth'; export enum Events { WantsAdded = 'WantsAdded', Sync = 'Sync', - ListingsUpdated = 'ListingsUpdated', Connection = 'Connection', - OfferUpdated = 'OfferUpdated', - WantUpdated = 'WantUpdated', + ListingsUpdated = 'ListingsUpdated', + OffersUpdated = 'OffersUpdated', + WantsUpdated = 'WantsUpdated', } export default function createEvents(log: Log, pg: Pg, discogs, offers) { @@ -68,6 +68,10 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers) { events.on(Events.ListingsUpdated, async function listingsUpdated() { const updatedOffers = await offers.generate() log.debug({ offers: updatedOffers.length }, 'offers up to date'); + + for (const [id, rpc] of connections) { + rpc.pushUserOffers(); + } }); events.on(Events.Connection, async function onConnection({ rpc, id }) { @@ -75,17 +79,17 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers) { connections.set(id, rpc); }) - events.on(Events.OfferUpdated, async function onOfferUpdated(offer: Offer) { + events.on(Events.OffersUpdated, async function onOffersUpdated(offer: Offer) { const rpc = connections.get(offer.userId); if (!rpc) return null; - log.debug({ event: Events.OfferUpdated, rpc, offer }); + log.debug({ event: Events.OffersUpdated, rpc, offer }); return rpc.pushUserOffers(); }); - events.on(Events.WantUpdated, async function onWantUpdated(want: Want) { + events.on(Events.WantsUpdated, async function onWantsUpdated(want: Want) { const rpc = connections.get(want.userId); if (!rpc) return null; - log.debug({ event: Events.WantUpdated, rpc, want }); + log.debug({ event: Events.WantsUpdated, rpc, want }); return rpc.pushUserWants(); }); diff --git a/api/src/notify.ts b/api/src/notify.ts index 4fb016b..13145d8 100644 --- a/api/src/notify.ts +++ b/api/src/notify.ts @@ -21,8 +21,8 @@ export default function createNotify(log: Log, pg: Pg, events) { log.debug({ channel: OFFERS_CHANNEL, action, id }); if (action === 'DELETE') return null; - const offer = await pg.offerFindId(id); - // events.emit(Events.OfferUpdated, offer); + // const offer = await pg.offerFindId(id); + // events.emit(Events.OffersUpdated, offer); } async function handleUsers(payload) { @@ -37,6 +37,7 @@ export default function createNotify(log: Log, pg: Pg, events) { async function handleWants(payload) { const { action, id } = payload; + // const want = await pg.offerFindId(id); log.debug({ channel: WANTS_CHANNEL, action, id }); } diff --git a/api/src/pg.ts b/api/src/pg.ts index d729fe1..e408431 100644 --- a/api/src/pg.ts +++ b/api/src/pg.ts @@ -98,6 +98,12 @@ export default function createPg(log: Log, prisma: PrismaClient) { }); } + async function wantFindId(id: UUID) { + return prisma.want.findFirstOrThrow({ + where: { id }, + }); + } + async function wantList(user: User) { return prisma.want.findMany({ where: { userId: user.id }, diff --git a/api/src/rpc.ts b/api/src/rpc.ts index a007f03..e5b111c 100644 --- a/api/src/rpc.ts +++ b/api/src/rpc.ts @@ -37,6 +37,7 @@ export enum RpcMessageType { AcpListingsSync = 'AcpListingsSync', WantRm = 'WantRm', + WantAdd = 'WantAdd', } type ErrorMessage = [RpcMessageType.Error, string]; @@ -82,6 +83,10 @@ type WantRmParams = { id: UUID, }; +type WantAddParams = { + releaseId: number, +}; + function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType { if (RpcMessageType[msgType]) { return true; @@ -124,6 +129,18 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers return [RpcMessageType.Wants, wants]; } + async function wantAdd(params: WantAddParams): Promise { + const { releaseId } = params; + assert(releaseId, 'no id supplied'); + + const release = await discogs.release(releaseId); + const want = await pg.wantCreate(release, user); + events.emit(Events.WantsAdded, [want]); + + const wants = await pg.userWants(user); + return [RpcMessageType.Wants, wants]; + } + async function acpOfferStatusSet(params: OfferStatusSetParams): Promise { const { id, status } = params; assert(isAdmin(user), 'not authorised'); @@ -171,6 +188,8 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers case RpcMessageType.WantRm: return wantRm(params); + case RpcMessageType.WantAdd: + return wantAdd(params); case RpcMessageType.AcpOfferStatusSet: return acpOfferStatusSet(params); diff --git a/client/src/components/offer.group.tsx b/client/src/components/offer.group.tsx index 53f7086..c3915c7 100644 --- a/client/src/components/offer.group.tsx +++ b/client/src/components/offer.group.tsx @@ -49,8 +49,12 @@ function OfferPriceBtn(offer) { ? offer.status : 'Accept'; + const title = accepted + ? 'Click to cancel' + : 'Click to accept this offer'; + return ( - + ) } diff --git a/client/src/http.ts b/client/src/http.ts index a1e9605..e16e55c 100644 --- a/client/src/http.ts +++ b/client/src/http.ts @@ -33,15 +33,10 @@ api.interceptors.response.use(onSuccess, onError); const actions = { // TODO move to rpc - postWantSearch: async function postWantSearch(query) { return api.post('/want/search', { query }); }, - postWant: async function postWant(releaseId) { - return api.post('/want', { releaseId }); - }, - postDiscogs: async function postDiscogs(username) { return api.post('/want/discogs', { username }); }, @@ -71,14 +66,6 @@ const actions = { password, }); }, - - postOffers: async function postOffers(listingIds, shipping, baseShipping) { - return api.post('/offers', { - listingIds, - shipping, - baseShipping, - }); - }, }; export default actions; diff --git a/client/src/routes/search.tsx b/client/src/routes/search.tsx index 315cc13..43fc0c0 100644 --- a/client/src/routes/search.tsx +++ b/client/src/routes/search.tsx @@ -7,25 +7,22 @@ import { State } from './../store'; import http from '../http'; -function Result(r, wants) { - const [added, setAdded] = useState(false) - +function Result(r, wants, ws) { async function onClick() { - const [error, want] = await http.postWant(r.id); - if (error) return false; - setAdded(true); + ws.sendWantAdd(r.id); } - const wanted = wants.some(w => w.release === r.id); + // releaseIds are bigints on server + const wanted = wants.some(w => BigInt(w.release) === BigInt(r.id)); - const classes = `${added || wanted ? 'added' : ''}`; - const btnText = added + const classes = `${wanted ? 'added' : ''}`; + const btnText = wanted ? : '+'; return (
- +

{r.title}

{r.year}
@@ -45,6 +42,7 @@ const User = () => { const [infoText, setInfoText] = useState(''); const wants = useSelector((state: State) => state.wants); + const ws = useSelector((state: State) => state.ws); async function fetchResults() { const [error, results] = await http.postWantSearch(query); @@ -114,7 +112,7 @@ const User = () => {
{infoEl} - {results.map(r => Result(r, wants))} + {results.map(r => Result(r, wants, ws))} {topEl}
diff --git a/client/src/ws.ts b/client/src/ws.ts index 0a238ee..b22a6b5 100644 --- a/client/src/ws.ts +++ b/client/src/ws.ts @@ -37,6 +37,10 @@ export default function createSocket(dispatch) { send(['WantRm', { id }]); } + function sendWantAdd(releaseId) { + send(['WantAdd', { releaseId }]); + } + function sendOffers() { send(['Offers', {}]); } @@ -157,6 +161,7 @@ export default function createSocket(dispatch) { sendOfferStatusSet, sendWantRm, + sendWantAdd, sendOffers, sendWants,