From 18d5f263268431590874e695b197007a7220ba62 Mon Sep 17 00:00:00 2001 From: ntr Date: Sat, 26 Nov 2022 11:06:57 +1100 Subject: [PATCH 1/9] mobile styles --- client/src/app.tsx | 2 +- client/src/routes/login.tsx | 5 ++- client/src/routes/search.tsx | 1 - client/src/style/index.less | 61 +++++++++++++++++++++++++++++++++--- client/src/template.html | 14 +++++++++ 5 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 client/src/template.html diff --git a/client/src/app.tsx b/client/src/app.tsx index 7dbc6c5..a81e3d5 100644 --- a/client/src/app.tsx +++ b/client/src/app.tsx @@ -19,7 +19,7 @@ import Info from './routes/info'; if (window.location.host === 'matescrates.au') LogRocket.init('yh0dy3/mcrates'); const Main = () => { - const dispatch = useDispatch() + const dispatch = useDispatch(); // i have no idea why this is needed, // but it makes sure the user changes actually rerender diff --git a/client/src/routes/login.tsx b/client/src/routes/login.tsx index f712487..0dca412 100644 --- a/client/src/routes/login.tsx +++ b/client/src/routes/login.tsx @@ -97,9 +97,8 @@ function LoginPage({ mode }) { return (
-
-
- + +

Mates Crates is a record importing service.

We bundle orders together from thousands of online stores to reduce the cost of shipping vinyl.

Based in Preston.

diff --git a/client/src/routes/search.tsx b/client/src/routes/search.tsx index 0b6faaa..b50de6a 100644 --- a/client/src/routes/search.tsx +++ b/client/src/routes/search.tsx @@ -104,7 +104,6 @@ const User = () => {

Data provided by Discogs.

-
{infoEl} {results.map(Result)} diff --git a/client/src/style/index.less b/client/src/style/index.less index 4c4cccb..a216423 100644 --- a/client/src/style/index.less +++ b/client/src/style/index.less @@ -212,19 +212,27 @@ button, [type=submit] { .landing { display: grid; - grid-template-columns: 1fr 0.5fr 2fr; + + grid-template-columns: 1fr 2fr; + grid-gap: calc(var(--spacing-double) * 2); + grid-template-areas: + "login swirl" + "login words"; .login { + grid-area: login; button { width: 100%; } } .swirl { + grid-area: swirl; height: 15rem; } - .text { + .words { + grid-area: words; text-align: center; } } @@ -274,13 +282,14 @@ article { .search { display: grid; - grid-template-columns: 1fr 0.5fr 2fr; + grid-template-columns: 1fr 2fr; + grid-gap: calc(var(--spacing-double) * 2); article { display: grid; padding: var(--spacing); margin-bottom: var(--spacing); - grid-template-columns: 1fr 4fr 1fr; + grid-template-columns: 1fr 4fr min-content; } button { @@ -324,6 +333,10 @@ article { } padding: var(--spacing); + + img { + justify-self: end; + } } } @@ -482,4 +495,42 @@ header { background-position: center; margin-bottom: var(--spacing); -} \ No newline at end of file +} + +@media (max-width:900px) { + .landing { + grid-template-columns: 1fr; + grid-template-areas: + "swirl" + "login" + "words"; + } + + .list { + grid-template-columns: 1fr; + } + + .search { + grid-template-columns: 1fr; + } + + .offers { + grid-template-columns: 1fr; + } + + .info { + grid-template-columns: 1fr; + // is necessary for some reason + grid-template-areas: + "swirl" + "contact" + "words"; + } +} + +@media (max-width:1200px) { + img { + width: 5rem; + } +} + diff --git a/client/src/template.html b/client/src/template.html new file mode 100644 index 0000000..ce1e78f --- /dev/null +++ b/client/src/template.html @@ -0,0 +1,14 @@ + + + + + matescrates.au - record import service + + + + <% preact.headEnd %> + + + <% preact.bodyEnd %> + + \ No newline at end of file From f726c960cdb2afaf59ee0f398cb11bb46c7b7572 Mon Sep 17 00:00:00 2001 From: ntr Date: Sat, 26 Nov 2022 13:11:29 +1100 Subject: [PATCH 2/9] style changes and offer loading routing --- client/src/routes/offers.tsx | 2 ++ client/src/routes/search.tsx | 2 +- client/src/store.tsx | 2 +- client/src/style/index.less | 25 ++++++++++++++++--------- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/client/src/routes/offers.tsx b/client/src/routes/offers.tsx index d1ac1ae..98fd108 100644 --- a/client/src/routes/offers.tsx +++ b/client/src/routes/offers.tsx @@ -16,6 +16,8 @@ function Home() { route('/search'); } + if (!offers) return
loading...
; + return (
{offers.map(OfferGroup)} diff --git a/client/src/routes/search.tsx b/client/src/routes/search.tsx index b50de6a..2394b89 100644 --- a/client/src/routes/search.tsx +++ b/client/src/routes/search.tsx @@ -104,7 +104,7 @@ const User = () => {

Data provided by Discogs.

-
+
{infoEl} {results.map(Result)} {topEl} diff --git a/client/src/store.tsx b/client/src/store.tsx index a25dd5d..053f377 100644 --- a/client/src/store.tsx +++ b/client/src/store.tsx @@ -11,7 +11,7 @@ export const userState = createSlice({ export const offersState = createSlice({ name: 'offers', - initialState: [], + initialState: null, reducers: { set: (state, action) => action.payload }, diff --git a/client/src/style/index.less b/client/src/style/index.less index a216423..33e679b 100644 --- a/client/src/style/index.less +++ b/client/src/style/index.less @@ -201,7 +201,7 @@ body { } main { - padding: 0 5% 0 5%; + padding: 0 var(--spacing-double) 0 var(--spacing-double); } button, [type=submit] { @@ -283,13 +283,17 @@ article { .search { display: grid; grid-template-columns: 1fr 2fr; - grid-gap: calc(var(--spacing-double) * 2); - article { + .results { display: grid; - padding: var(--spacing); - margin-bottom: var(--spacing); - grid-template-columns: 1fr 4fr min-content; + padding-left: var(--spacing); + grid-gap: var(--spacing); + + article { + display: grid; + padding: var(--spacing); + grid-template-columns: 1fr 4fr min-content; + } } button { @@ -309,8 +313,7 @@ article { grid-template-columns: 1fr 1fr 1fr; - grid-row-gap: var(--spacing); - grid-column-gap: var(--spacing); + grid-gap: var(--spacing); article { display: grid; @@ -425,6 +428,7 @@ header { .offers { display: grid; grid-template-columns: 1fr 1fr; + grid-gap: var(--spacing); .offer { display: grid; @@ -435,7 +439,6 @@ header { "lines lines"; padding: var(--spacing); - margin: var(--spacing-half); background: var(--background-1); .details { @@ -526,6 +529,10 @@ header { "contact" "words"; } + + main { + padding: 0 var(--spacing) 0 var(--spacing); + } } @media (max-width:1200px) { From e185d04039d7f52327acec1963a6dd9133ce0e91 Mon Sep 17 00:00:00 2001 From: ntr Date: Sun, 27 Nov 2022 14:48:15 +1100 Subject: [PATCH 3/9] fix login form --- TODO.md | 2 ++ api/src/rpc.ts | 4 ++++ client/src/routes/login.tsx | 35 ++++++++++++++++++----------------- client/src/ws.ts | 2 -- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/TODO.md b/TODO.md index 40006d3..f78ec8c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,7 @@ # TODO +contact options on landing page + listing ids overflowing skip unofficial release diff --git a/api/src/rpc.ts b/api/src/rpc.ts index 5e62291..b1bb7c1 100644 --- a/api/src/rpc.ts +++ b/api/src/rpc.ts @@ -113,6 +113,10 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off const userMsg: UserMessage = [RpcMessageType.User, user]; send(userMsg); + const userOffers = await offers.userOffers(user); + const offersMsg: OffersMessage = [RpcMessageType.Offers, userOffers]; + send(offersMsg); + const wants = await pg.userWants(user); const wantsMsg: WantsMessage = [RpcMessageType.Wants, wants]; send(wantsMsg); diff --git a/client/src/routes/login.tsx b/client/src/routes/login.tsx index 0dca412..f46cc93 100644 --- a/client/src/routes/login.tsx +++ b/client/src/routes/login.tsx @@ -12,12 +12,9 @@ const Login = ({ mode }) => { const user = useSelector((state: State) => state.user); const ws = useSelector((state: State) => state.ws); - const [email, setEmail] = useState('') - const [password, setPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); const [error, setError] = useState(''); - async function postLogin() { + async function postLogin(email, password) { const [error, user] = await http.postLogin(email, password); if (error) return setError(error); route('/'); @@ -26,7 +23,9 @@ const Login = ({ mode }) => { ws.connect(); } - async function postRegister() { + async function postRegister(email, password, confirm) { + if (password !== confirm) return setError('passwords do not match'); + const [error, user] = await http.postRegister(email, password); if (error) return setError(error); route('/search'); @@ -35,8 +34,10 @@ const Login = ({ mode }) => { function handleSubmit(e) { e.preventDefault(); - if (mode === 'login') return postLogin() - if (mode === 'register') return postRegister(); + + console.log(e.target.email, e.target.password, e.target.confirm); + if (mode === 'login') return postLogin(e.target.email.value, e.target.password.value); + if (mode === 'register') return postRegister(e.target.email.value, e.target.password.value, e.target.confirm.value); } const emailRef = useRef() @@ -52,21 +53,21 @@ const Login = ({ mode }) => { if (mode === 'register') return ( @@ -76,17 +77,17 @@ const Login = ({ mode }) => { return ( @@ -101,7 +102,7 @@ function LoginPage({ mode }) {

Mates Crates is a record importing service.

We bundle orders together from thousands of online stores to reduce the cost of shipping vinyl.

-

Based in Preston.

+

Records can then be picked up from Preston.

) diff --git a/client/src/ws.ts b/client/src/ws.ts index 69fbe68..acf12e0 100644 --- a/client/src/ws.ts +++ b/client/src/ws.ts @@ -112,8 +112,6 @@ export default function createSocket(dispatch) { console.log('websocket connected', SOCKET_URL) sendPing(); sendAcpOffers(); - sendOffers(); - sendWants(); } function onError(event) { From 5a19bf62fdaa43412f8bf8402f66ea316acc82d6 Mon Sep 17 00:00:00 2001 From: ntr Date: Sun, 27 Nov 2022 15:08:59 +1100 Subject: [PATCH 4/9] adminconnection --- api/src/rpc.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/api/src/rpc.ts b/api/src/rpc.ts index b1bb7c1..b3414f2 100644 --- a/api/src/rpc.ts +++ b/api/src/rpc.ts @@ -109,7 +109,18 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off throw new Error(`Invalid parameters for message type ${msgType}: ${JSON.stringify(message)}`); } + async function anonConnection() { + + } + + async function adminConnection() { + // don't need it, requested on route + // send(await acpOffers()); + } + async function onConnection() { + if (!user) return anonConnection(); + const userMsg: UserMessage = [RpcMessageType.User, user]; send(userMsg); @@ -120,6 +131,8 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off const wants = await pg.userWants(user); const wantsMsg: WantsMessage = [RpcMessageType.Wants, wants]; send(wantsMsg); + + if (isAdmin(user)) return adminConnection(); } function send(msg: RpcMessage) { From 9e854f5d812ff15e053480882b374904b34300cf Mon Sep 17 00:00:00 2001 From: ntr Date: Mon, 28 Nov 2022 09:52:45 +1100 Subject: [PATCH 5/9] properly reconnect on register --- TODO.md | 1 - client/src/routes/login.tsx | 2 ++ client/src/ws.ts | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index f78ec8c..350864d 100644 --- a/TODO.md +++ b/TODO.md @@ -11,7 +11,6 @@ status stepper queue up wantlist adds to gracefully handle rate limiting push offers -cover off admin routes and commands listen / notify on offers diff --git a/client/src/routes/login.tsx b/client/src/routes/login.tsx index f46cc93..03990e0 100644 --- a/client/src/routes/login.tsx +++ b/client/src/routes/login.tsx @@ -30,6 +30,8 @@ const Login = ({ mode }) => { if (error) return setError(error); route('/search'); dispatch(userState.actions.set(user)); + // reconnect ws to get logged in methods + ws.connect(); } function handleSubmit(e) { diff --git a/client/src/ws.ts b/client/src/ws.ts index acf12e0..da8d744 100644 --- a/client/src/ws.ts +++ b/client/src/ws.ts @@ -111,7 +111,6 @@ export default function createSocket(dispatch) { function onOpen() { console.log('websocket connected', SOCKET_URL) sendPing(); - sendAcpOffers(); } function onError(event) { From 5115dc5f8e796e90046ebc79958feaebf098352f Mon Sep 17 00:00:00 2001 From: ntr Date: Mon, 28 Nov 2022 09:58:34 +1100 Subject: [PATCH 6/9] create account btn --- client/src/routes/login.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/routes/login.tsx b/client/src/routes/login.tsx index 03990e0..87742f7 100644 --- a/client/src/routes/login.tsx +++ b/client/src/routes/login.tsx @@ -14,6 +14,10 @@ const Login = ({ mode }) => { const [error, setError] = useState(''); + function gotoRegister() { + return route('/register'); + } + async function postLogin(email, password) { const [error, user] = await http.postLogin(email, password); if (error) return setError(error); @@ -92,6 +96,7 @@ const Login = ({ mode }) => {
+
); }; From e74ab36313a49d7dc96459a34184c342c4ddc5ae Mon Sep 17 00:00:00 2001 From: ntr Date: Mon, 28 Nov 2022 10:10:37 +1100 Subject: [PATCH 7/9] fix offers comparison --- api/index.ts | 2 +- api/src/discogs.ts | 2 +- api/src/offers.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/index.ts b/api/index.ts index 4d12a0c..0c70153 100644 --- a/api/index.ts +++ b/api/index.ts @@ -28,7 +28,7 @@ async function start() { events, } = createServices(log); - // events.emit(Events.StartupSync); + events.emit(Events.StartupSync); // events.emit(Events.ListingsUpdated); api.listen(); diff --git a/api/src/discogs.ts b/api/src/discogs.ts index d220281..efff64c 100644 --- a/api/src/discogs.ts +++ b/api/src/discogs.ts @@ -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(additions); + log.info('want list additions', additions); return additions; } diff --git a/api/src/offers.ts b/api/src/offers.ts index 605acdc..f73e143 100644 --- a/api/src/offers.ts +++ b/api/src/offers.ts @@ -51,7 +51,7 @@ export default function createOffers(log: Log, pg: Pg) { // TODO probably need to take one of each condition of record from a store // eg. they have a VG+ and a M we should make offers for each - log.info(sorted); + log.trace('generated offers', sorted); const inserts = await pg.offersUpdate(sorted); From 631942bb41ec8a7aa10c35763405f6b91612e97b Mon Sep 17 00:00:00 2001 From: ntr Date: Mon, 28 Nov 2022 10:31:37 +1100 Subject: [PATCH 8/9] clean up wants flow and http usage --- TODO.md | 2 ++ api/index.ts | 2 +- api/src/api.ts | 15 --------------- api/src/events.ts | 1 + client/package-lock.json | 11 +++++++++++ client/package.json | 1 + client/src/http.ts | 19 ++++--------------- client/src/routes/search.tsx | 13 ++++++++++--- 8 files changed, 30 insertions(+), 34 deletions(-) diff --git a/TODO.md b/TODO.md index 350864d..abbb330 100644 --- a/TODO.md +++ b/TODO.md @@ -2,6 +2,8 @@ contact options on landing page +move want add to rpc + listing ids overflowing skip unofficial release diff --git a/api/index.ts b/api/index.ts index 0c70153..4d12a0c 100644 --- a/api/index.ts +++ b/api/index.ts @@ -28,7 +28,7 @@ async function start() { events, } = createServices(log); - events.emit(Events.StartupSync); + // events.emit(Events.StartupSync); // events.emit(Events.ListingsUpdated); api.listen(); diff --git a/api/src/api.ts b/api/src/api.ts index af4de29..6da1d9a 100644 --- a/api/src/api.ts +++ b/api/src/api.ts @@ -59,13 +59,6 @@ export default function createApi(log: Log, pg: Pg, discogs, offers, events) { ctx.status = 200; }); - router.get('/user/offers', async function userOffersGet(ctx, next) { - ctx.assert(ctx.isAuthenticated(), 401, 'log in required'); - const user = ctx.state.user; - const offers = await pg.userOffers(user); - ctx.response.body = offers; - }); - router.post('/want', async function wantPost(ctx, next) { ctx.assert(ctx.isAuthenticated(), 401, 'log in required'); const { releaseId } = ctx.request.body; @@ -85,14 +78,6 @@ export default function createApi(log: Log, pg: Pg, discogs, offers, events) { ctx.response.body = wants; }); - router.get('/want/all', async function wantGet(ctx, next) { - ctx.assert(ctx.isAuthenticated(), 401, 'log in required'); - const user = ctx.state.user; - const wants = await pg.wantList(user); - - ctx.response.body = wants; - }); - router.post('/want/search', async function wantSearch(ctx, next) { const { query } = ctx.request.body; const results = await discogs.search(query); diff --git a/api/src/events.ts b/api/src/events.ts index 0618e24..bdc465e 100644 --- a/api/src/events.ts +++ b/api/src/events.ts @@ -51,6 +51,7 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers) { log.info({ event: Events.WantsAdded, wants: wants.map(w => wantDetails(w)) }, 'starting listings population'); await updateWantListings(wants); log.info({ event: Events.WantsAdded, wants: wants.map(w => wantDetails(w)) }, 'finished populating listings'); + events.emit(Events.ListingsUpdated); }); events.on(Events.StartupSync, async function startupSync() { diff --git a/client/package-lock.json b/client/package-lock.json index dc36a28..fb24194 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@reduxjs/toolkit": "^1.8.5", + "izitoast": "^1.4.0", "lodash": "^4.17.21", "logrocket": "^3.0.1", "preact": "^10.10.0", @@ -10844,6 +10845,11 @@ "node": ">=8" } }, + "node_modules/izitoast": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/izitoast/-/izitoast-1.4.0.tgz", + "integrity": "sha512-Oc1X2wiQtPp39i5VpIjf3GJf5sfCtHKXZ5szx7RareyEeFLUlcEW0FSfBni28+Ul6KNKZRKzhVuWzSP4Xngh0w==" + }, "node_modules/jake": { "version": "10.8.5", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", @@ -29566,6 +29572,11 @@ "istanbul-lib-report": "^3.0.0" } }, + "izitoast": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/izitoast/-/izitoast-1.4.0.tgz", + "integrity": "sha512-Oc1X2wiQtPp39i5VpIjf3GJf5sfCtHKXZ5szx7RareyEeFLUlcEW0FSfBni28+Ul6KNKZRKzhVuWzSP4Xngh0w==" + }, "jake": { "version": "10.8.5", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", diff --git a/client/package.json b/client/package.json index a44f292..4b24086 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@reduxjs/toolkit": "^1.8.5", + "izitoast": "^1.4.0", "lodash": "^4.17.21", "logrocket": "^3.0.1", "preact": "^10.10.0", diff --git a/client/src/http.ts b/client/src/http.ts index 29a92e2..a1e9605 100644 --- a/client/src/http.ts +++ b/client/src/http.ts @@ -32,21 +32,7 @@ function onError(error) { api.interceptors.response.use(onSuccess, onError); const actions = { - getBundles: async function getBundles() { - return api.get('/acp/bundles'); - }, - - getOffers: async function getOffers() { - return api.get('/user/offers'); - }, - - getOffersAcp: async function getOffersAcp() { - return api.get('/acp/offers'); - }, - - getWants: async function getWants() { - return api.get('/want/all'); - }, + // TODO move to rpc postWantSearch: async function postWantSearch(query) { return api.post('/want/search', { query }); @@ -60,6 +46,9 @@ const actions = { return api.post('/want/discogs', { username }); }, + // -------------------------------- + // require as http to set cookies + getLogin: async function getLogin() { return api.get('/auth/login') }, diff --git a/client/src/routes/search.tsx b/client/src/routes/search.tsx index 2394b89..46ec12a 100644 --- a/client/src/routes/search.tsx +++ b/client/src/routes/search.tsx @@ -1,10 +1,13 @@ import { h } from 'preact'; import { useState, useRef, useEffect } from 'preact/hooks'; import { ExternalLink, Check } from 'react-feather'; +import { useSelector } from 'react-redux' + +import { State } from './../store'; import http from '../http'; -function Result(r) { +function Result(r, wants) { const [added, setAdded] = useState(false) async function onClick() { @@ -13,7 +16,9 @@ function Result(r) { setAdded(true); } - const classes = `${added ? 'added' : ''}`; + const wanted = wants.some(w => w.release === r.id); + + const classes = `${added || wanted ? 'added' : ''}`; const btnText = added ? : '+'; @@ -39,6 +44,8 @@ const User = () => { const [results, setResults] = useState([]); const [infoText, setInfoText] = useState(''); + const wants = useSelector((state: State) => state.wants); + async function fetchResults() { const [error, results] = await http.postWantSearch(query); @@ -106,7 +113,7 @@ const User = () => {
{infoEl} - {results.map(Result)} + {results.map(r => Result(r, wants))} {topEl}
From dfc783c9360000e64df7f3d203cc8a4f11f4fe8e Mon Sep 17 00:00:00 2001 From: ntr Date: Mon, 28 Nov 2022 10:46:19 +1100 Subject: [PATCH 9/9] acp listings sync --- api/index.ts | 2 +- api/src/api.ts | 2 +- api/src/events.ts | 6 +++--- api/src/rpc.ts | 16 +++++++++++++--- client/src/routes/acp.tsx | 14 +++++++++----- client/src/store.tsx | 1 + client/src/ws.ts | 5 +++++ 7 files changed, 33 insertions(+), 13 deletions(-) diff --git a/api/index.ts b/api/index.ts index 4d12a0c..f8a6823 100644 --- a/api/index.ts +++ b/api/index.ts @@ -28,7 +28,7 @@ async function start() { events, } = createServices(log); - // events.emit(Events.StartupSync); + // events.emit(Events.Sync); // events.emit(Events.ListingsUpdated); api.listen(); diff --git a/api/src/api.ts b/api/src/api.ts index 6da1d9a..ae8e163 100644 --- a/api/src/api.ts +++ b/api/src/api.ts @@ -101,7 +101,7 @@ export default function createApi(log: Log, pg: Pg, discogs, offers, events) { // log.child... const ws = ctx.websocket; - const rpc = createRpc(log, pg, ctx.state.user, ws, discogs, offers); + const rpc = createRpc(log, pg, ctx.state.user, ws, discogs, offers, events); if (ctx.state.user) events.emit(Events.Connection, { ws, id: ctx.state.user.id }); ws.on('message', async function incoming(message) { diff --git a/api/src/events.ts b/api/src/events.ts index bdc465e..ba6ab62 100644 --- a/api/src/events.ts +++ b/api/src/events.ts @@ -10,7 +10,7 @@ import routerAuth from './routers/router.auth'; export enum Events { WantsAdded = 'WantsAdded', - StartupSync = 'StartupSync', + Sync = 'Sync', ListingsUpdated = 'ListingsUpdated', Connection = 'Connection', } @@ -54,9 +54,9 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers) { events.emit(Events.ListingsUpdated); }); - events.on(Events.StartupSync, async function startupSync() { + events.on(Events.Sync, async function sync() { const wants = await pg.wantsToSync(); - log.info({ event: Events.StartupSync, wants: wants.length }); + log.info({ event: Events.Sync, wants: wants.length }); await updateWantListings(wants); events.emit(Events.ListingsUpdated); }); diff --git a/api/src/rpc.ts b/api/src/rpc.ts index b3414f2..29da67e 100644 --- a/api/src/rpc.ts +++ b/api/src/rpc.ts @@ -1,5 +1,3 @@ -import { EventEmitter } from 'node:events'; -import { setTimeout } from 'timers/promises'; import assert from 'assert/strict'; import type Log from 'bunyan'; @@ -9,6 +7,7 @@ import type { Want, Offer, User } from '@prisma/client'; import { Pg } from './pg'; import { Status } from './offers'; import { isAdmin } from './utils'; +import { Events } from './events'; function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType { if (RpcMessageType[msgType]) { @@ -18,7 +17,7 @@ function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType { } // rpc is created per connection from the api -export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, offers) { +export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, offers, events) { async function ping(): Promise { return [RpcMessageType.Pong, []]; } @@ -68,6 +67,12 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off return [RpcMessageType.AcpOffers, await offers.acpOffers()]; } + async function acpListingsSync(): Promise { + assert(isAdmin(user), 'not authorised'); + events.emit(Events.Sync); + return [RpcMessageType.AcpListingsSync, []]; + } + async function receive(message: any): Promise { const [msgType, params] = message; @@ -98,6 +103,8 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off return acpOfferStatusSet(params); case RpcMessageType.AcpOffers: return acpOffers(); + case RpcMessageType.AcpListingsSync: + return acpListingsSync(); default: throw new Error(`Message type ${msgType} is known but unhandled`); @@ -169,6 +176,7 @@ export enum RpcMessageType { AcpOfferStatusSet = 'AcpOfferStatusSet', AcpOffers = 'AcpOffers', + AcpListingsSync = 'AcpListingsSync', WantRm = 'WantRm', } @@ -184,6 +192,7 @@ type OfferStatusSetMessage = [RpcMessageType.OfferStatusSet, Offer[]]; type AcpOfferStatusSetMessage = [RpcMessageType.AcpOfferStatusSet, Offer[]]; type AcpOffersMessage = [RpcMessageType.AcpOffers, Offer[]]; +type AcpListingsSyncMessage = [RpcMessageType.AcpListingsSync, []]; export type RpcMessage = PingMessage | @@ -199,6 +208,7 @@ export type RpcMessage = AcpOfferStatusSetMessage | AcpOffersMessage | + AcpListingsSyncMessage | ErrorMessage; diff --git a/client/src/routes/acp.tsx b/client/src/routes/acp.tsx index dbfc975..f22fe62 100644 --- a/client/src/routes/acp.tsx +++ b/client/src/routes/acp.tsx @@ -70,9 +70,9 @@ function Home() { const offers = useSelector((state: State) => state.acpOffers); const ws = useSelector((state: State) => state.ws); - // useEffect(() => { - // ws.sendAcpOffers(); - // }, [offers, ws]); + function sync() { + ws.sendAcpListingsSync(); + } if (!offers.length) { return ( @@ -83,8 +83,12 @@ function Home() { } return ( -
- {offers.map(SellerGroup)} +
+ +
+
+ {offers.map(SellerGroup)} +
); } diff --git a/client/src/store.tsx b/client/src/store.tsx index 053f377..b2cb56e 100644 --- a/client/src/store.tsx +++ b/client/src/store.tsx @@ -52,6 +52,7 @@ const reducer = combineReducers({ const store = createStore( reducer, + // @ts-ignore -- devtools window exists on dev window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); diff --git a/client/src/ws.ts b/client/src/ws.ts index da8d744..2afbe62 100644 --- a/client/src/ws.ts +++ b/client/src/ws.ts @@ -50,6 +50,10 @@ export default function createSocket(dispatch) { send(['AcpOfferStatusSet', { id, status }]); } + function sendAcpListingsSync() { + send(['AcpListingsSync', {}]); + } + let pongTimeout; function onPong() { pongTimeout = setTimeout(sendPing, 5000); @@ -154,5 +158,6 @@ export default function createSocket(dispatch) { sendAcpOffers, sendAcpOfferStatusSet, + sendAcpListingsSync, }; }