want events
This commit is contained in:
1 parent
f5e75f4fe6
commit
30bf7426e4
19 files changed
+293
-108
No files matched your search
@@ -40,7 +40,7 @@ model User {
|
|||||||
|
|
||||||
model Want {
|
model Want {
|
||||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
release Int?
|
release Int
|
||||||
data Json
|
data Json
|
||||||
|
|
||||||
userId String @db.Uuid
|
userId String @db.Uuid
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const services = createServices(log);
|
|||||||
const HOST = 'http://localhost:41337';
|
const HOST = 'http://localhost:41337';
|
||||||
|
|
||||||
before(function setup() {
|
before(function setup() {
|
||||||
console.log('setup')
|
|
||||||
services.api.listen();
|
services.api.listen();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+29
-11
@@ -14,8 +14,9 @@ import type { Listing, User, Want, Prisma } from '@prisma/client';
|
|||||||
|
|
||||||
import { Pg } from './pg';
|
import { Pg } from './pg';
|
||||||
import routerAuth from './routers/router.auth';
|
import routerAuth from './routers/router.auth';
|
||||||
|
import { Events } from './events';
|
||||||
|
|
||||||
export default function createApi(log: Log, pg: Pg, discogs) {
|
export default function createApi(log: Log, pg: Pg, discogs, events) {
|
||||||
const PORT = 41337;
|
const PORT = 41337;
|
||||||
const SECRET = 'ohja4Eif9eidah3aj5ta2haa4iemooth';
|
const SECRET = 'ohja4Eif9eidah3aj5ta2haa4iemooth';
|
||||||
|
|
||||||
@@ -147,23 +148,40 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
|||||||
ctx.response.body = listings;
|
ctx.response.body = listings;
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/wants', async function wantPost(ctx, next) {
|
router.post('/want', async function wantPost(ctx, next) {
|
||||||
const { userId, releaseId } = ctx.request.body;
|
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
||||||
const user = await pg.userFindId(userId);
|
const { releaseId } = ctx.request.body;
|
||||||
|
const user = await pg.userFindId(ctx.state.user.id);
|
||||||
const release = await discogs.release(releaseId);
|
const release = await discogs.release(releaseId);
|
||||||
const want = await pg.wantUpsert(release, user);
|
const want = await pg.wantCreate([release], user);
|
||||||
ctx.response.body = want;
|
|
||||||
|
// fetch listings
|
||||||
|
const listings = await discogs.listings(want);
|
||||||
|
|
||||||
|
ctx.response.body = { want, listings };
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/wants', async function wantGet(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');
|
||||||
log.warn(ctx.state.user);
|
const { username } = ctx.request.body;
|
||||||
const user = ctx.state.user;
|
const user = await pg.userFindId(ctx.state.user.id);
|
||||||
const wants = await pg.wantFind(user);
|
|
||||||
|
const wants = await discogs.wantListSync(username, user);
|
||||||
|
|
||||||
|
events.emit(Events.WantsAdded, wants);
|
||||||
|
|
||||||
ctx.response.body = wants;
|
ctx.response.body = wants;
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/wants/search', async function wantPost(ctx, next) {
|
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 wantPost(ctx, next) {
|
||||||
const { artist, title } = ctx.request.body;
|
const { artist, title } = ctx.request.body;
|
||||||
const results = await discogs.search(artist, title);
|
const results = await discogs.search(artist, title);
|
||||||
ctx.response.body = results;
|
ctx.response.body = results;
|
||||||
|
|||||||
+10
-26
@@ -31,30 +31,15 @@ export default function discogs(log: Log, pg: Pg) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function user(discogs: string) {
|
async function wantListSync(username: string, user: User): Promise<Want[]> {
|
||||||
// const user = await pg.userFindName(discogs);
|
|
||||||
// if (user) return user;
|
|
||||||
// log.debug('fetching user', discogs);
|
|
||||||
// const { data } = await api.get(`${apiBase}/users/${discogs}`);
|
|
||||||
// return pg.userUpsert(data.username, data.username, data);
|
|
||||||
// }
|
|
||||||
|
|
||||||
async function wantlist(user: User): Promise<Want[]> {
|
|
||||||
const stored = await pg.userWants(user);
|
|
||||||
if (stored.length) return stored;
|
|
||||||
|
|
||||||
if (!user.discogs) {
|
|
||||||
log.error(user, 'attempted to fetch wantlist of non discogs user');
|
|
||||||
throw new Error('User is not associated with a discogs account');
|
|
||||||
}
|
|
||||||
|
|
||||||
return wantlistLatest(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function wantlistLatest(user: User): Promise<Want[]> {
|
|
||||||
const { username } = user.data as { username: string };
|
|
||||||
const { data: { wants, pagination }} = await api.get(`${apiBase}/users/${username}/wants`);
|
const { data: { wants, pagination }} = await api.get(`${apiBase}/users/${username}/wants`);
|
||||||
return Bluebird.mapSeries(wants, async r => pg.wantUpsert(r, user));
|
const releases = await Promise.all(wants.map(want => release(want.id)));
|
||||||
|
|
||||||
|
// convert want object to actual release and ignore duplicate errors
|
||||||
|
const results = await Promise.allSettled(releases.map(release => pg.wantCreate(release, user)));
|
||||||
|
// @ts-ignore
|
||||||
|
const additions = results.filter(r => r.status === 'fulfilled').map(r => r.value);
|
||||||
|
return additions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function release(id: DiscogsRelease): Promise<any> {
|
async function release(id: DiscogsRelease): Promise<any> {
|
||||||
@@ -99,6 +84,7 @@ export default function discogs(log: Log, pg: Pg) {
|
|||||||
try {
|
try {
|
||||||
log.debug('fetching listing', id);
|
log.debug('fetching listing', id);
|
||||||
const res = await api.get(`${apiBase}/marketplace/listings/${id}`);
|
const res = await api.get(`${apiBase}/marketplace/listings/${id}`);
|
||||||
|
log.info(res.data);
|
||||||
return pg.listingUpsert(res.data, want);
|
return pg.listingUpsert(res.data, want);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return handleApiError(e);
|
return handleApiError(e);
|
||||||
@@ -106,9 +92,7 @@ export default function discogs(log: Log, pg: Pg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// user,
|
wantListSync,
|
||||||
wantlist,
|
|
||||||
wantlistLatest,
|
|
||||||
inventory,
|
inventory,
|
||||||
listings,
|
listings,
|
||||||
listing,
|
listing,
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { EventEmitter } from 'node:events';
|
||||||
|
|
||||||
|
import type Log from 'bunyan';
|
||||||
|
import type { Listing, User, Want, Prisma } from '@prisma/client';
|
||||||
|
|
||||||
|
import { Pg } from './pg';
|
||||||
|
import routerAuth from './routers/router.auth';
|
||||||
|
|
||||||
|
export enum Events {
|
||||||
|
WantsAdded = 'WantsAdded',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function eventService(log: Log, pg: Pg, discogs) {
|
||||||
|
const events = new EventEmitter({ captureRejections: true });
|
||||||
|
|
||||||
|
events.on('error', function eventError(e) {
|
||||||
|
log.error(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
events.on(Events.WantsAdded, async function wantsAdded(wants: Want[]) {
|
||||||
|
log.info('wants added', wants);
|
||||||
|
});
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
+10
-16
@@ -16,8 +16,8 @@ export interface Pg {
|
|||||||
userCreate: (params: UserCreateParams) => Promise<User>,
|
userCreate: (params: UserCreateParams) => Promise<User>,
|
||||||
userWants: (user: User) => Promise<Want[]>,
|
userWants: (user: User) => Promise<Want[]>,
|
||||||
|
|
||||||
wantUpsert: (want: Want, user: User) => Promise<Want>,
|
wantCreate: (discogsWant: any, user: User) => Promise<Want>,
|
||||||
wantFind: (user: User) => Promise<Want[]>,
|
wantList: (user: User) => Promise<Want[]>,
|
||||||
listingUpsert: (listing: Listing, want: Want) => Promise<Listing>,
|
listingUpsert: (listing: Listing, want: Want) => Promise<Listing>,
|
||||||
listingFind: (id: DiscogsListing) => Promise<Listing>,
|
listingFind: (id: DiscogsListing) => Promise<Listing>,
|
||||||
listingRmUnlisted: (want: Want, ids: DiscogsListing[]) => Promise<number>,
|
listingRmUnlisted: (want: Want, ids: DiscogsListing[]) => Promise<number>,
|
||||||
@@ -60,22 +60,15 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function wantUpsert(dcWant, user) {
|
async function wantCreate(discogsRelease, user) {
|
||||||
const values: { userId: UUID, release: DiscogsListing, data: any } = {
|
const data: { userId: UUID, release: DiscogsListing, data: any } = {
|
||||||
data: dcWant,
|
data: discogsRelease,
|
||||||
release: dcWant.id,
|
release: discogsRelease.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
return prisma.want.upsert({
|
return prisma.want.create({
|
||||||
// get these from the generated types
|
data,
|
||||||
// ./node_modules/.prisma/client/index.d.ts
|
|
||||||
where: { userId_release: {
|
|
||||||
userId: values.userId,
|
|
||||||
release: values.release || null,
|
|
||||||
}},
|
|
||||||
update: values,
|
|
||||||
create: values,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +147,8 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
|
|||||||
userCreate,
|
userCreate,
|
||||||
userWants,
|
userWants,
|
||||||
|
|
||||||
wantUpsert,
|
wantCreate,
|
||||||
|
wantList,
|
||||||
listingUpsert,
|
listingUpsert,
|
||||||
listingFind,
|
listingFind,
|
||||||
listingRmUnlisted,
|
listingRmUnlisted,
|
||||||
|
|||||||
+7
-5
@@ -2,20 +2,22 @@ import { PrismaClient } from '@prisma/client'
|
|||||||
|
|
||||||
import discogsService from './discogs';
|
import discogsService from './discogs';
|
||||||
import pgService from './pg';
|
import pgService from './pg';
|
||||||
import { logAxiosError } from './utils';
|
|
||||||
import createLog from './log';
|
import createLog from './log';
|
||||||
import createApi from './api';
|
import createApi from './api';
|
||||||
|
import { eventService } from './events';
|
||||||
|
|
||||||
export default function createServices(log) {
|
export default function createServices(log) {
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const pg = pgService(log, prisma);
|
const pg = pgService(log, prisma);
|
||||||
const discogs = discogsService(log, pg);
|
const discogs = discogsService(log, pg);
|
||||||
const api = createApi(log, pg, discogs);
|
const events = eventService(log, pg, discogs);
|
||||||
|
const api = createApi(log, pg, discogs, events);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
prisma,
|
|
||||||
pg,
|
|
||||||
discogs,
|
|
||||||
api,
|
api,
|
||||||
|
discogs,
|
||||||
|
events,
|
||||||
|
pg,
|
||||||
|
prisma,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This application uses Discogs’ API but is not affiliated with, sponsored or endorsed by Discogs. ‘Discogs’ is a trademark of Zink Media, LLC.
|
||||||
Generated
+23
-9
@@ -12,6 +12,7 @@
|
|||||||
"preact": "^10.10.0",
|
"preact": "^10.10.0",
|
||||||
"preact-render-to-string": "^5.2.1",
|
"preact-render-to-string": "^5.2.1",
|
||||||
"preact-router": "^3.2.1",
|
"preact-router": "^3.2.1",
|
||||||
|
"react-feather": "^2.0.10",
|
||||||
"react-redux": "^8.0.2",
|
"react-redux": "^8.0.2",
|
||||||
"redux": "^4.2.0",
|
"redux": "^4.2.0",
|
||||||
"unistore": "^3.5.2"
|
"unistore": "^3.5.2"
|
||||||
@@ -12778,7 +12779,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -15798,7 +15798,6 @@
|
|||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@@ -15808,8 +15807,7 @@
|
|||||||
"node_modules/prop-types/node_modules/react-is": {
|
"node_modules/prop-types/node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/proxy-addr": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
@@ -16113,6 +16111,17 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-feather": {
|
||||||
|
"version": "2.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-feather/-/react-feather-2.0.10.tgz",
|
||||||
|
"integrity": "sha512-BLhukwJ+Z92Nmdcs+EMw6dy1Z/VLiJTzEQACDUEnWMClhYnFykJCGWQx+NmwP/qQHGX/5CzQ+TGi8ofg2+HzVQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||||
@@ -31062,8 +31071,7 @@
|
|||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"object-copy": {
|
"object-copy": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
@@ -33326,7 +33334,6 @@
|
|||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@@ -33336,8 +33343,7 @@
|
|||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -33580,6 +33586,14 @@
|
|||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-feather": {
|
||||||
|
"version": "2.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-feather/-/react-feather-2.0.10.tgz",
|
||||||
|
"integrity": "sha512-BLhukwJ+Z92Nmdcs+EMw6dy1Z/VLiJTzEQACDUEnWMClhYnFykJCGWQx+NmwP/qQHGX/5CzQ+TGi8ofg2+HzVQ==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"preact": "^10.10.0",
|
"preact": "^10.10.0",
|
||||||
"preact-render-to-string": "^5.2.1",
|
"preact-render-to-string": "^5.2.1",
|
||||||
"preact-router": "^3.2.1",
|
"preact-router": "^3.2.1",
|
||||||
|
"react-feather": "^2.0.10",
|
||||||
"react-redux": "^8.0.2",
|
"react-redux": "^8.0.2",
|
||||||
"redux": "^4.2.0",
|
"redux": "^4.2.0",
|
||||||
"unistore": "^3.5.2"
|
"unistore": "^3.5.2"
|
||||||
|
|||||||
@@ -49,7 +49,15 @@ const actions = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getWants: async function getWants() {
|
getWants: async function getWants() {
|
||||||
return api.get('/wants');
|
return api.get('/want/all');
|
||||||
|
},
|
||||||
|
|
||||||
|
postWant: async function postWant(releaseId) {
|
||||||
|
return api.post('/want', { releaseId });
|
||||||
|
},
|
||||||
|
|
||||||
|
postDiscogs: async function postDiscogs(username) {
|
||||||
|
return api.post('/want/discogs', { username });
|
||||||
},
|
},
|
||||||
|
|
||||||
getLogin: async function getLogin() {
|
getLogin: async function getLogin() {
|
||||||
|
|||||||
+4
-2
@@ -9,7 +9,8 @@ import Header from './components/header';
|
|||||||
import Home from './routes/home';
|
import Home from './routes/home';
|
||||||
import Login from './routes/login';
|
import Login from './routes/login';
|
||||||
import Search from './routes/search';
|
import Search from './routes/search';
|
||||||
import User from './routes/user';
|
import List from './routes/list';
|
||||||
|
import Offers from './routes/offers';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
@@ -38,7 +39,8 @@ function App() {
|
|||||||
<Route path="/" component={Home} />
|
<Route path="/" component={Home} />
|
||||||
<Route path="/login" component={Login} success={loginSuccess} />
|
<Route path="/login" component={Login} success={loginSuccess} />
|
||||||
<Route path="/search" component={Search} />
|
<Route path="/search" component={Search} />
|
||||||
<Route path="/list/" component={User} user={user} />
|
<Route path="/list" component={List} user={user} />
|
||||||
|
<Route path="/offers" component={Offers} user={user} />
|
||||||
</Router>
|
</Router>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Link } from 'preact-router/match';
|
|||||||
const Header = ({ user }) => {
|
const Header = ({ user }) => {
|
||||||
const nav = () => (
|
const nav = () => (
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/search">Offers</a>
|
<a href="/">Offers</a>
|
||||||
<a href='/list'>List</a>
|
<a href='/list'>List</a>
|
||||||
<a href="/search">Search</a>
|
<a href="/search">Search</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
|
||||||
|
function Want(want) {
|
||||||
|
console.log(want);
|
||||||
|
const r = want.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article key={r.id}>
|
||||||
|
<div>
|
||||||
|
<h3>{r.artists_sort} - {r.title}</h3>
|
||||||
|
<h5 class='alt-text' >{r.year}</h5>
|
||||||
|
<h5 class='alt-text'>{r.country}</h5>
|
||||||
|
<h5 class='alt-text'>{r.formats[0].descriptions.join(' ')}</h5>
|
||||||
|
</div>
|
||||||
|
<img src={r.thumb} />
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Want;
|
||||||
@@ -4,12 +4,17 @@ import { useState, useEffect } from 'preact/hooks';
|
|||||||
import actions from './../actions';
|
import actions from './../actions';
|
||||||
import Bundle from './../components/bundle';
|
import Bundle from './../components/bundle';
|
||||||
|
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const [bundles, setBundles] = useState([]);
|
const [bundles, setBundles] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
actions.getBundles().then(setBundles);
|
async function getBundles() {
|
||||||
|
const [error, bundles] = await actions.getBundles();
|
||||||
|
if (error) return console.error(error);
|
||||||
|
console.log(bundles, 'bundles')
|
||||||
|
setBundles(bundles);
|
||||||
|
}
|
||||||
|
getBundles();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!bundles.length) {
|
if (!bundles.length) {
|
||||||
|
|||||||
@@ -3,40 +3,37 @@ import { useState, useEffect } from 'preact/hooks';
|
|||||||
|
|
||||||
// import style from './style.less';
|
// import style from './style.less';
|
||||||
import actions from './../actions';
|
import actions from './../actions';
|
||||||
import Bundle from './../components/bundle';
|
import Want from './../components/want';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: any,
|
user: any,
|
||||||
offers: any[],
|
wants: any[],
|
||||||
getUserOffers: (userId: string) => void,
|
getUserOffers: (userId: string) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
const User = ({ user }: Props) => {
|
const List = ({ user }: Props) => {
|
||||||
const [offers, setOffers] = useState([]);
|
const [wants, setOffers] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getWants() {
|
async function getWants() {
|
||||||
const [error, wants] = await actions.getWants();
|
const [error, wants] = await actions.getWants();
|
||||||
if (error) return console.error(error);
|
if (error) return console.error(error);
|
||||||
|
console.log(wants, 'wants')
|
||||||
setOffers(wants);
|
setOffers(wants);
|
||||||
}
|
}
|
||||||
getWants();
|
getWants();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
if (!offers.length) {
|
if (!wants.length) {
|
||||||
return (
|
return <div> loading... </div>;
|
||||||
<div class={'home'}>
|
|
||||||
loading...
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={'home'}>
|
<div class="list">
|
||||||
{offers.map(Bundle)}
|
{wants.map(Want)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default User;
|
export default List;
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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 (
|
||||||
|
<article key={r.id}>
|
||||||
|
<button onClick={onClick}> + </button>
|
||||||
|
<div>
|
||||||
|
<h3>{r.title}</h3>
|
||||||
|
<h5 class='alt-text' >{r.year}</h5>
|
||||||
|
<h5 class='alt-text'>{r.country}</h5>
|
||||||
|
<h5 class='alt-text'>{r.format.join(' ')}</h5>
|
||||||
|
</div>
|
||||||
|
<img src={r.thumb} />
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
? <button onClick={() => window.scrollTo(0, 0)}> top </button>
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const noResultsEl = noResults
|
||||||
|
? <article>No Results</article>
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class='search'>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div>
|
||||||
|
{noResultsEl}
|
||||||
|
{results.map(result)}
|
||||||
|
{topEl}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default User;
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { useState } from 'preact/hooks';
|
import { useState } from 'preact/hooks';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { ExternalLink } from 'react-feather';
|
||||||
|
|
||||||
|
import actions from '../actions';
|
||||||
|
|
||||||
function result(r) {
|
function result(r) {
|
||||||
|
function onClick() {
|
||||||
|
actions.postWant(r.id);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article key={r.id}>
|
<article key={r.id}>
|
||||||
<button> + </button>
|
<button onClick={onClick}> + </button>
|
||||||
<div>
|
<div>
|
||||||
<h3>{r.title}</h3>
|
<h3>{r.title} <a href={`https://discogs.com${r.uri}`}><ExternalLink /></a></h3>
|
||||||
<h5 class='alt-text' >{r.year}</h5>
|
<h5 class='alt-text' >{r.year}</h5>
|
||||||
<h5 class='alt-text'>{r.country}</h5>
|
<h5 class='alt-text'>{r.country}</h5>
|
||||||
<h5 class='alt-text'>{r.format.join(' ')}</h5>
|
<h5 class='alt-text'>{r.format.join(' ')}</h5>
|
||||||
@@ -20,11 +27,12 @@ function result(r) {
|
|||||||
const User = () => {
|
const User = () => {
|
||||||
const [artist, setArtist] = useState('nine inch nails')
|
const [artist, setArtist] = useState('nine inch nails')
|
||||||
const [title, setTitle] = useState('the fragile');
|
const [title, setTitle] = useState('the fragile');
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
const [results, setResults] = useState([]);
|
const [results, setResults] = useState([]);
|
||||||
const [noResults, setNoResults] = useState(false);
|
const [noResults, setNoResults] = useState(false);
|
||||||
|
|
||||||
async function fetchResults() {
|
async function fetchResults() {
|
||||||
const { data } = await axios.post(`http://localhost:41337/wants/search`, {
|
const { data } = await axios.post(`http://localhost:41337/want/search`, {
|
||||||
artist,
|
artist,
|
||||||
title,
|
title,
|
||||||
});
|
});
|
||||||
@@ -40,11 +48,22 @@ const User = () => {
|
|||||||
setResults(data);
|
setResults(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function submitWantlist() {
|
||||||
|
const [error, wants] = await actions.postDiscogs(username);
|
||||||
|
// setNoResults(false);
|
||||||
|
// setResults(data);
|
||||||
|
}
|
||||||
|
|
||||||
function handleSubmit(e) {
|
function handleSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
fetchResults();
|
fetchResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDiscogsSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitWantlist();
|
||||||
|
}
|
||||||
|
|
||||||
const topEl = results.length
|
const topEl = results.length
|
||||||
? <button onClick={() => window.scrollTo(0, 0)}> top </button>
|
? <button onClick={() => window.scrollTo(0, 0)}> top </button>
|
||||||
: null;
|
: null;
|
||||||
@@ -55,14 +74,26 @@ const User = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class='search'>
|
<div class='search'>
|
||||||
<form action="" method="get">
|
<div>
|
||||||
<label for="artist">Artist</label>
|
<form action="" method="get">
|
||||||
<input type="text" name="artist" placeholder="Ben Klock" id="artist" value={artist} onChange={e => setArtist(e.target.value) } required />
|
<label for="artist">Artist</label>
|
||||||
<label for="title">Title</label>
|
<input type="text" name="artist" placeholder="Ben Klock" id="artist" value={artist} onChange={e => setArtist(e.target.value) } required />
|
||||||
<input type="text" name="title" placeholder="Subzero" id="title" value={title} onChange={e => setTitle(e.target.value)} required />
|
<label for="title">Title</label>
|
||||||
<input type="submit" value="Search" onClick={handleSubmit} />
|
<input type="text" name="title" placeholder="Subzero" id="title" value={title} onChange={e => setTitle(e.target.value)} required />
|
||||||
</form>
|
<input type="submit" value="Search" onClick={handleSubmit} />
|
||||||
<div></div>
|
</form>
|
||||||
|
<p>Data provided by Discogs.</p>
|
||||||
|
<hr />
|
||||||
|
<div>
|
||||||
|
<p>Sync Discogs Wantlist</p>
|
||||||
|
<form action="" method="get">
|
||||||
|
<label for="Discogs">Discogs Username</label>
|
||||||
|
<input type="text" name="username" placeholder="username" id="username" value={username} onChange={e => setUsername(e.target.value) } required />
|
||||||
|
<input type="submit" value="Search" onClick={handleDiscogsSubmit} />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div />
|
||||||
<div>
|
<div>
|
||||||
{noResultsEl}
|
{noResultsEl}
|
||||||
{results.map(result)}
|
{results.map(result)}
|
||||||
|
|||||||
@@ -180,22 +180,36 @@ button, [type=submit] {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
background-color: var(--secondary-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 0.5fr 2fr;
|
grid-template-columns: 1fr 0.5fr 2fr;
|
||||||
|
|
||||||
article {
|
article {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 4fr 1fr;
|
|
||||||
background-color: var(--secondary-background-color);
|
|
||||||
padding: var(--base-spacing);
|
padding: var(--base-spacing);
|
||||||
margin-bottom: var(--base-spacing);
|
margin-bottom: var(--base-spacing);
|
||||||
|
grid-template-columns: 1fr 4fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
margin-right: var(--base-spacing);
|
margin-right: var(--base-spacing);
|
||||||
background: var(--tertiary-background-color);
|
background: var(--tertiary-background-color);
|
||||||
border-color: var(--secondary-background-color);
|
border-color: var(--secondary-background-color);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
article {
|
||||||
|
flex: 1;
|
||||||
|
padding: var(--base-spacing);
|
||||||
|
margin: var(--base-spacing-half);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,11 +260,11 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.home {
|
.home {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr 0.5fr 2fr;
|
|
||||||
|
|
||||||
.bundle {
|
.bundle {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user