yeh boi
This commit is contained in:
1 parent
30bf7426e4
commit
323c7877b1
13 files changed
+94
-52
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
import createServices from './src/services';
|
import createServices from './src/services';
|
||||||
import createLog from './src/log';
|
import createLog from './src/log';
|
||||||
|
import { Events } from './src/events';
|
||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
const log = createLog();
|
const log = createLog();
|
||||||
@@ -14,8 +15,11 @@ async function start() {
|
|||||||
pg,
|
pg,
|
||||||
discogs,
|
discogs,
|
||||||
api,
|
api,
|
||||||
|
events,
|
||||||
} = createServices(log);
|
} = createServices(log);
|
||||||
|
|
||||||
|
events.emit(Events.StartupSync);
|
||||||
|
|
||||||
await api.listen();
|
await api.listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-6
@@ -153,12 +153,9 @@ export default function createApi(log: Log, pg: Pg, discogs, events) {
|
|||||||
const { releaseId } = ctx.request.body;
|
const { releaseId } = ctx.request.body;
|
||||||
const user = await pg.userFindId(ctx.state.user.id);
|
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.wantCreate([release], user);
|
const want = await pg.wantCreate(release, user);
|
||||||
|
events.emit(Events.WantsAdded, [want]);
|
||||||
// fetch listings
|
ctx.response.body = [want];
|
||||||
const listings = await discogs.listings(want);
|
|
||||||
|
|
||||||
ctx.response.body = { want, listings };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/want/discogs', async function wantListPost(ctx, next) {
|
router.post('/want/discogs', async function wantListPost(ctx, next) {
|
||||||
|
|||||||
+1
-1
@@ -84,7 +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);
|
log.debug(res.data);
|
||||||
return pg.listingUpsert(res.data, want);
|
return pg.listingUpsert(res.data, want);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return handleApiError(e);
|
return handleApiError(e);
|
||||||
|
|||||||
+28
-3
@@ -1,25 +1,50 @@
|
|||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
|
|
||||||
import type Log from 'bunyan';
|
import type Log from 'bunyan';
|
||||||
import type { Listing, User, Want, Prisma } from '@prisma/client';
|
import type { Want } from '@prisma/client';
|
||||||
|
|
||||||
import { Pg } from './pg';
|
import { Pg } from './pg';
|
||||||
import routerAuth from './routers/router.auth';
|
import routerAuth from './routers/router.auth';
|
||||||
|
|
||||||
export enum Events {
|
export enum Events {
|
||||||
WantsAdded = 'WantsAdded',
|
WantsAdded = 'WantsAdded',
|
||||||
|
StartupSync = 'StartupSync',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function eventService(log: Log, pg: Pg, discogs) {
|
export function eventService(log: Log, pg: Pg, discogs) {
|
||||||
const events = new EventEmitter({ captureRejections: true });
|
const events = new EventEmitter({ captureRejections: true });
|
||||||
|
|
||||||
events.on('error', function eventError(e) {
|
events.on('error', function eventError(e) {
|
||||||
log.error(e);
|
log.error('event error', e);
|
||||||
});
|
});
|
||||||
|
|
||||||
events.on(Events.WantsAdded, async function wantsAdded(wants: Want[]) {
|
events.on(Events.WantsAdded, async function wantsAdded(wants: Want[]) {
|
||||||
log.info('wants added', wants);
|
for (const want of wants) {
|
||||||
|
const listings = await discogs.listings(want);
|
||||||
|
log.info({ count: listings.length, title: want.id }, 'listings added')
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('finished fetching listings', { wants: wants.length });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
events.on(Events.StartupSync, async function wantsAdded() {
|
||||||
|
const wants = await pg.wantsToSync();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const want of wants) {
|
||||||
|
const listings = await discogs.listings(want);
|
||||||
|
log.info({ count: listings.length, title: want.id }, 'listings added')
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('finished syncing', { wants: wants.length });
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message === 'rate limited') {
|
||||||
|
log.warn('rate limited, trying again in 60s');
|
||||||
|
return setTimeout(() => events.emit(Events.StartupSync), 60 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,7 @@ export interface Pg {
|
|||||||
|
|
||||||
wantCreate: (discogsWant: any, user: User) => Promise<Want>,
|
wantCreate: (discogsWant: any, user: User) => Promise<Want>,
|
||||||
wantList: (user: User) => Promise<Want[]>,
|
wantList: (user: User) => Promise<Want[]>,
|
||||||
|
wantsToSync: () => 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,6 +61,10 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function wantsToSync() {
|
||||||
|
return prisma.want.findMany({});
|
||||||
|
}
|
||||||
|
|
||||||
async function wantCreate(discogsRelease, user) {
|
async function wantCreate(discogsRelease, user) {
|
||||||
const data: { userId: UUID, release: DiscogsListing, data: any } = {
|
const data: { userId: UUID, release: DiscogsListing, data: any } = {
|
||||||
data: discogsRelease,
|
data: discogsRelease,
|
||||||
@@ -149,6 +154,7 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
|
|||||||
|
|
||||||
wantCreate,
|
wantCreate,
|
||||||
wantList,
|
wantList,
|
||||||
|
wantsToSync,
|
||||||
listingUpsert,
|
listingUpsert,
|
||||||
listingFind,
|
listingFind,
|
||||||
listingRmUnlisted,
|
listingRmUnlisted,
|
||||||
|
|||||||
+13
-13
@@ -1,5 +1,4 @@
|
|||||||
import axios from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import type { AxiosInstance } from 'axios';
|
|
||||||
|
|
||||||
type ApiResponse = [string | null, any | null];
|
type ApiResponse = [string | null, any | null];
|
||||||
|
|
||||||
@@ -20,20 +19,17 @@ const api = axios.create({
|
|||||||
withCredentials: true
|
withCredentials: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// axios.interceptors.request.use(function onReq(config) {
|
function onSuccess(response) {
|
||||||
// return config;
|
|
||||||
// }, function onError(error) {
|
|
||||||
// // console.error(error);
|
|
||||||
// Promise.resolve(['Network Error', null]);
|
|
||||||
// });
|
|
||||||
|
|
||||||
api.interceptors.response.use(function onSuccess(response) {
|
|
||||||
return [null, response.data];
|
return [null, response.data];
|
||||||
}, function onError(error) {
|
}
|
||||||
// console.error(error);
|
|
||||||
|
function onError(error) {
|
||||||
if (error.response) return [error.response.data.error, null];
|
if (error.response) return [error.response.data.error, null];
|
||||||
if (error.request) return ['Network Error', null];
|
if (error.request) return ['Network Error', null];
|
||||||
});
|
return ['Unknown Error', null];
|
||||||
|
}
|
||||||
|
|
||||||
|
api.interceptors.response.use(onSuccess, onError);
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
getBundles: async function getBundles() {
|
getBundles: async function getBundles() {
|
||||||
@@ -52,6 +48,10 @@ const actions = {
|
|||||||
return api.get('/want/all');
|
return api.get('/want/all');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
postWantSearch: async function postWantSearch(artist, title) {
|
||||||
|
return api.post('/want/search', { artist, title });
|
||||||
|
},
|
||||||
|
|
||||||
postWant: async function postWant(releaseId) {
|
postWant: async function postWant(releaseId) {
|
||||||
return api.post('/want', { releaseId });
|
return api.post('/want', { releaseId });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ function App() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getLogin() {
|
async function getLogin() {
|
||||||
const [error, user] = await actions.getLogin();
|
const [error, user] = await actions.getLogin();
|
||||||
console.log(error, user);
|
|
||||||
if (error) return route('/login');
|
if (error) return route('/login');
|
||||||
setUser(user);
|
setUser(user);
|
||||||
route('/search');
|
route('/search');
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
|
|
||||||
export default function Bundle(bundle) {
|
export default function Bundle(bundle) {
|
||||||
console.log(bundle);
|
|
||||||
return (
|
return (
|
||||||
<div class="bundle">
|
<div class="bundle">
|
||||||
<h2>{bundle.username} - {bundle.seller}</h2>
|
<h2>{bundle.username} - {bundle.seller}</h2>
|
||||||
<h3>{bundle.items.length} items - {bundle.total} {bundle.currency}</h3>
|
<h3>{bundle.items.length} items - {bundle.total.toFixed(2)} {bundle.currency}</h3>
|
||||||
<h4>Ships: {bundle.mentionsAustralia && 'AU'} {bundle.mentionsWorldwide && 'WORLD'}</h4>
|
<h4>Ships: {bundle.mentionsAustralia && 'AU'} {bundle.mentionsWorldwide && 'WORLD'}</h4>
|
||||||
{bundle.items.map(i => (
|
{bundle.items.map(i => (
|
||||||
<div key={i.id}>
|
<div key={i.id}>
|
||||||
<a href={i.uri}>
|
<a href={i.uri} target="_blank" >
|
||||||
<h4>{i.description}</h4>
|
<h4>{i.description}</h4>
|
||||||
</a>
|
</a>
|
||||||
<h5>{i.user.name}</h5>
|
<h5>{i.user.name}</h5>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ function Want(want) {
|
|||||||
<div>
|
<div>
|
||||||
<h3>{r.artists_sort} - {r.title}</h3>
|
<h3>{r.artists_sort} - {r.title}</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.formats[0].descriptions.join(' ')}</h5>
|
<h5 class='alt-text'>{r.formats[0].descriptions.join(' ')}</h5>
|
||||||
</div>
|
</div>
|
||||||
<img src={r.thumb} />
|
<img src={r.thumb} />
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ function Home() {
|
|||||||
async function getBundles() {
|
async function getBundles() {
|
||||||
const [error, bundles] = await actions.getBundles();
|
const [error, bundles] = await actions.getBundles();
|
||||||
if (error) return console.error(error);
|
if (error) return console.error(error);
|
||||||
console.log(bundles, 'bundles')
|
|
||||||
setBundles(bundles);
|
setBundles(bundles);
|
||||||
}
|
}
|
||||||
getBundles();
|
getBundles();
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ const List = ({ user }: Props) => {
|
|||||||
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();
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
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 { ExternalLink, Check } from 'react-feather';
|
||||||
|
|
||||||
import actions from '../actions';
|
import actions from '../actions';
|
||||||
|
|
||||||
function result(r) {
|
function Result(r) {
|
||||||
function onClick() {
|
const [added, setAdded] = useState(false)
|
||||||
actions.postWant(r.id);
|
|
||||||
|
async function onClick() {
|
||||||
|
const [error, want] = await actions.postWant(r.id);
|
||||||
|
if (error) return false;
|
||||||
|
setAdded(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const classes = `${added ? 'added' : ''}`;
|
||||||
|
const btnText = added
|
||||||
|
? <Check />
|
||||||
|
: '+';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article key={r.id}>
|
<article key={r.id}>
|
||||||
<button onClick={onClick}> + </button>
|
<button onClick={onClick} disabled={added} class={classes} >{btnText}</button>
|
||||||
<div>
|
<div>
|
||||||
<h3>{r.title} <a href={`https://discogs.com${r.uri}`}><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>
|
||||||
<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>
|
||||||
@@ -25,33 +34,32 @@ function result(r) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const User = () => {
|
const User = () => {
|
||||||
const [artist, setArtist] = useState('nine inch nails')
|
const [artist, setArtist] = useState('')
|
||||||
const [title, setTitle] = useState('the fragile');
|
const [title, setTitle] = useState('');
|
||||||
const [username, setUsername] = useState('');
|
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/want/search`, {
|
const [error, results] = await actions.postWantSearch(artist, title);
|
||||||
artist,
|
|
||||||
title,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(data);
|
if (results.length === 0) {
|
||||||
|
|
||||||
if (data.length === 0) {
|
|
||||||
setResults([]);
|
setResults([]);
|
||||||
return setNoResults(true);
|
return setNoResults(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
setNoResults(false);
|
setNoResults(false);
|
||||||
setResults(data);
|
setResults(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitWantlist() {
|
async function submitWantlist() {
|
||||||
const [error, wants] = await actions.postDiscogs(username);
|
const [error, wants] = await actions.postDiscogs(username);
|
||||||
// setNoResults(false);
|
if (results.length === 0) {
|
||||||
// setResults(data);
|
setResults([]);
|
||||||
|
return setNoResults(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('synced', wants.length, 'wants');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit(e) {
|
function handleSubmit(e) {
|
||||||
@@ -88,15 +96,15 @@ const User = () => {
|
|||||||
<p>Sync Discogs Wantlist</p>
|
<p>Sync Discogs Wantlist</p>
|
||||||
<form action="" method="get">
|
<form action="" method="get">
|
||||||
<label for="Discogs">Discogs Username</label>
|
<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="text" name="username" placeholder="Discogs Username" id="username" value={username} onChange={e => setUsername(e.target.value) } required />
|
||||||
<input type="submit" value="Search" onClick={handleDiscogsSubmit} />
|
<input type="submit" value="Sync" onClick={handleDiscogsSubmit} />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<div>
|
<div>
|
||||||
{noResultsEl}
|
{noResultsEl}
|
||||||
{results.map(result)}
|
{results.map(Result)}
|
||||||
{topEl}
|
{topEl}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ html {
|
|||||||
--color-accent-blue: #5E9ED6;
|
--color-accent-blue: #5E9ED6;
|
||||||
|
|
||||||
--color-blue-gray: #506485;
|
--color-blue-gray: #506485;
|
||||||
|
--color-green: #537a34;
|
||||||
|
|
||||||
--base-accent-color: var(--color-accent-blue);
|
--base-accent-color: var(--color-accent-blue);
|
||||||
--fg-color: var(--color-off-white);
|
--fg-color: var(--color-off-white);
|
||||||
@@ -200,14 +201,20 @@ article {
|
|||||||
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);
|
||||||
|
|
||||||
|
&.added {
|
||||||
|
background: var(--color-green);
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
|
||||||
article {
|
article {
|
||||||
flex: 1;
|
flex: 1 1 20%;
|
||||||
padding: var(--base-spacing);
|
padding: var(--base-spacing);
|
||||||
margin: var(--base-spacing-half);
|
margin: var(--base-spacing-half);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user