navbar and notifications
This commit is contained in:
1 parent
a97560c1c7
commit
5b0c7c05ca
11 files changed
+65
-46
No files matched your search
@@ -3,9 +3,9 @@ push offers
|
|||||||
|
|
||||||
add last synced time to want
|
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
|
skip unofficial release
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ datasource db {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Listing {
|
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
|
id BigInt @id
|
||||||
seller BigInt
|
seller BigInt
|
||||||
data Json
|
data Json
|
||||||
|
|||||||
@@ -63,16 +63,6 @@ export default function createApi(log: Log, pg: Pg, discogs: Discogs, offers: Of
|
|||||||
ctx.status = 200;
|
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) {
|
router.post('/want/discogs', async function wantListPost(ctx, next) {
|
||||||
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
||||||
const { username } = ctx.request.body;
|
const { username } = ctx.request.body;
|
||||||
|
|||||||
+11
-7
@@ -12,11 +12,11 @@ import routerAuth from './routers/router.auth';
|
|||||||
export enum Events {
|
export enum Events {
|
||||||
WantsAdded = 'WantsAdded',
|
WantsAdded = 'WantsAdded',
|
||||||
Sync = 'Sync',
|
Sync = 'Sync',
|
||||||
ListingsUpdated = 'ListingsUpdated',
|
|
||||||
Connection = 'Connection',
|
Connection = 'Connection',
|
||||||
|
|
||||||
OfferUpdated = 'OfferUpdated',
|
ListingsUpdated = 'ListingsUpdated',
|
||||||
WantUpdated = 'WantUpdated',
|
OffersUpdated = 'OffersUpdated',
|
||||||
|
WantsUpdated = 'WantsUpdated',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function createEvents(log: Log, pg: Pg, discogs, offers) {
|
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() {
|
events.on(Events.ListingsUpdated, async function listingsUpdated() {
|
||||||
const updatedOffers = await offers.generate()
|
const updatedOffers = await offers.generate()
|
||||||
log.debug({ offers: updatedOffers.length }, 'offers up to date');
|
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 }) {
|
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);
|
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);
|
const rpc = connections.get(offer.userId);
|
||||||
if (!rpc) return null;
|
if (!rpc) return null;
|
||||||
log.debug({ event: Events.OfferUpdated, rpc, offer });
|
log.debug({ event: Events.OffersUpdated, rpc, offer });
|
||||||
return rpc.pushUserOffers();
|
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);
|
const rpc = connections.get(want.userId);
|
||||||
if (!rpc) return null;
|
if (!rpc) return null;
|
||||||
log.debug({ event: Events.WantUpdated, rpc, want });
|
log.debug({ event: Events.WantsUpdated, rpc, want });
|
||||||
return rpc.pushUserWants();
|
return rpc.pushUserWants();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -21,8 +21,8 @@ export default function createNotify(log: Log, pg: Pg, events) {
|
|||||||
log.debug({ channel: OFFERS_CHANNEL, action, id });
|
log.debug({ channel: OFFERS_CHANNEL, action, id });
|
||||||
if (action === 'DELETE') return null;
|
if (action === 'DELETE') return null;
|
||||||
|
|
||||||
const offer = await pg.offerFindId(id);
|
// const offer = await pg.offerFindId(id);
|
||||||
// events.emit(Events.OfferUpdated, offer);
|
// events.emit(Events.OffersUpdated, offer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUsers(payload) {
|
async function handleUsers(payload) {
|
||||||
@@ -37,6 +37,7 @@ export default function createNotify(log: Log, pg: Pg, events) {
|
|||||||
|
|
||||||
async function handleWants(payload) {
|
async function handleWants(payload) {
|
||||||
const { action, id } = payload;
|
const { action, id } = payload;
|
||||||
|
// const want = await pg.offerFindId(id);
|
||||||
log.debug({ channel: WANTS_CHANNEL, action, id });
|
log.debug({ channel: WANTS_CHANNEL, action, id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
async function wantList(user: User) {
|
||||||
return prisma.want.findMany({
|
return prisma.want.findMany({
|
||||||
where: { userId: user.id },
|
where: { userId: user.id },
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export enum RpcMessageType {
|
|||||||
AcpListingsSync = 'AcpListingsSync',
|
AcpListingsSync = 'AcpListingsSync',
|
||||||
|
|
||||||
WantRm = 'WantRm',
|
WantRm = 'WantRm',
|
||||||
|
WantAdd = 'WantAdd',
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorMessage = [RpcMessageType.Error, string];
|
type ErrorMessage = [RpcMessageType.Error, string];
|
||||||
@@ -82,6 +83,10 @@ type WantRmParams = {
|
|||||||
id: UUID,
|
id: UUID,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type WantAddParams = {
|
||||||
|
releaseId: number,
|
||||||
|
};
|
||||||
|
|
||||||
function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType {
|
function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType {
|
||||||
if (RpcMessageType[msgType]) {
|
if (RpcMessageType[msgType]) {
|
||||||
return true;
|
return true;
|
||||||
@@ -124,6 +129,18 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
|
|||||||
return [RpcMessageType.Wants, wants];
|
return [RpcMessageType.Wants, wants];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function wantAdd(params: WantAddParams): Promise<WantsMessage> {
|
||||||
|
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<AcpOffersMessage> {
|
async function acpOfferStatusSet(params: OfferStatusSetParams): Promise<AcpOffersMessage> {
|
||||||
const { id, status } = params;
|
const { id, status } = params;
|
||||||
assert(isAdmin(user), 'not authorised');
|
assert(isAdmin(user), 'not authorised');
|
||||||
@@ -171,6 +188,8 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
|
|||||||
|
|
||||||
case RpcMessageType.WantRm:
|
case RpcMessageType.WantRm:
|
||||||
return wantRm(params);
|
return wantRm(params);
|
||||||
|
case RpcMessageType.WantAdd:
|
||||||
|
return wantAdd(params);
|
||||||
|
|
||||||
case RpcMessageType.AcpOfferStatusSet:
|
case RpcMessageType.AcpOfferStatusSet:
|
||||||
return acpOfferStatusSet(params);
|
return acpOfferStatusSet(params);
|
||||||
|
|||||||
@@ -49,8 +49,12 @@ function OfferPriceBtn(offer) {
|
|||||||
? offer.status
|
? offer.status
|
||||||
: 'Accept';
|
: 'Accept';
|
||||||
|
|
||||||
|
const title = accepted
|
||||||
|
? 'Click to cancel'
|
||||||
|
: 'Click to accept this offer';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button disabled={confirmed} onClick={() => setStatus()} class={classes}>${offer.total} {offer.condition}</button>
|
<button disabled={confirmed} title={title} onClick={() => setStatus()} class={classes}>${offer.total} {offer.condition}</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,15 +33,10 @@ api.interceptors.response.use(onSuccess, onError);
|
|||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
// TODO move to rpc
|
// TODO move to rpc
|
||||||
|
|
||||||
postWantSearch: async function postWantSearch(query) {
|
postWantSearch: async function postWantSearch(query) {
|
||||||
return api.post('/want/search', { query });
|
return api.post('/want/search', { query });
|
||||||
},
|
},
|
||||||
|
|
||||||
postWant: async function postWant(releaseId) {
|
|
||||||
return api.post('/want', { releaseId });
|
|
||||||
},
|
|
||||||
|
|
||||||
postDiscogs: async function postDiscogs(username) {
|
postDiscogs: async function postDiscogs(username) {
|
||||||
return api.post('/want/discogs', { username });
|
return api.post('/want/discogs', { username });
|
||||||
},
|
},
|
||||||
@@ -71,14 +66,6 @@ const actions = {
|
|||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
postOffers: async function postOffers(listingIds, shipping, baseShipping) {
|
|
||||||
return api.post('/offers', {
|
|
||||||
listingIds,
|
|
||||||
shipping,
|
|
||||||
baseShipping,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default actions;
|
export default actions;
|
||||||
@@ -7,25 +7,22 @@ import { State } from './../store';
|
|||||||
|
|
||||||
import http from '../http';
|
import http from '../http';
|
||||||
|
|
||||||
function Result(r, wants) {
|
function Result(r, wants, ws) {
|
||||||
const [added, setAdded] = useState(false)
|
|
||||||
|
|
||||||
async function onClick() {
|
async function onClick() {
|
||||||
const [error, want] = await http.postWant(r.id);
|
ws.sendWantAdd(r.id);
|
||||||
if (error) return false;
|
|
||||||
setAdded(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 classes = `${wanted ? 'added' : ''}`;
|
||||||
const btnText = added
|
const btnText = wanted
|
||||||
? <Check />
|
? <Check />
|
||||||
: '+';
|
: '+';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article key={r.id}>
|
<article key={r.id}>
|
||||||
<button onClick={onClick} disabled={added} class={classes} >{btnText}</button>
|
<button onClick={onClick} disabled={wanted} class={classes} >{btnText}</button>
|
||||||
<div>
|
<div>
|
||||||
<h3>{r.title} <a href={`https://discogs.com${r.uri}`} target="_blank"><ExternalLink /></a></h3>
|
<h3>{r.title} <a href={`https://discogs.com${r.uri}`} target="_blank"><ExternalLink /></a></h3>
|
||||||
<h5 class='alt-text' >{r.year}</h5>
|
<h5 class='alt-text' >{r.year}</h5>
|
||||||
@@ -45,6 +42,7 @@ const User = () => {
|
|||||||
const [infoText, setInfoText] = useState('');
|
const [infoText, setInfoText] = useState('');
|
||||||
|
|
||||||
const wants = useSelector((state: State) => state.wants);
|
const wants = useSelector((state: State) => state.wants);
|
||||||
|
const ws = useSelector((state: State) => state.ws);
|
||||||
|
|
||||||
async function fetchResults() {
|
async function fetchResults() {
|
||||||
const [error, results] = await http.postWantSearch(query);
|
const [error, results] = await http.postWantSearch(query);
|
||||||
@@ -114,7 +112,7 @@ const User = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class='results' ref={resultsRef}>
|
<div class='results' ref={resultsRef}>
|
||||||
{infoEl}
|
{infoEl}
|
||||||
{results.map(r => Result(r, wants))}
|
{results.map(r => Result(r, wants, ws))}
|
||||||
{topEl}
|
{topEl}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ export default function createSocket(dispatch) {
|
|||||||
send(['WantRm', { id }]);
|
send(['WantRm', { id }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendWantAdd(releaseId) {
|
||||||
|
send(['WantAdd', { releaseId }]);
|
||||||
|
}
|
||||||
|
|
||||||
function sendOffers() {
|
function sendOffers() {
|
||||||
send(['Offers', {}]);
|
send(['Offers', {}]);
|
||||||
}
|
}
|
||||||
@@ -157,6 +161,7 @@ export default function createSocket(dispatch) {
|
|||||||
|
|
||||||
sendOfferStatusSet,
|
sendOfferStatusSet,
|
||||||
sendWantRm,
|
sendWantRm,
|
||||||
|
sendWantAdd,
|
||||||
|
|
||||||
sendOffers,
|
sendOffers,
|
||||||
sendWants,
|
sendWants,
|
||||||
|
|||||||
Reference in new issue
Block a user