add basic repo setup, with user creation
This commit is contained in:
parent
9166c37aea
commit
94e1f9ebe1
|
@ -0,0 +1 @@
|
|||
config.env
|
|
@ -0,0 +1,35 @@
|
|||
# how to use docker development?
|
||||
|
||||
## How to setup?
|
||||
1. have docker installed
|
||||
2. create a `config.env` in `/.docker/development`. inspire its contents from `example.config.env`
|
||||
|
||||
## how to run?
|
||||
1. while in directory `/.docker/development` run `docker compose up -d`
|
||||
1.1 if running first time in docker, make sure you have no node_modules folder present in `/`.
|
||||
|
||||
## not working? try this:
|
||||
1. while in directory `/.docker/development` run `docker compose down -v`
|
||||
2. remove `node_modules` directory in `/` if it exists
|
||||
3. remove `.env` and `config.json` file in `/` if any of them exist
|
||||
4. while in directory `/.docker/development` run `docker compose up -d --build`
|
||||
|
||||
## how to stop?
|
||||
1. while in directory `/.docker/development` run `docker compose down`
|
||||
> NOTE: if you want also delete all saved data for a full reset, run `docker compose down -v` instead
|
||||
|
||||
## how do I access the terminal for the backend service?
|
||||
make sure the docker services are running, then run `docker attach mw_backend-1`.
|
||||
this will appear to show nothing at first, but all new logs will show up,
|
||||
and anything you type in the terminal now affect the backend service.
|
||||
> Warning: doing CTRL+C will shut down the backend service, it will not kick your terminal back to its original shell.
|
||||
|
||||
## how do I read logs?
|
||||
1. while in directory `/.docker/development` run `docker compose ps`
|
||||
2. note the name of the service you want to see the logs of
|
||||
3. while in directory `/.docker/development` run `docker compose logs <NAME>`. fill in the name of the service without the brackets.
|
||||
|
||||
## Exposed ports
|
||||
- http://localhost:8081 - backend API
|
||||
- http://localhost:8082 - postgres web UI
|
||||
- postgres://localhost:5432 - postgres
|
|
@ -0,0 +1,47 @@
|
|||
version: '3.8'
|
||||
|
||||
name: "mw_backend"
|
||||
|
||||
services:
|
||||
# required services
|
||||
postgres:
|
||||
image: postgres
|
||||
ports:
|
||||
- '5432:5432'
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
volumes:
|
||||
- 'postgres_data:/var/lib/postgresql/data'
|
||||
|
||||
# custom services
|
||||
backend:
|
||||
stdin_open: true
|
||||
tty: true
|
||||
build:
|
||||
dockerfile: ./dev.Dockerfile
|
||||
context: ../../
|
||||
volumes:
|
||||
- '../../:/app'
|
||||
env_file:
|
||||
- './config.env'
|
||||
ports:
|
||||
- '8081:8080'
|
||||
depends_on:
|
||||
- postgres
|
||||
environment:
|
||||
- WAIT_HOSTS=postgres:5432
|
||||
|
||||
# util services
|
||||
pgweb:
|
||||
image: sosedoff/pgweb
|
||||
ports:
|
||||
- "8082:8081"
|
||||
links:
|
||||
- postgres:postgres
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
|
@ -0,0 +1 @@
|
|||
MWB_USE_PRESETS=dev,dockerdev
|
|
@ -4,3 +4,4 @@ config.json
|
|||
dist
|
||||
.git
|
||||
.vscode
|
||||
.docker
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:18-alpine
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
|
||||
# install dependencies
|
||||
|
@ -11,6 +11,6 @@ RUN npm run build
|
|||
|
||||
# start server
|
||||
EXPOSE 80
|
||||
ENV CONF_SERVER__PORT=80
|
||||
ENV MWB_SERVER__PORT=80
|
||||
ENV NODE_ENV=production
|
||||
CMD ["npm", "run", "start"]
|
||||
|
|
|
@ -1,2 +1,11 @@
|
|||
# backend
|
||||
Backend for movie-web
|
||||
|
||||
## Todo list
|
||||
- [ ] endpoint to consume provider metrics
|
||||
- [ ] register endpoint
|
||||
- [ ] remove user endpoint
|
||||
- [ ] ratelimits for all endpoints
|
||||
- [ ] metrics for all http requests
|
||||
- [ ] session CRUD endpoints
|
||||
- [ ] data save endpoints
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
|
||||
# wait script for development
|
||||
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /wait
|
||||
RUN chmod +x /wait
|
||||
|
||||
VOLUME [ "/app" ]
|
||||
CMD npm i && /wait && npm run dev
|
File diff suppressed because it is too large
Load Diff
|
@ -32,6 +32,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^8.3.0",
|
||||
"@mikro-orm/core": "^5.9.0",
|
||||
"@mikro-orm/postgresql": "^5.9.0",
|
||||
"fastify": "^4.21.0",
|
||||
"fastify-type-provider-zod": "^1.1.9",
|
||||
"neat-config": "^2.0.0",
|
||||
|
|
|
@ -9,4 +9,7 @@ export const devFragment: FragmentSchema = {
|
|||
format: 'pretty',
|
||||
debug: true,
|
||||
},
|
||||
postgres: {
|
||||
syncSchema: true,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { FragmentSchema } from '@/config/fragments/types';
|
||||
|
||||
export const dockerFragment: FragmentSchema = {
|
||||
postgres: {
|
||||
connection: 'postgres://postgres:postgres@postgres:5432/postgres',
|
||||
},
|
||||
};
|
|
@ -1,9 +1,11 @@
|
|||
import { devFragment } from '@/config/fragments/dev';
|
||||
import { dockerFragment } from '@/config/fragments/docker';
|
||||
import { configSchema } from '@/config/schema';
|
||||
import { createConfigLoader } from 'neat-config';
|
||||
|
||||
const fragments = {
|
||||
dev: devFragment,
|
||||
dockerdev: dockerFragment,
|
||||
};
|
||||
|
||||
export const conf = createConfigLoader()
|
||||
|
|
|
@ -26,4 +26,16 @@ export const configSchema = z.object({
|
|||
debug: z.coerce.boolean().default(false),
|
||||
})
|
||||
.default({}),
|
||||
postgres: z.object({
|
||||
// connection URL for postgres database
|
||||
connection: z.string(),
|
||||
|
||||
// run all migrations on boot of the application
|
||||
migrateOnBoot: z.coerce.boolean().default(false),
|
||||
|
||||
// try to sync the schema on boot, useful for development
|
||||
// will always keep the database schema in sync with the connected database
|
||||
// it is extremely destructive, do not use it EVER in production
|
||||
syncSchema: z.coerce.boolean().default(false),
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { Entity, PrimaryKey, Property, types } from '@mikro-orm/core';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
@Entity({ tableName: 'users' })
|
||||
export class User {
|
||||
@PrimaryKey({ name: 'id', type: 'uuid' })
|
||||
id: string = randomUUID();
|
||||
|
||||
@Property({ type: 'date' })
|
||||
createdAt: Date = new Date();
|
||||
|
||||
@Property({ type: 'text' })
|
||||
name!: string;
|
||||
|
||||
@Property({ name: 'permissions', type: types.array })
|
||||
roles: string[] = [];
|
||||
}
|
||||
|
||||
export interface UserDTO {
|
||||
id: string;
|
||||
roles: string[];
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export function formatUser(user: User): UserDTO {
|
||||
return {
|
||||
id: user.id,
|
||||
roles: user.roles,
|
||||
createdAt: user.createdAt.toISOString(),
|
||||
};
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { setupFastify } from '@/modules/fastify';
|
||||
import { setupMikroORM } from '@/modules/mikro';
|
||||
import { scopedLogger } from '@/services/logger';
|
||||
|
||||
const log = scopedLogger('mw-backend');
|
||||
|
@ -9,6 +10,7 @@ async function bootstrap(): Promise<void> {
|
|||
});
|
||||
|
||||
await setupFastify();
|
||||
await setupMikroORM();
|
||||
|
||||
log.info(`App setup, ready to accept connections`, {
|
||||
evt: 'success',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { helloRouter } from '@/routes/hello';
|
||||
import { manageAuthRouter } from '@/routes/auth/manage';
|
||||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
export async function setupRoutes(app: FastifyInstance) {
|
||||
app.register(helloRouter);
|
||||
app.register(manageAuthRouter.register);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { conf } from '@/config';
|
||||
import { scopedLogger } from '@/services/logger';
|
||||
import { PostgreSqlDriver } from '@mikro-orm/postgresql';
|
||||
import { MikroORM } from '@mikro-orm/core';
|
||||
import { createORM } from './orm';
|
||||
|
||||
const log = scopedLogger('orm');
|
||||
let orm: MikroORM<PostgreSqlDriver> | null = null;
|
||||
|
||||
export function getORM() {
|
||||
if (!orm) throw new Error('ORM not set');
|
||||
return orm;
|
||||
}
|
||||
|
||||
export async function setupMikroORM() {
|
||||
log.info(`Connecting to postgres`, { evt: 'connecting' });
|
||||
const mikro = await createORM(conf.postgres.connection, (msg) =>
|
||||
log.info(msg),
|
||||
);
|
||||
|
||||
if (conf.postgres.syncSchema) {
|
||||
const generator = mikro.getSchemaGenerator();
|
||||
try {
|
||||
await generator.updateSchema();
|
||||
} catch {
|
||||
try {
|
||||
await generator.clearDatabase();
|
||||
await generator.updateSchema();
|
||||
} catch {
|
||||
await generator.clearDatabase();
|
||||
await generator.dropSchema();
|
||||
await generator.updateSchema();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (conf.postgres.migrateOnBoot) {
|
||||
const migrator = mikro.getMigrator();
|
||||
await migrator.up();
|
||||
}
|
||||
|
||||
orm = mikro;
|
||||
log.info(`Connected to postgres - ORM is setup!`, { evt: 'success' });
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { MikroORM, PostgreSqlDriver } from '@mikro-orm/postgresql';
|
||||
import path from 'path';
|
||||
|
||||
export async function createORM(url: string, log: (msg: string) => void) {
|
||||
return await MikroORM.init<PostgreSqlDriver>({
|
||||
type: 'postgresql',
|
||||
clientUrl: url,
|
||||
entities: ['./models/**/*.js'],
|
||||
entitiesTs: ['./models/**/*.ts'],
|
||||
baseDir: path.join(__dirname, '../../db'),
|
||||
migrations: {
|
||||
pathTs: './migrations/**/*.ts',
|
||||
path: './migrations/**/*.ts',
|
||||
},
|
||||
logger: log,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { User, formatUser } from '@/db/models/User';
|
||||
import { handle } from '@/services/handler';
|
||||
import { makeRouter } from '@/services/router';
|
||||
import { z } from 'zod';
|
||||
|
||||
const registerSchema = z.object({
|
||||
name: z.string().max(500).min(1),
|
||||
device: z.string().max(500).min(1),
|
||||
});
|
||||
|
||||
export const manageAuthRouter = makeRouter((app) => {
|
||||
app.post(
|
||||
'/auth/register',
|
||||
{ schema: { body: registerSchema } },
|
||||
handle(({ em, body }) => {
|
||||
const user = new User();
|
||||
user.name = body.name;
|
||||
em.persistAndFlush(user);
|
||||
|
||||
return {
|
||||
user: formatUser(user),
|
||||
};
|
||||
}),
|
||||
);
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
import { FastifyPluginAsync } from 'fastify';
|
||||
|
||||
export const helloRouter: FastifyPluginAsync = async (app) => {
|
||||
app.get('/ping', (req, res) => {
|
||||
res.send('pong!');
|
||||
});
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
export const roles = {
|
||||
ADMIN: 'ADMIN', // has access to admin endpoints
|
||||
} as const;
|
||||
|
||||
export type Roles = (typeof roles)[keyof typeof roles];
|
|
@ -0,0 +1,5 @@
|
|||
import { Roles } from '@/services/access';
|
||||
|
||||
export function assertHasRole(_role: Roles) {
|
||||
throw new Error('requires role');
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
import { getORM } from '@/modules/mikro';
|
||||
import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import {
|
||||
ContextConfigDefault,
|
||||
FastifyBaseLogger,
|
||||
FastifyReply,
|
||||
FastifyRequest,
|
||||
FastifySchema,
|
||||
RawReplyDefaultExpression,
|
||||
RawRequestDefaultExpression,
|
||||
RawServerBase,
|
||||
RawServerDefault,
|
||||
RouteGenericInterface,
|
||||
RouteHandlerMethod,
|
||||
} from 'fastify';
|
||||
import { ZodTypeProvider } from 'fastify-type-provider-zod';
|
||||
import { ResolveFastifyReplyReturnType } from 'fastify/types/type-provider';
|
||||
|
||||
export type RequestContext<
|
||||
RawServer extends RawServerBase = RawServerDefault,
|
||||
RawRequest extends
|
||||
RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
|
||||
RawReply extends
|
||||
RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>,
|
||||
RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
|
||||
ContextConfig = ContextConfigDefault,
|
||||
SchemaCompiler extends FastifySchema = FastifySchema,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
||||
> = {
|
||||
req: FastifyRequest<
|
||||
RouteGeneric,
|
||||
RawServer,
|
||||
RawRequest,
|
||||
SchemaCompiler,
|
||||
ZodTypeProvider,
|
||||
ContextConfig,
|
||||
Logger
|
||||
>;
|
||||
res: FastifyReply<
|
||||
RawServer,
|
||||
RawRequest,
|
||||
RawReply,
|
||||
RouteGeneric,
|
||||
ContextConfig,
|
||||
SchemaCompiler,
|
||||
ZodTypeProvider
|
||||
>;
|
||||
body: FastifyRequest<
|
||||
RouteGeneric,
|
||||
RawServer,
|
||||
RawRequest,
|
||||
SchemaCompiler,
|
||||
ZodTypeProvider,
|
||||
ContextConfig,
|
||||
Logger
|
||||
>['body'];
|
||||
params: FastifyRequest<
|
||||
RouteGeneric,
|
||||
RawServer,
|
||||
RawRequest,
|
||||
SchemaCompiler,
|
||||
ZodTypeProvider,
|
||||
ContextConfig,
|
||||
Logger
|
||||
>['params'];
|
||||
query: FastifyRequest<
|
||||
RouteGeneric,
|
||||
RawServer,
|
||||
RawRequest,
|
||||
SchemaCompiler,
|
||||
ZodTypeProvider,
|
||||
ContextConfig,
|
||||
Logger
|
||||
>['query'];
|
||||
em: EntityManager;
|
||||
};
|
||||
|
||||
export function handle<
|
||||
RawServer extends RawServerBase = RawServerDefault,
|
||||
RawRequest extends
|
||||
RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
|
||||
RawReply extends
|
||||
RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>,
|
||||
RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
|
||||
ContextConfig = ContextConfigDefault,
|
||||
SchemaCompiler extends FastifySchema = FastifySchema,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
||||
>(
|
||||
handler: (
|
||||
ctx: RequestContext<
|
||||
RawServer,
|
||||
RawRequest,
|
||||
RawReply,
|
||||
RouteGeneric,
|
||||
ContextConfig,
|
||||
SchemaCompiler,
|
||||
Logger
|
||||
>,
|
||||
) => ResolveFastifyReplyReturnType<
|
||||
ZodTypeProvider,
|
||||
SchemaCompiler,
|
||||
RouteGeneric
|
||||
>,
|
||||
): RouteHandlerMethod<
|
||||
RawServer,
|
||||
RawRequest,
|
||||
RawReply,
|
||||
RouteGeneric,
|
||||
ContextConfig,
|
||||
SchemaCompiler,
|
||||
ZodTypeProvider,
|
||||
Logger
|
||||
> {
|
||||
const reqHandler: any = async (req: any, res: any) => {
|
||||
res.send(
|
||||
await handler({
|
||||
req,
|
||||
res,
|
||||
body: req.body,
|
||||
params: req.params,
|
||||
query: req.query,
|
||||
em: getORM().em.fork(),
|
||||
}),
|
||||
);
|
||||
};
|
||||
return reqHandler;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import {
|
||||
FastifyBaseLogger,
|
||||
FastifyInstance,
|
||||
FastifyPluginAsync,
|
||||
RawReplyDefaultExpression,
|
||||
RawRequestDefaultExpression,
|
||||
RawServerBase,
|
||||
} from 'fastify';
|
||||
import { ZodTypeProvider } from 'fastify-type-provider-zod';
|
||||
|
||||
export type Instance = FastifyInstance<
|
||||
RawServerBase,
|
||||
RawRequestDefaultExpression<RawServerBase>,
|
||||
RawReplyDefaultExpression<RawServerBase>,
|
||||
FastifyBaseLogger,
|
||||
ZodTypeProvider
|
||||
>;
|
||||
export type RegisterPlugin = FastifyPluginAsync<
|
||||
Record<never, never>,
|
||||
RawServerBase,
|
||||
ZodTypeProvider
|
||||
>;
|
||||
|
||||
export function makeRouter(cb: (app: Instance) => void): {
|
||||
register: RegisterPlugin;
|
||||
} {
|
||||
return {
|
||||
register: async (app) => {
|
||||
cb(app);
|
||||
},
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue