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
|
||||
|
||||
type UUID = string;
|
||||
type Email = string;
|
||||
|
||||
type DiscogsName = string;
|
||||
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": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "mocha",
|
||||
"test:watch": "mocha --watch",
|
||||
"migrate": "knex migrate:latest",
|
||||
"migrate:make": "prisma migrate dev --name",
|
||||
"start": "ts-node-dev --respawn --transpile-only index.ts | bunyan -o short"
|
||||
@@ -12,9 +13,13 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bluebird": "^3.5.36",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@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",
|
||||
"prisma": "^4.1.0",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
@@ -24,11 +29,20 @@
|
||||
"@koa/cors": "^3.3.0",
|
||||
"@koa/router": "^12.0.0",
|
||||
"@prisma/client": "^4.1.0",
|
||||
"axios": "^0.27.2",
|
||||
"bcrypt": "^5.0.1",
|
||||
"bluebird": "^3.7.2",
|
||||
"bunyan": "^1.8.15",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"koa": "^2.13.4",
|
||||
"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 {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
name String @default("unknown")
|
||||
email String @unique
|
||||
password String
|
||||
discogs String? @unique
|
||||
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 bodyParser from 'koa-body';
|
||||
import session from 'koa-session';
|
||||
import koaRouter from '@koa/router'
|
||||
import cors from '@koa/cors';
|
||||
import http from 'http';
|
||||
|
||||
import { groupBy } from 'lodash';
|
||||
import Bluebird from 'bluebird';
|
||||
|
||||
import type Log from 'bunyan';
|
||||
import type { Listing, User, Want, Prisma } from '@prisma/client';
|
||||
|
||||
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) {
|
||||
const PORT = 41337;
|
||||
const SECRET = 'ohja4Eif9eidah3aj5ta2haa4iemooth';
|
||||
|
||||
const app = new koa();
|
||||
const server = http.createServer(app.callback());
|
||||
|
||||
app.use(async (ctx, next) => {
|
||||
try {
|
||||
await next();
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
ctx.status = err.statusCode || err.status || 500;
|
||||
ctx.body = {
|
||||
message: err.message
|
||||
};
|
||||
}
|
||||
});
|
||||
// app.use(async (ctx, next) => {
|
||||
// try {
|
||||
// await next();
|
||||
// } catch (err) {
|
||||
// log.error(err);
|
||||
// ctx.status = err.statusCode || err.status || 500;
|
||||
// ctx.body = {
|
||||
// message: err.message
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
|
||||
const router = koaRouter();
|
||||
|
||||
@@ -65,26 +71,23 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
||||
mentionsWorldwide: shipping.toLowerCase().includes('world'),
|
||||
}
|
||||
|
||||
log.info(bundle);
|
||||
return bundle;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function populateUserListings(user: User) {
|
||||
log.info(user);
|
||||
|
||||
const wants = await discogs.wantlist(user);
|
||||
|
||||
// const latest = await discogs.wantlistLatest(user);
|
||||
// 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 => {
|
||||
const listings = await discogs.listings(want);
|
||||
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;
|
||||
});
|
||||
}
|
||||
@@ -95,19 +98,34 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
||||
reject(err);
|
||||
})
|
||||
|
||||
app.listen(PORT, () => {
|
||||
server.listen(PORT, () => {
|
||||
log.info('listening', { PORT });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.use(cors());
|
||||
app.use(cors({ origin: false, credentials: true }));
|
||||
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 search = await discogs.searchMaster('nine inch nails', 'the fragile');
|
||||
log.info(search);
|
||||
ctx.response.body = bundles;
|
||||
})
|
||||
|
||||
@@ -127,12 +145,6 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
||||
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) {
|
||||
const { userId } = ctx.request.body;
|
||||
const user = await pg.userFindId(userId);
|
||||
@@ -158,6 +170,7 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
||||
|
||||
return {
|
||||
app,
|
||||
server,
|
||||
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;
|
||||
}
|
||||
|
||||
async function user(discogs: string) {
|
||||
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 user(discogs: string) {
|
||||
// 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);
|
||||
@@ -106,7 +106,7 @@ export default function discogs(log: Log, pg: Pg) {
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
// user,
|
||||
wantlist,
|
||||
wantlistLatest,
|
||||
inventory,
|
||||
File renamed without changes.
@@ -1,11 +1,19 @@
|
||||
import bcrypt from 'bcrypt';
|
||||
import type Log from 'bunyan';
|
||||
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 {
|
||||
userFindId: (id: UUID) => Promise<User>,
|
||||
userFindName: (name: DiscogsName) => Promise<User>,
|
||||
userUpsert: (discogs: DiscogsName | null, name: string, data: any) => Promise<User>,
|
||||
userFindEmail: (email: Email) => Promise<User>,
|
||||
userCreate: (params: UserCreateParams) => Promise<User>,
|
||||
userWants: (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 }});
|
||||
}
|
||||
|
||||
async function userFindName(name: DiscogsName) {
|
||||
return prisma.user.findUnique({ where: { discogs: name }});
|
||||
async function userFindEmail(email: Email) {
|
||||
return prisma.user.findUnique({ where: { email }});
|
||||
}
|
||||
|
||||
async function userWants(user: User) {
|
||||
return prisma.want.findMany({ where: { userId: user.id }});
|
||||
}
|
||||
|
||||
async function userUpsert(discogs: DiscogsName | null, name: string, data: any) {
|
||||
const insert = {
|
||||
discogs,
|
||||
name,
|
||||
data,
|
||||
async function userCreate(params: UserCreateParams) {
|
||||
const hashRounds = 10;
|
||||
const password = await bcrypt.hash(params.password, hashRounds);
|
||||
|
||||
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({
|
||||
data: { name },
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -139,8 +143,8 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
|
||||
|
||||
return {
|
||||
userFindId,
|
||||
userFindName,
|
||||
userUpsert,
|
||||
userFindEmail,
|
||||
userCreate,
|
||||
userWants,
|
||||
|
||||
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. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"types": [
|
||||
"./index.d.ts"
|
||||
"./index.d.ts",
|
||||
"mocha"
|
||||
], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
@@ -101,5 +102,10 @@
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
},
|
||||
|
||||
"ts-node": {
|
||||
// strict mode gtfo
|
||||
"transpileOnly": true,
|
||||
},
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "preact build",
|
||||
"start": "preact watch",
|
||||
"start": "preact --no-clear watch",
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
||||
+16
-8
@@ -1,17 +1,25 @@
|
||||
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 () => {
|
||||
const { data: bundles } = await axios.get('http://localhost:41337/bundles');
|
||||
return { bundles };
|
||||
const { data: bundles } = await axios.get(`${API_URL}/bundles`);
|
||||
return bundles;
|
||||
},
|
||||
|
||||
getUserOffers: async (state, userId) => {
|
||||
store.setState({ offers: [] });
|
||||
const { data: offers } = await axios.get(`http://localhost:41337/user/${userId}/offers`);
|
||||
console.log(userId, offers);
|
||||
return { offers };
|
||||
const { data: offers } = await axios.get(`${API_URL}/user/${userId}/offers`);
|
||||
return offers;
|
||||
},
|
||||
});
|
||||
|
||||
getOffers: async (userId) => {
|
||||
const { data } = await axios.get(`${API_URL}/user/${userId}/offers`);
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
export default actions;
|
||||
@@ -8,6 +8,8 @@ import actions from './actions';
|
||||
import Header from './components/header';
|
||||
|
||||
import Home from './routes/home';
|
||||
import Login from './routes/login';
|
||||
import Register from './routes/register';
|
||||
import Search from './routes/search';
|
||||
import User from './routes/user';
|
||||
|
||||
@@ -21,6 +23,8 @@ const App = () => (
|
||||
<Header />
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/register" component={Register} />
|
||||
<Route path="/search" component={Search} />
|
||||
<Route path="/user/:userId" component={User} />
|
||||
</Router>
|
||||
|
||||
+24
-23
@@ -1,30 +1,31 @@
|
||||
import { h } from 'preact';
|
||||
import { connect } from 'unistore/preact'
|
||||
import { useState, useEffect } from 'preact/hooks';
|
||||
|
||||
import actions from './../actions';
|
||||
import Bundle from './../components/bundle';
|
||||
|
||||
interface Props {
|
||||
bundles: any[];
|
||||
getBundles: () => void,
|
||||
|
||||
function Home() {
|
||||
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) {
|
||||
if (!bundles.length) {
|
||||
getBundles();
|
||||
|
||||
return (
|
||||
<div class={'home'}>
|
||||
loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={'home'}>
|
||||
{bundles.map(Bundle)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect('bundles', actions)(Home);
|
||||
export default 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 { 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 Bundle from './../components/bundle';
|
||||
|
||||
@@ -14,31 +12,26 @@ interface Props {
|
||||
}
|
||||
|
||||
const User = ({ userId }: Props) => {
|
||||
const [offers, setOffers] = useState([]);
|
||||
const [offers, setOffers] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchOffers() {
|
||||
const { data } = await axios.get(`http://localhost:41337/user/${userId}/offers`);
|
||||
setOffers(data);
|
||||
}
|
||||
|
||||
fetchOffers();
|
||||
useEffect(() => {
|
||||
actions.getOffers(userId).then(setOffers);
|
||||
}, [userId]);
|
||||
|
||||
|
||||
if (!offers.length) {
|
||||
return (
|
||||
<div class={'home'}>
|
||||
loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!offers.length) {
|
||||
return (
|
||||
<div class={'home'}>
|
||||
loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={'home'}>
|
||||
{offers.map(Bundle)}
|
||||
</div>
|
||||
);
|
||||
<div class={'home'}>
|
||||
{offers.map(Bundle)}
|
||||
</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