auth init
This commit is contained in:
1 parent
faa2e70e08
commit
413aeedadf
29 files changed
+2854
-649
No files matched your search
File renamed without changes.
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extension": ["ts"],
|
||||||
|
"spec": "src/**/*.test.ts",
|
||||||
|
"require": "ts-node/register",
|
||||||
|
|
||||||
|
"watch-files": "**/*.ts",
|
||||||
|
"watch-ignore": ["db"]
|
||||||
|
}
|
||||||
File renamed without changes.
Vendored
+1
@@ -1,6 +1,7 @@
|
|||||||
// global type aliases and whatnot
|
// global type aliases and whatnot
|
||||||
|
|
||||||
type UUID = string;
|
type UUID = string;
|
||||||
|
type Email = string;
|
||||||
|
|
||||||
type DiscogsName = string;
|
type DiscogsName = string;
|
||||||
type DiscogsListing = number;
|
type DiscogsListing = number;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import createServices from './src/services';
|
||||||
|
import createLog from './src/log';
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
const log = createLog();
|
||||||
|
|
||||||
|
process.on('SIGTERM', () => {
|
||||||
|
log.debug('SIGTERM received, exiting');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
prisma,
|
||||||
|
pg,
|
||||||
|
discogs,
|
||||||
|
api,
|
||||||
|
} = createServices(log);
|
||||||
|
|
||||||
|
await api.listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
+2378
-472
File diff suppressed because it is too large.
Load diff
@@ -4,7 +4,8 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "mocha",
|
||||||
|
"test:watch": "mocha --watch",
|
||||||
"migrate": "knex migrate:latest",
|
"migrate": "knex migrate:latest",
|
||||||
"migrate:make": "prisma migrate dev --name",
|
"migrate:make": "prisma migrate dev --name",
|
||||||
"start": "ts-node-dev --respawn --transpile-only index.ts | bunyan -o short"
|
"start": "ts-node-dev --respawn --transpile-only index.ts | bunyan -o short"
|
||||||
@@ -12,9 +13,13 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bluebird": "^3.5.36",
|
"@types/bluebird": "^3.5.36",
|
||||||
"@types/lodash": "^4.14.182",
|
|
||||||
"@types/koa": "^2.13.5",
|
"@types/koa": "^2.13.5",
|
||||||
|
"@types/lodash": "^4.14.182",
|
||||||
|
"@types/mocha": "^9.1.1",
|
||||||
|
"axios": "^0.27.2",
|
||||||
|
"mocha": "^10.0.0",
|
||||||
"node-dev": "^7.4.3",
|
"node-dev": "^7.4.3",
|
||||||
"prisma": "^4.1.0",
|
"prisma": "^4.1.0",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
@@ -24,11 +29,20 @@
|
|||||||
"@koa/cors": "^3.3.0",
|
"@koa/cors": "^3.3.0",
|
||||||
"@koa/router": "^12.0.0",
|
"@koa/router": "^12.0.0",
|
||||||
"@prisma/client": "^4.1.0",
|
"@prisma/client": "^4.1.0",
|
||||||
"axios": "^0.27.2",
|
"bcrypt": "^5.0.1",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"bunyan": "^1.8.15",
|
"bunyan": "^1.8.15",
|
||||||
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"koa": "^2.13.4",
|
"koa": "^2.13.4",
|
||||||
"koa-body": "^5.0.0",
|
"koa-body": "^5.0.0",
|
||||||
"lodash": "^4.17.21"
|
"koa-passport": "^5.0.0",
|
||||||
|
"koa-session": "^6.2.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"passport-local": "^1.0.0"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"koa-passport": {
|
||||||
|
"passport": "^0.5"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,8 @@ model Listing {
|
|||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
name String @default("unknown")
|
email String @unique
|
||||||
|
password String
|
||||||
discogs String? @unique
|
discogs String? @unique
|
||||||
data Json?
|
data Json?
|
||||||
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import assert from 'assert/strict';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import createServices from './services';
|
||||||
|
import createLog from './log';
|
||||||
|
|
||||||
|
const log = createLog();
|
||||||
|
log.level('error');
|
||||||
|
const services = createServices(log);
|
||||||
|
|
||||||
|
const HOST = 'http://localhost:41337';
|
||||||
|
|
||||||
|
before(function setup() {
|
||||||
|
console.log('setup')
|
||||||
|
services.api.listen();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function teardown(done) {
|
||||||
|
services.api.server.close(done)
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('api', function apiTests() {
|
||||||
|
it('listens', async function listenTest() {
|
||||||
|
const res = await axios.get(`${HOST}/health`);
|
||||||
|
assert.equal(res.status, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('register', async function register() {
|
||||||
|
const body = {
|
||||||
|
email: `test+${Date.now()}@user.com`,
|
||||||
|
password: 'woaaah theres no limit',
|
||||||
|
};
|
||||||
|
const { data } = await axios.post(`${HOST}/auth/register`, body);
|
||||||
|
assert.equal(data.email, body.email);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('login', async function login() {
|
||||||
|
const body = {
|
||||||
|
email: `test+${Date.now()}@user.com`,
|
||||||
|
password: 'woaaah theres no limit',
|
||||||
|
};
|
||||||
|
await axios.post(`${HOST}/auth/register`, body);
|
||||||
|
const res = await axios.post(`${HOST}/auth/login`, body);
|
||||||
|
assert(res.headers['set-cookie']);
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
@@ -1,32 +1,38 @@
|
|||||||
|
import http from 'http';
|
||||||
|
|
||||||
import koa from 'koa';
|
import koa from 'koa';
|
||||||
import bodyParser from 'koa-body';
|
import bodyParser from 'koa-body';
|
||||||
|
import session from 'koa-session';
|
||||||
import koaRouter from '@koa/router'
|
import koaRouter from '@koa/router'
|
||||||
import cors from '@koa/cors';
|
import cors from '@koa/cors';
|
||||||
import http from 'http';
|
|
||||||
import { groupBy } from 'lodash';
|
import { groupBy } from 'lodash';
|
||||||
import Bluebird from 'bluebird';
|
import Bluebird from 'bluebird';
|
||||||
|
|
||||||
import type Log from 'bunyan';
|
import type Log from 'bunyan';
|
||||||
|
import type { Listing, User, Want, Prisma } from '@prisma/client';
|
||||||
|
|
||||||
import { Pg } from './pg';
|
import { Pg } from './pg';
|
||||||
import type { Listing, User, Want, Prisma } from '@prisma/client';
|
import routerAuth from './routers/router.auth';
|
||||||
|
|
||||||
export default function createApi(log: Log, pg: Pg, discogs) {
|
export default function createApi(log: Log, pg: Pg, discogs) {
|
||||||
const PORT = 41337;
|
const PORT = 41337;
|
||||||
|
const SECRET = 'ohja4Eif9eidah3aj5ta2haa4iemooth';
|
||||||
|
|
||||||
const app = new koa();
|
const app = new koa();
|
||||||
|
const server = http.createServer(app.callback());
|
||||||
|
|
||||||
app.use(async (ctx, next) => {
|
// app.use(async (ctx, next) => {
|
||||||
try {
|
// try {
|
||||||
await next();
|
// await next();
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
log.error(err);
|
// log.error(err);
|
||||||
ctx.status = err.statusCode || err.status || 500;
|
// ctx.status = err.statusCode || err.status || 500;
|
||||||
ctx.body = {
|
// ctx.body = {
|
||||||
message: err.message
|
// message: err.message
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
const router = koaRouter();
|
const router = koaRouter();
|
||||||
|
|
||||||
@@ -65,26 +71,23 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
|||||||
mentionsWorldwide: shipping.toLowerCase().includes('world'),
|
mentionsWorldwide: shipping.toLowerCase().includes('world'),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(bundle);
|
|
||||||
return bundle;
|
return bundle;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function populateUserListings(user: User) {
|
async function populateUserListings(user: User) {
|
||||||
log.info(user);
|
|
||||||
|
|
||||||
const wants = await discogs.wantlist(user);
|
const wants = await discogs.wantlist(user);
|
||||||
|
|
||||||
// const latest = await discogs.wantlistLatest(user);
|
// const latest = await discogs.wantlistLatest(user);
|
||||||
// if (wants.length !== latest.length)
|
// if (wants.length !== latest.length)
|
||||||
|
|
||||||
log.info({ username: user.discogs, wantCount: wants.length });
|
log.debug({ username: user.discogs, wantCount: wants.length });
|
||||||
|
|
||||||
return Bluebird.mapSeries<Want, Listing[]>(wants, async want => {
|
return Bluebird.mapSeries<Want, Listing[]>(wants, async want => {
|
||||||
const listings = await discogs.listings(want);
|
const listings = await discogs.listings(want);
|
||||||
const populated = listings.filter(l => l);
|
const populated = listings.filter(l => l);
|
||||||
log.info({ id: want.id, release: want.release, populated: populated.length, total: listings.length });
|
log.debug({ id: want.id, release: want.release, populated: populated.length, total: listings.length });
|
||||||
return populated;
|
return populated;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -95,19 +98,34 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
|||||||
reject(err);
|
reject(err);
|
||||||
})
|
})
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
log.info('listening', { PORT });
|
log.info('listening', { PORT });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors({ origin: false, credentials: true }));
|
||||||
app.use(bodyParser())
|
app.use(bodyParser())
|
||||||
|
|
||||||
router.get('/bundles', async (ctx, next) => {
|
app.keys = [SECRET];
|
||||||
|
app.use(session(app));
|
||||||
|
|
||||||
|
const { authRouter, passport } = routerAuth(log, pg);
|
||||||
|
app.use(passport.initialize());
|
||||||
|
app.use(passport.session())
|
||||||
|
app.use(authRouter.routes());
|
||||||
|
|
||||||
|
router.get('/health', async function health(ctx) {
|
||||||
|
ctx.status = 200;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function requireAuth(ctx, next) {
|
||||||
|
log.warn(ctx.isAuthenticated());
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/bundles', requireAuth, async (ctx, next) => {
|
||||||
const bundles = await getBundles();
|
const bundles = await getBundles();
|
||||||
const search = await discogs.searchMaster('nine inch nails', 'the fragile');
|
|
||||||
log.info(search);
|
|
||||||
ctx.response.body = bundles;
|
ctx.response.body = bundles;
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -127,12 +145,6 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
|||||||
ctx.response.body = bundles;
|
ctx.response.body = bundles;
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/user', async function usersPost(ctx, next) {
|
|
||||||
const { name } = ctx.request.body;
|
|
||||||
const user = await pg.userUpsert(null, name, null);
|
|
||||||
ctx.response.body = user;
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/user/populate', async function usersPopulate(ctx, next) {
|
router.post('/user/populate', async function usersPopulate(ctx, next) {
|
||||||
const { userId } = ctx.request.body;
|
const { userId } = ctx.request.body;
|
||||||
const user = await pg.userFindId(userId);
|
const user = await pg.userFindId(userId);
|
||||||
@@ -158,6 +170,7 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
app,
|
app,
|
||||||
|
server,
|
||||||
listen,
|
listen,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { KoaPassport } from 'koa-passport';
|
||||||
|
import { Strategy as LocalStrategy } from 'passport-local';
|
||||||
|
|
||||||
|
export default function authService(pg) {
|
||||||
|
const passport = new KoaPassport();
|
||||||
|
|
||||||
|
passport.serializeUser((user, done) => done(null, user.id));
|
||||||
|
passport.deserializeUser((id, done) => {
|
||||||
|
pg.userFindId(id)
|
||||||
|
.then(u => done(null, u))
|
||||||
|
.catch(e => done(e, null));
|
||||||
|
})
|
||||||
|
|
||||||
|
passport.use(new LocalStrategy(
|
||||||
|
{ usernameField: 'email '},
|
||||||
|
function localStrategy(email, password, done) {
|
||||||
|
pg.userFindEmail(email)
|
||||||
|
.then(user => {
|
||||||
|
if (!user) return done(null, false);
|
||||||
|
// if (!user.verifyPassword(password)) return done(null, false);
|
||||||
|
return done(null, user);
|
||||||
|
})
|
||||||
|
.catch(e => done(e, null));
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
return passport;
|
||||||
|
}
|
||||||
@@ -31,13 +31,13 @@ export default function discogs(log: Log, pg: Pg) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function user(discogs: string) {
|
// async function user(discogs: string) {
|
||||||
const user = await pg.userFindName(discogs);
|
// const user = await pg.userFindName(discogs);
|
||||||
if (user) return user;
|
// if (user) return user;
|
||||||
log.debug('fetching user', discogs);
|
// log.debug('fetching user', discogs);
|
||||||
const { data } = await api.get(`${apiBase}/users/${discogs}`);
|
// const { data } = await api.get(`${apiBase}/users/${discogs}`);
|
||||||
return pg.userUpsert(data.username, data.username, data);
|
// return pg.userUpsert(data.username, data.username, data);
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function wantlist(user: User): Promise<Want[]> {
|
async function wantlist(user: User): Promise<Want[]> {
|
||||||
const stored = await pg.userWants(user);
|
const stored = await pg.userWants(user);
|
||||||
@@ -106,7 +106,7 @@ export default function discogs(log: Log, pg: Pg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
// user,
|
||||||
wantlist,
|
wantlist,
|
||||||
wantlistLatest,
|
wantlistLatest,
|
||||||
inventory,
|
inventory,
|
||||||
File renamed without changes.
@@ -1,11 +1,19 @@
|
|||||||
|
import bcrypt from 'bcrypt';
|
||||||
import type Log from 'bunyan';
|
import type Log from 'bunyan';
|
||||||
import type { PrismaClient, Want, User, Listing, Prisma } from '@prisma/client';
|
import type { PrismaClient, Want, User, Listing, Prisma } from '@prisma/client';
|
||||||
import * as Bluebird from 'bluebird';
|
import * as Bluebird from 'bluebird';
|
||||||
|
|
||||||
|
export interface UserCreateParams {
|
||||||
|
discogs: DiscogsName | null,
|
||||||
|
email: string,
|
||||||
|
password: string,
|
||||||
|
data: any,
|
||||||
|
}
|
||||||
|
|
||||||
export interface Pg {
|
export interface Pg {
|
||||||
userFindId: (id: UUID) => Promise<User>,
|
userFindId: (id: UUID) => Promise<User>,
|
||||||
userFindName: (name: DiscogsName) => Promise<User>,
|
userFindEmail: (email: Email) => Promise<User>,
|
||||||
userUpsert: (discogs: DiscogsName | null, name: string, data: any) => Promise<User>,
|
userCreate: (params: UserCreateParams) => Promise<User>,
|
||||||
userWants: (user: User) => Promise<Want[]>,
|
userWants: (user: User) => Promise<Want[]>,
|
||||||
|
|
||||||
wantUpsert: (want: Want, user: User) => Promise<Want>,
|
wantUpsert: (want: Want, user: User) => Promise<Want>,
|
||||||
@@ -21,31 +29,27 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
|
|||||||
return prisma.user.findUnique({ where: { id }});
|
return prisma.user.findUnique({ where: { id }});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function userFindName(name: DiscogsName) {
|
async function userFindEmail(email: Email) {
|
||||||
return prisma.user.findUnique({ where: { discogs: name }});
|
return prisma.user.findUnique({ where: { email }});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function userWants(user: User) {
|
async function userWants(user: User) {
|
||||||
return prisma.want.findMany({ where: { userId: user.id }});
|
return prisma.want.findMany({ where: { userId: user.id }});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function userUpsert(discogs: DiscogsName | null, name: string, data: any) {
|
async function userCreate(params: UserCreateParams) {
|
||||||
const insert = {
|
const hashRounds = 10;
|
||||||
discogs,
|
const password = await bcrypt.hash(params.password, hashRounds);
|
||||||
name,
|
|
||||||
data,
|
const data = {
|
||||||
|
discogs: params.discogs,
|
||||||
|
email: params.email,
|
||||||
|
data: params.data,
|
||||||
|
password,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (discogs) return prisma.user.upsert({
|
|
||||||
where: {
|
|
||||||
discogs,
|
|
||||||
},
|
|
||||||
update: insert,
|
|
||||||
create: insert,
|
|
||||||
});
|
|
||||||
|
|
||||||
return prisma.user.create({
|
return prisma.user.create({
|
||||||
data: { name },
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,8 +143,8 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
userFindId,
|
userFindId,
|
||||||
userFindName,
|
userFindEmail,
|
||||||
userUpsert,
|
userCreate,
|
||||||
userWants,
|
userWants,
|
||||||
|
|
||||||
wantUpsert,
|
wantUpsert,
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import { KoaPassport } from 'koa-passport';
|
||||||
|
import { Strategy as LocalStrategy } from 'passport-local';
|
||||||
|
import Router from '@koa/router'
|
||||||
|
|
||||||
|
import type { Pg } from './../pg';
|
||||||
|
|
||||||
|
export default function authRouter(log, pg: Pg) {
|
||||||
|
const passport = new KoaPassport();
|
||||||
|
|
||||||
|
async function verifyPassword(user, password) {
|
||||||
|
const match = await bcrypt.compare(password, user.password);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
passport.use(new LocalStrategy(
|
||||||
|
{ usernameField: 'email' },
|
||||||
|
function localStrategy(email, password, done) {
|
||||||
|
pg.userFindEmail(email)
|
||||||
|
.then(user => {
|
||||||
|
if (!user) return done(null, false);
|
||||||
|
if (!verifyPassword(user, password)) return done(null, false);
|
||||||
|
return done(null, user);
|
||||||
|
})
|
||||||
|
.catch(e => done(e, null));
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
passport.serializeUser((user, done) => done(null, user.id));
|
||||||
|
passport.deserializeUser((id, done) => {
|
||||||
|
pg.userFindId(id)
|
||||||
|
.then(u => done(null, u))
|
||||||
|
.catch(e => done(e, null));
|
||||||
|
})
|
||||||
|
|
||||||
|
const authRouter = new Router({ prefix: '/auth' });
|
||||||
|
|
||||||
|
authRouter.post('/login', async (ctx, next) => {
|
||||||
|
await passport.authenticate('local', (err, user, info, status) => {
|
||||||
|
if (user) {
|
||||||
|
ctx.login(user);
|
||||||
|
ctx.status = 200;
|
||||||
|
ctx.body = { status: 'success' };
|
||||||
|
} else {
|
||||||
|
ctx.status = 400;
|
||||||
|
ctx.body = { status: 'error' };
|
||||||
|
}
|
||||||
|
})(ctx, next);
|
||||||
|
});
|
||||||
|
|
||||||
|
authRouter.post('/register', async (ctx, next) => {
|
||||||
|
const { discogs, email, password } = ctx.request.body;
|
||||||
|
// fetch discogs data if they have it
|
||||||
|
const params = { discogs, email, password, data: {} };
|
||||||
|
const user = await pg.userCreate(params)
|
||||||
|
ctx.response.body = user;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { authRouter, passport };
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
import discogsService from './discogs';
|
||||||
|
import pgService from './pg';
|
||||||
|
import { logAxiosError } from './utils';
|
||||||
|
import createLog from './log';
|
||||||
|
import createApi from './api';
|
||||||
|
|
||||||
|
export default function createServices(log) {
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
const pg = pgService(log, prisma);
|
||||||
|
const discogs = discogsService(log, pg);
|
||||||
|
const api = createApi(log, pg, discogs);
|
||||||
|
|
||||||
|
return {
|
||||||
|
prisma,
|
||||||
|
pg,
|
||||||
|
discogs,
|
||||||
|
api,
|
||||||
|
};
|
||||||
|
}
|
||||||
File renamed without changes.
@@ -33,7 +33,8 @@
|
|||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
"types": [
|
"types": [
|
||||||
"./index.d.ts"
|
"./index.d.ts",
|
||||||
|
"mocha"
|
||||||
], /* Specify type package names to be included without being referenced in a source file. */
|
], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
@@ -101,5 +102,10 @@
|
|||||||
/* Completeness */
|
/* Completeness */
|
||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
}
|
},
|
||||||
|
|
||||||
|
"ts-node": {
|
||||||
|
// strict mode gtfo
|
||||||
|
"transpileOnly": true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "preact build",
|
"build": "preact build",
|
||||||
"start": "preact watch",
|
"start": "preact --no-clear watch",
|
||||||
"lint": "eslint src"
|
"lint": "eslint src"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
|||||||
+16
-8
@@ -1,17 +1,25 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const actions = store => ({
|
const API_URL = 'http://localhost:41337';
|
||||||
|
|
||||||
|
axios.defaults.withCredentials = true;
|
||||||
|
axios.defaults.crossDomain = true;
|
||||||
|
|
||||||
|
const actions = {
|
||||||
getBundles: async () => {
|
getBundles: async () => {
|
||||||
const { data: bundles } = await axios.get('http://localhost:41337/bundles');
|
const { data: bundles } = await axios.get(`${API_URL}/bundles`);
|
||||||
return { bundles };
|
return bundles;
|
||||||
},
|
},
|
||||||
|
|
||||||
getUserOffers: async (state, userId) => {
|
getUserOffers: async (state, userId) => {
|
||||||
store.setState({ offers: [] });
|
const { data: offers } = await axios.get(`${API_URL}/user/${userId}/offers`);
|
||||||
const { data: offers } = await axios.get(`http://localhost:41337/user/${userId}/offers`);
|
return offers;
|
||||||
console.log(userId, offers);
|
|
||||||
return { offers };
|
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
getOffers: async (userId) => {
|
||||||
|
const { data } = await axios.get(`${API_URL}/user/${userId}/offers`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default actions;
|
export default actions;
|
||||||
@@ -8,6 +8,8 @@ import actions from './actions';
|
|||||||
import Header from './components/header';
|
import Header from './components/header';
|
||||||
|
|
||||||
import Home from './routes/home';
|
import Home from './routes/home';
|
||||||
|
import Login from './routes/login';
|
||||||
|
import Register from './routes/register';
|
||||||
import Search from './routes/search';
|
import Search from './routes/search';
|
||||||
import User from './routes/user';
|
import User from './routes/user';
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ const App = () => (
|
|||||||
<Header />
|
<Header />
|
||||||
<Router>
|
<Router>
|
||||||
<Route path="/" component={Home} />
|
<Route path="/" component={Home} />
|
||||||
|
<Route path="/login" component={Login} />
|
||||||
|
<Route path="/register" component={Register} />
|
||||||
<Route path="/search" component={Search} />
|
<Route path="/search" component={Search} />
|
||||||
<Route path="/user/:userId" component={User} />
|
<Route path="/user/:userId" component={User} />
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
+24
-23
@@ -1,30 +1,31 @@
|
|||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { connect } from 'unistore/preact'
|
import { useState, useEffect } from 'preact/hooks';
|
||||||
|
|
||||||
import actions from './../actions';
|
import actions from './../actions';
|
||||||
import Bundle from './../components/bundle';
|
import Bundle from './../components/bundle';
|
||||||
|
|
||||||
interface Props {
|
|
||||||
bundles: any[];
|
function Home() {
|
||||||
getBundles: () => void,
|
const [bundles, setBundles] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
actions.getBundles().then(setBundles);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!bundles.length) {
|
||||||
|
return (
|
||||||
|
<div class={'home'}>
|
||||||
|
loading...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={'home'}>
|
||||||
|
{bundles.map(Bundle)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Home({ bundles, getBundles }: Props) {
|
export default Home;
|
||||||
if (!bundles.length) {
|
|
||||||
getBundles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={'home'}>
|
|
||||||
loading...
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={'home'}>
|
|
||||||
{bundles.map(Bundle)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect('bundles', actions)(Home);
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import { useState, useEffect } from 'preact/hooks';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const Register = () => {
|
||||||
|
const [email, setEmail] = useState('')
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
async function postLogin() {
|
||||||
|
const { data } = await axios.post(`http://localhost:41337/auth/login`, {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
postLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={'home'}>
|
||||||
|
<form action="" method="get" >
|
||||||
|
<label for="email">email: </label>
|
||||||
|
<input type="email" name="email" id="email" value={email} required onChange={(e) => setEmail(e.target.value)} />
|
||||||
|
<label for="password">password: </label>
|
||||||
|
<input type="password" name="password" id="password" value={password} required onChange={(e) => setPassword(e.target.value)} />
|
||||||
|
<input type="submit" value="Login" onClick={handleSubmit} />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Register;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import { useState, useEffect } from 'preact/hooks';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const Register = () => {
|
||||||
|
const [email, setEmail] = useState('')
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
async function postRegister() {
|
||||||
|
const { data } = await axios.post(`http://localhost:41337/auth/register`, {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
postRegister();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={'home'}>
|
||||||
|
<form action="" method="get" >
|
||||||
|
<label for="email">email: </label>
|
||||||
|
<input type="email" name="email" id="email" value={email} required onChange={(e) => setEmail(e.target.value)} />
|
||||||
|
<label for="password">password: </label>
|
||||||
|
<input type="password" name="password" id="password" value={password} required onChange={(e) => setPassword(e.target.value)} />
|
||||||
|
<input type="submit" value="Register" onClick={handleSubmit} />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Register;
|
||||||
+16
-23
@@ -1,9 +1,7 @@
|
|||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { useState, useEffect } from 'preact/hooks';
|
import { useState, useEffect } from 'preact/hooks';
|
||||||
import { connect } from 'unistore/preact';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
import style from './style.less';
|
// import style from './style.less';
|
||||||
import actions from './../actions';
|
import actions from './../actions';
|
||||||
import Bundle from './../components/bundle';
|
import Bundle from './../components/bundle';
|
||||||
|
|
||||||
@@ -14,31 +12,26 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const User = ({ userId }: Props) => {
|
const User = ({ userId }: Props) => {
|
||||||
const [offers, setOffers] = useState([]);
|
const [offers, setOffers] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchOffers() {
|
actions.getOffers(userId).then(setOffers);
|
||||||
const { data } = await axios.get(`http://localhost:41337/user/${userId}/offers`);
|
|
||||||
setOffers(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchOffers();
|
|
||||||
}, [userId]);
|
}, [userId]);
|
||||||
|
|
||||||
|
|
||||||
if (!offers.length) {
|
if (!offers.length) {
|
||||||
return (
|
return (
|
||||||
<div class={'home'}>
|
<div class={'home'}>
|
||||||
loading...
|
loading...
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={'home'}>
|
<div class={'home'}>
|
||||||
{offers.map(Bundle)}
|
{offers.map(Bundle)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(['offers'], actions)(User);
|
export default User;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
version: "3.3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
pg:
|
||||||
|
image: postgres:latest
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: 'mcrates'
|
||||||
|
POSTGRES_DB: 'test'
|
||||||
|
POSTGRES_PASSWORD: 'digdeep'
|
||||||
|
|
||||||
|
api:
|
||||||
|
image: node:16
|
||||||
|
working_dir: /opt/mcrates/api
|
||||||
|
entrypoint: sleep 10000
|
||||||
|
depends_on:
|
||||||
|
- pg
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: "postgresql://mcrates:digdeep@pg:5432/test?schema=public"
|
||||||
|
volumes:
|
||||||
|
- "./api:/opt/mcrates/api"
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
version: "3.3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
pg:
|
||||||
|
image: postgres:latest
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: 'mcrates'
|
||||||
|
POSTGRES_DB: 'mcrates'
|
||||||
|
POSTGRES_PASSWORD: 'digdeep'
|
||||||
|
ports:
|
||||||
|
- '45432:5432'
|
||||||
|
volumes:
|
||||||
|
- "./db:/var/lib/postgresql/data"
|
||||||
|
|
||||||
|
# api:
|
||||||
|
# image: node:16
|
||||||
|
# working_dir: /opt/mcrates/api
|
||||||
|
# entrypoint: npm start
|
||||||
|
# environment:
|
||||||
|
# DATABASE_URL: 'postgresql://mcrates:digdeep@pg:5432/mcrates?schema=public'
|
||||||
|
# ports:
|
||||||
|
# - '41337:41337'
|
||||||
|
# volumes:
|
||||||
|
# - "./api:/opt/mcrates/api"
|
||||||
|
|
||||||
|
# client:
|
||||||
|
# image: node:16
|
||||||
|
# working_dir: /opt/mcrates/client
|
||||||
|
# entrypoint: npm start
|
||||||
|
# ports:
|
||||||
|
# - '8080:8080'
|
||||||
|
# volumes:
|
||||||
|
# - "./client:/opt/mcrates/client"
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
version: "3.3"
|
|
||||||
|
|
||||||
services:
|
|
||||||
pg:
|
|
||||||
image: postgres:latest
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: 'mcrates'
|
|
||||||
POSTGRES_DB: 'mcrates'
|
|
||||||
POSTGRES_PASSWORD: 'digdeep'
|
|
||||||
ports:
|
|
||||||
- '45432:5432'
|
|
||||||
volumes:
|
|
||||||
- "./db:/var/lib/postgresql/data"
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import fs from 'fs/promises';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import Bunyan from 'bunyan';
|
|
||||||
import { PrismaClient } from '@prisma/client'
|
|
||||||
import Promise from "bluebird";
|
|
||||||
|
|
||||||
import discogsService from './src/discogs';
|
|
||||||
import pgService from './src/pg';
|
|
||||||
import { logAxiosError } from './src/utils';
|
|
||||||
import createLog from './src/log';
|
|
||||||
import createApi from './src/api';
|
|
||||||
|
|
||||||
async function start(log) {
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
const pg = pgService(log, prisma);
|
|
||||||
const discogs = discogsService(log, pg);
|
|
||||||
const api = createApi(log, pg, discogs);
|
|
||||||
|
|
||||||
await api.listen();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
const log = createLog();
|
|
||||||
|
|
||||||
process.on('SIGTERM', () => {
|
|
||||||
log.debug('SIGTERM received, exiting');
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const r = await start(log)
|
|
||||||
// log.info('done syncing', { totalListings: r.flat().length });
|
|
||||||
log.info(r);
|
|
||||||
setTimeout(run, 60 * 1000);
|
|
||||||
} catch (e) {
|
|
||||||
if (e.message === 'rate limited') {
|
|
||||||
log.info('rate limited, trying again in 60s');
|
|
||||||
return setTimeout(run, 60 * 1000);
|
|
||||||
}
|
|
||||||
log.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run();
|
|
||||||
Reference in new issue
Block a user