diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index 5645808..a1b913e 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -27,8 +27,6 @@ model User { id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid email String @unique password String - discogs String? @unique - data Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/api/src/api.ts b/api/src/api.ts index aa40dd6..537ff90 100644 --- a/api/src/api.ts +++ b/api/src/api.ts @@ -76,23 +76,6 @@ export default function createApi(log: Log, pg: Pg, discogs, events) { }); } - - async function populateUserListings(user: User) { - const wants = await discogs.wantlist(user); - - // const latest = await discogs.wantlistLatest(user); - // if (wants.length !== latest.length) - - log.debug({ username: user.discogs, wantCount: wants.length }); - - return Bluebird.mapSeries(wants, async want => { - const listings = await discogs.listings(want); - const populated = listings.filter(l => l); - log.debug({ id: want.id, release: want.release, populated: populated.length, total: listings.length }); - return populated; - }); - } - function listen() { return new Promise((resolve, reject) => { app.on('error', err => { @@ -125,15 +108,12 @@ export default function createApi(log: Log, pg: Pg, discogs, events) { ctx.response.body = bundles; }) - router.get('/user/:id/offers', async function userOffersGet(ctx, next) { - const { id } = ctx.request.params; - const user = await pg.userFindId(id); - - ctx.assert(user, 404, `user ${id} not found`); - + router.get('/user/offers', async function userOffersGet(ctx, next) { + ctx.assert(ctx.isAuthenticated(), 401, 'log in required'); + const user = ctx.state.user; const bundles = (await getBundles()) .map(b => { - b.items = b.items.filter(i => i.user.id === user.id) + b.items = b.items.filter(i => i.user.id === user.id); return b; }) .filter(b => b.items.length); @@ -141,13 +121,6 @@ export default function createApi(log: Log, pg: Pg, discogs, events) { ctx.response.body = bundles; }); - router.post('/user/populate', async function usersPopulate(ctx, next) { - const { userId } = ctx.request.body; - const user = await pg.userFindId(userId); - const listings = await populateUserListings(user); - ctx.response.body = listings; - }); - router.post('/want', async function wantPost(ctx, next) { ctx.assert(ctx.isAuthenticated(), 401, 'log in required'); const { releaseId } = ctx.request.body; diff --git a/api/src/discogs.ts b/api/src/discogs.ts index c5c12bd..2cbdf94 100644 --- a/api/src/discogs.ts +++ b/api/src/discogs.ts @@ -10,6 +10,10 @@ import type { PrismaClient, Want, User, Listing } from '@prisma/client'; import { logAxiosError } from './utils'; import { Pg } from './pg'; +// margh +// vanderbabe +// jmcdermid1 + export default function discogs(log: Log, pg: Pg) { const TOKEN = 'Discogs token=wRTXuYZGOPsIQgKRGKCAkUkidGtvpSfQgPWkYIpD'; const apiBase = 'https://api.discogs.com' @@ -35,6 +39,8 @@ export default function discogs(log: Log, pg: Pg) { const { data: { wants, pagination }} = await api.get(`${apiBase}/users/${username}/wants`); const releases = await Promise.all(wants.map(want => release(want.id))); + // TODO filter releases to be vinyl format only + // convert want object to actual release and ignore duplicate errors const results = await Promise.allSettled(releases.map(release => pg.wantCreate(release, user))); // @ts-ignore @@ -74,6 +80,9 @@ export default function discogs(log: Log, pg: Pg) { const unlisted = await pg.listingRmUnlisted(want, ids); if (unlisted) log.info({ wantId: want.id, unlisted: unlisted }, 'missing listings removed'); + + // preferentially take listings from sellers if we hit req limits + return Bluebird.mapSeries(ids, async id => listing(id, want)); } diff --git a/api/src/pg.ts b/api/src/pg.ts index 5b01037..bc21d7b 100644 --- a/api/src/pg.ts +++ b/api/src/pg.ts @@ -4,10 +4,8 @@ import type { PrismaClient, Want, User, Listing, Prisma } from '@prisma/client'; import * as Bluebird from 'bluebird'; export interface UserCreateParams { - discogs: DiscogsName | null, email: string, password: string, - data: any, } export interface Pg { @@ -44,9 +42,7 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg { const password = await bcrypt.hash(params.password, hashRounds); const data = { - discogs: params.discogs, email: params.email, - data: params.data, password, }; @@ -55,6 +51,21 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg { }); } + 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 wantList(user: User) { return prisma.want.findMany({ where: { userId: user.id }, diff --git a/api/src/routers/router.auth.ts b/api/src/routers/router.auth.ts index c3e8f21..d7c721c 100644 --- a/api/src/routers/router.auth.ts +++ b/api/src/routers/router.auth.ts @@ -44,7 +44,6 @@ export default function authRouter(log, pg: Pg) { ctx.status = 200; ctx.body = { email: user.email, - discogs: user.discogs, }; })(ctx, next); }); @@ -57,10 +56,18 @@ export default function authRouter(log, pg: Pg) { ctx.body = { id: user.id, email: user.email, - discogs: user.discogs, }; }); + authRouter.post('/logout', async (ctx, next) => { + await passport.authenticate('local', (err, user, info, status) => { + ctx.assert(user, 401, 'incorrect login'); + ctx.logout(); + ctx.status = 200; + ctx.body = { success: true }; + })(ctx, next); + }); + authRouter.post('/register', async (ctx, next) => { const { discogs, email, password } = ctx.request.body; // fetch discogs data if they have it @@ -71,7 +78,6 @@ export default function authRouter(log, pg: Pg) { ctx.body = { id: user.id, email: user.email, - discogs: user.discogs, }; }); diff --git a/client/src/actions.ts b/client/src/actions.ts index 9db8dd5..b465cb9 100644 --- a/client/src/actions.ts +++ b/client/src/actions.ts @@ -36,12 +36,8 @@ const actions = { return api.get('/bundles'); }, - getUserOffers: async function getUserOffers(state, userId) { - return api.get(`/user/${userId}/offers`); - }, - - getOffers: async function getOffers(userId) { - return api.get(`/user/${userId}/offers`); + getOffers: async function getOffers() { + return api.get('/user/offers'); }, getWants: async function getWants() { diff --git a/client/src/app.tsx b/client/src/app.tsx index fd46dca..ba9a562 100644 --- a/client/src/app.tsx +++ b/client/src/app.tsx @@ -7,10 +7,10 @@ import actions from './actions'; import Header from './components/header'; import Home from './routes/home'; +import Bundles from './routes/bundles'; import Login from './routes/login'; import Search from './routes/search'; import List from './routes/list'; -import Offers from './routes/offers'; function App() { const [user, setUser] = useState(null); @@ -36,10 +36,10 @@ function App() {
+ - ); diff --git a/client/src/components/header.tsx b/client/src/components/header.tsx index 2f6e3c7..38907a8 100644 --- a/client/src/components/header.tsx +++ b/client/src/components/header.tsx @@ -1,10 +1,11 @@ import { h } from 'preact'; -import { Link } from 'preact-router/match'; +// import { Link } from 'preact-router/match'; const Header = ({ user }) => { const nav = () => ( diff --git a/client/src/components/offer.tsx b/client/src/components/offer.tsx new file mode 100644 index 0000000..1c81877 --- /dev/null +++ b/client/src/components/offer.tsx @@ -0,0 +1,19 @@ +import { h } from 'preact'; + +export default function Bundle(bundle) { + return ( +
+

{bundle.items.length} items - {bundle.total.toFixed(2)} {bundle.currency}

+ {bundle.items.map(i => ( +
+ +

{i.description}

+
+
{i.user.name}
+
{i.price} {i.currency}
+
+
+ ))} +
+ ) +} diff --git a/client/src/routes/bundles.tsx b/client/src/routes/bundles.tsx new file mode 100644 index 0000000..6fbcb60 --- /dev/null +++ b/client/src/routes/bundles.tsx @@ -0,0 +1,35 @@ +import { h } from 'preact'; +import { useState, useEffect } from 'preact/hooks'; + +import actions from './../actions'; +import Bundle from './../components/bundle'; + +function Bundles() { + const [bundles, setBundles] = useState([]); + + useEffect(() => { + async function getBundles() { + const [error, bundles] = await actions.getBundles(); + if (error) return console.error(error); + setBundles(bundles); + } + getBundles(); + }, []); + + if (!bundles.length) { + return ( +
+ loading... +
+ ); + } + + + return ( +
+ {bundles.map(Bundle)} +
+ ); +} + +export default Bundles; diff --git a/client/src/routes/home.tsx b/client/src/routes/home.tsx index 2489edf..c5ba943 100644 --- a/client/src/routes/home.tsx +++ b/client/src/routes/home.tsx @@ -2,21 +2,21 @@ import { h } from 'preact'; import { useState, useEffect } from 'preact/hooks'; import actions from './../actions'; -import Bundle from './../components/bundle'; +import Offer from './../components/offer'; function Home() { - const [bundles, setBundles] = useState([]); + const [offers, setOffers] = useState([]); useEffect(() => { - async function getBundles() { - const [error, bundles] = await actions.getBundles(); + async function getOffers() { + const [error, offers] = await actions.getOffers(); if (error) return console.error(error); - setBundles(bundles); + setOffers(offers); } - getBundles(); + getOffers(); }, []); - if (!bundles.length) { + if (!offers.length) { return (
loading... @@ -27,7 +27,7 @@ function Home() { return (
- {bundles.map(Bundle)} + {offers.map(Offer)}
); } diff --git a/client/src/routes/offers.tsx b/client/src/routes/offers.tsx deleted file mode 100644 index cb14a0e..0000000 --- a/client/src/routes/offers.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { h } from 'preact'; -import { useState } from 'preact/hooks'; -import axios from 'axios'; - -import actions from '../actions'; - -function result(r) { - function onClick() { - actions.postWant(r.id); - } - - return ( -
- -
-

{r.title}

-
{r.year}
-
{r.country}
-
{r.format.join(' ')}
-
- -
- ); -} - -const User = () => { - const [results, setResults] = useState([]); - const [noResults, setNoResults] = useState(false); - - async function fetchResults() { - const { data } = await actions.getBundles(); - - console.log(data); - - if (data.length === 0) { - setResults([]); - return setNoResults(true); - } - - setNoResults(false); - setResults(data); - } - - function handleSubmit(e) { - e.preventDefault(); - fetchResults(); - } - - const topEl = results.length - ? - : null; - - const noResultsEl = noResults - ?
No Results
- : null; - - return ( - - ); -}; - -export default User; diff --git a/client/src/style/index.less b/client/src/style/index.less index 0b463da..cf0c4d2 100644 --- a/client/src/style/index.less +++ b/client/src/style/index.less @@ -32,6 +32,23 @@ img { unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } +@font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/sourcecodepro/v21/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DMyQtMlrTA.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: 'Noto Sans Mono'; + font-style: normal; + font-weight: 400; + font-stretch: normal; + src: url(https://fonts.gstatic.com/s/notosansmono/v20/BngrUXNETWXI6LwhGYvaxZikqZqK6fBq6kPvUce2oAZcdthSBUsYck4-_FNJ093dVQ.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + // stuff from UX laws html { color:var(--base-font-color); @@ -72,7 +89,7 @@ html { --large-font-size: 2.369em; --xlarge-font-size: 3.157em; --base-font-family: 'Raleway', Arial, Sans-Serif; - --secondary-font-family: monospace; + --secondary-font-family: 'Noto Sans Mono', monospace; --base-font-color: var(--color-off-white); --secondary-font-color: var(--color-mid-grey);