This commit is contained in:
ntr committed 2022-12-27 15:37:46 +11:00
1 parent b86a4216cf
commit b91f877045
11 files changed
+118 -130

No files matched your search

+1 -4
View File
@@ -1,10 +1,7 @@
# TODO
push offers
listen / notify
on offers
notify admins
add last synced time to want
contact options on landing page
+1 -1
View File
@@ -48,7 +48,7 @@ export default function createDiscogs(log: Log, pg: Pg) {
// @ts-ignore
const additions = results.filter(r => r.status === 'fulfilled').map(r => r.value);
log.info('want list additions', additions);
log.info('want list additions', { additions: additions.length });
return additions;
}
+13 -5
View File
@@ -15,7 +15,8 @@ export enum Events {
ListingsUpdated = 'ListingsUpdated',
Connection = 'Connection',
OffersUpdated = 'OffersUpdated'
OfferUpdated = 'OfferUpdated',
WantUpdated = 'WantUpdated',
}
export default function createEvents(log: Log, pg: Pg, discogs, offers) {
@@ -43,7 +44,7 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers) {
async function updateWantListings(wants: Want[]) {
for (const want of wants) {
const listings = await populateListings(want);
log.info({ listings: listings.length, ...wantDetails(want) }, 'listings added');
log.info({ listings: listings.length, ...wantDetails(want) }, 'listings found');
events.emit(Events.ListingsUpdated);
}
@@ -74,11 +75,18 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers) {
connections.set(id, rpc);
})
events.on(Events.OffersUpdated, async function onOffersUpdated(offer: Offer) {
events.on(Events.OfferUpdated, async function onOfferUpdated(offer: Offer) {
const rpc = connections.get(offer.userId);
if (!rpc) return null;
log.debug({ event: Events.OffersUpdated, rpc, offer });
return rpc.sendUserOffers();
log.debug({ event: Events.OfferUpdated, rpc, offer });
return rpc.pushUserOffers();
});
events.on(Events.WantUpdated, async function onWantUpdated(want: Want) {
const rpc = connections.get(want.userId);
if (!rpc) return null;
log.debug({ event: Events.WantUpdated, rpc, want });
return rpc.pushUserWants();
});
return events;
-20
View File
@@ -1,20 +0,0 @@
import createSubscriber from 'pg-listen'
import type Log from 'bunyan';
import { Events } from './events';
export default function createNotify(log: Log, events) {
const CHANNEL = 'events';
const subscriber = createSubscriber({ connectionString: process.env.DATABASE_URL });
subscriber.notifications.on(CHANNEL, ({ table, action, record }) => {
if (table === 'offers') return events.emit(Events.OffersUpdated, record);
})
async function connect() {
await subscriber.connect();
return subscriber.listenTo(CHANNEL);
}
return { connect };
}
+8 -4
View File
@@ -18,22 +18,26 @@ export default function createNotify(log: Log, pg: Pg, events) {
async function handleOffers(payload) {
const { action, id } = payload;
log.info({ channel: OFFERS_CHANNEL, action, id });
log.debug({ channel: OFFERS_CHANNEL, action, id });
if (action === 'DELETE') return null;
const offer = await pg.offerFindId(id);
// events.emit(Events.OfferUpdated, offer);
}
async function handleUsers(payload) {
const { action, id } = payload;
log.info({ channel: USERS_CHANNEL, action, id });
log.debug({ channel: USERS_CHANNEL, action, id });
}
async function handleListings(payload) {
const { action, id } = payload;
log.info({ channel: LISTINGS_CHANNEL, action, id });
log.debug({ channel: LISTINGS_CHANNEL, action, id });
}
async function handleWants(payload) {
const { action, id } = payload;
log.info({ channel: WANTS_CHANNEL, action, id });
log.debug({ channel: WANTS_CHANNEL, action, id });
}
subscriber.notifications.on(OFFERS_CHANNEL, handleOffers);
+80 -80
View File
@@ -12,6 +12,76 @@ import { Status } from './offers';
import { isAdmin } from './utils';
import { Events } from './events';
export type Rpc = ReturnType<typeof createRpc>;
export enum RpcMessageType {
Ping = 'Ping',
Pong = 'Pong',
// points out user is anon
WhoDis = 'WhoDis',
Bundles = 'Bundles',
Offers = 'Offers',
List = 'List',
Wants = 'Wants',
User = 'User',
Error = 'Error',
OfferSamples = 'OfferSamples',
OfferStatusSet = 'OfferStatusSet',
AcpOfferStatusSet = 'AcpOfferStatusSet',
AcpOffers = 'AcpOffers',
AcpListingsSync = 'AcpListingsSync',
WantRm = 'WantRm',
}
type ErrorMessage = [RpcMessageType.Error, string];
type PongMessage = [RpcMessageType.Pong, []];
type PingMessage = [RpcMessageType.Ping, []];
type WhoDisMessage = [RpcMessageType.WhoDis, []];
type UserMessage = [RpcMessageType.User, User];
type WantsMessage = [RpcMessageType.Wants, Want[]];
type OffersMessage = [RpcMessageType.Offers, Offer[]];
type OfferStatusSetMessage = [RpcMessageType.OfferStatusSet, Offer[]];
type OfferSamplesMessage = [RpcMessageType.OfferSamples, Offer[]];
type AcpOfferStatusSetMessage = [RpcMessageType.AcpOfferStatusSet, Offer[]];
type AcpOffersMessage = [RpcMessageType.AcpOffers, Offer[]];
type AcpListingsSyncMessage = [RpcMessageType.AcpListingsSync, []];
export type RpcMessage =
PingMessage |
PongMessage |
WhoDisMessage |
UserMessage |
WantsMessage |
OffersMessage |
OfferSamplesMessage |
OfferStatusSetMessage |
AcpOfferStatusSetMessage |
AcpOffersMessage |
AcpListingsSyncMessage |
ErrorMessage;
type OfferStatusSetParams = {
id: UUID,
status: Status,
};
type WantRmParams = {
id: UUID,
};
function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType {
if (RpcMessageType[msgType]) {
return true;
@@ -134,11 +204,8 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
const userMsg: UserMessage = [RpcMessageType.User, user];
send(userMsg);
await sendUserOffers();
const wants = await pg.userWants(user);
const wantsMsg: WantsMessage = [RpcMessageType.Wants, wants];
send(wantsMsg);
await pushUserOffers();
await pushUserWants();
if (isAdmin(user)) return adminConnection();
}
@@ -151,10 +218,12 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
))
}
async function sendUserOffers() {
const userOffers = await offers.userOffers(user);
const offersMsg: OffersMessage = [RpcMessageType.Offers, userOffers];
send(offersMsg);
async function pushUserOffers() {
send(await userOffers());
}
async function pushUserWants() {
send(await userWants());
}
return {
@@ -162,76 +231,7 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
send,
onConnection,
sendUserOffers,
pushUserOffers,
pushUserWants,
}
}
export type Rpc = ReturnType<typeof createRpc>;
export enum RpcMessageType {
Ping = 'Ping',
Pong = 'Pong',
// points out user is anon
WhoDis = 'WhoDis',
Bundles = 'Bundles',
Offers = 'Offers',
List = 'List',
Wants = 'Wants',
User = 'User',
Error = 'Error',
OfferSamples = 'OfferSamples',
OfferStatusSet = 'OfferStatusSet',
AcpOfferStatusSet = 'AcpOfferStatusSet',
AcpOffers = 'AcpOffers',
AcpListingsSync = 'AcpListingsSync',
WantRm = 'WantRm',
}
type ErrorMessage = [RpcMessageType.Error, string];
type PongMessage = [RpcMessageType.Pong, []];
type PingMessage = [RpcMessageType.Ping, []];
type WhoDisMessage = [RpcMessageType.WhoDis, []];
type UserMessage = [RpcMessageType.User, User];
type WantsMessage = [RpcMessageType.Wants, Want[]];
type OffersMessage = [RpcMessageType.Offers, Offer[]];
type OfferStatusSetMessage = [RpcMessageType.OfferStatusSet, Offer[]];
type OfferSamplesMessage = [RpcMessageType.OfferSamples, Offer[]];
type AcpOfferStatusSetMessage = [RpcMessageType.AcpOfferStatusSet, Offer[]];
type AcpOffersMessage = [RpcMessageType.AcpOffers, Offer[]];
type AcpListingsSyncMessage = [RpcMessageType.AcpListingsSync, []];
export type RpcMessage =
PingMessage |
PongMessage |
WhoDisMessage |
UserMessage |
WantsMessage |
OffersMessage |
OfferSamplesMessage |
OfferStatusSetMessage |
AcpOfferStatusSetMessage |
AcpOffersMessage |
AcpListingsSyncMessage |
ErrorMessage;
type OfferStatusSetParams = {
id: UUID,
status: Status,
};
type WantRmParams = {
id: UUID,
};
+6 -5
View File
@@ -1,12 +1,10 @@
import { h } from 'preact';
import { useState, useCallback } from 'preact/hooks';
import { useState } from 'preact/hooks';
import { ExternalLink } from 'react-feather';
import { useSelector } from 'react-redux'
import { State } from './../store'
import http from './../http';
function Want(want) {
const r = want.data;
@@ -14,10 +12,13 @@ function Want(want) {
const [confirm, setConfirm] = useState(false);
function onClick() {
if (confirm) return ws.sendWantRm(want.id);
if (confirm) {
setConfirm(false);
return ws.sendWantRm(want.id);
}
setTimeout(() => setConfirm(false), 2500);
return setConfirm(true);
};
}
const text = confirm
? 'confirm'
-1
View File
@@ -10,7 +10,6 @@ import Want from './../components/want';
const List = () => {
const wants = useSelector((state: State) => state.wants);
const user = useSelector((state: State) => state.user);
if (!wants) {
return <div> loading... </div>;
+5 -8
View File
@@ -378,19 +378,16 @@ a {
}
}
nav {
a {
font-size: var(--small-font-size);
margin-right: var(--spacing);
}
}
header {
display: grid;
grid-template-columns: 1fr 1fr min-content;
padding-bottom: var(--spacing);
a {
font-size: var(--small-font-size);
margin-right: var(--spacing);
}
a {
white-space: nowrap;
}
+1 -1
View File
@@ -3,7 +3,7 @@ import toast from 'izitoast';
export function info(title, message) {
toast.info({
title,
message,
message: message || '',
position: 'bottomCenter',
color: '#1f2128',
titleColor: '#F4F1D0',
+3 -1
View File
@@ -1,5 +1,6 @@
import { wantsState, wsState, userState, offersState, acpOffersState } from './store'
import debounce from 'lodash/debounce';
import { wantsState, userState, offersState, acpOffersState } from './store'
import * as toast from './toast';
const SOCKET_URL = `${window.location.protocol === 'https:' ? 'wss://' : 'ws://'}${window.location.host}/api/ws`;
@@ -116,6 +117,7 @@ export default function createSocket(dispatch) {
// Connection opened
function onOpen() {
console.log('websocket connected', SOCKET_URL)
toast.info('connected', 'connected to server');
sendPing();
}