feat(auth/user): Finished User Rework to handle Guest
- Split userinfo APIs to their own service (`user`) - Added user service to nginx and docker-compose - Cleaned up package.json across the project to remove useless depedencies - Added word list for Guest username generation (source in file itself) - Reworked internal of `user` DB to not have a difference between "raw" id and normal ID (UUID)
This commit is contained in:
parent
7d0f5c11d6
commit
1cbd778131
24 changed files with 4273 additions and 46 deletions
|
|
@ -64,6 +64,26 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA
|
- JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA
|
||||||
- DATABASE_DIR=/volumes/database
|
- DATABASE_DIR=/volumes/database
|
||||||
|
|
||||||
|
###############
|
||||||
|
# USER #
|
||||||
|
###############
|
||||||
|
user:
|
||||||
|
build:
|
||||||
|
context: ./src/
|
||||||
|
args:
|
||||||
|
- SERVICE=user
|
||||||
|
# - EXTRA_FILES=user/extra
|
||||||
|
container_name: user
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- transcendance-network
|
||||||
|
volumes:
|
||||||
|
- sqlite-volume:/volumes/database
|
||||||
|
- static-volume:/volumes/static
|
||||||
|
environment:
|
||||||
|
- JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA
|
||||||
|
- DATABASE_DIR=/volumes/database
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
||||||
4
nginx/conf/locations/user.conf
Normal file
4
nginx/conf/locations/user.conf
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#forward the post request to the microservice
|
||||||
|
location /api/user/ {
|
||||||
|
proxy_pass http://user$uri;
|
||||||
|
}
|
||||||
|
|
@ -100,7 +100,7 @@ export const authPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
||||||
JSON.stringify(makeResponse('notLoggedIn', 'auth.invalidKind')),
|
JSON.stringify(makeResponse('notLoggedIn', 'auth.invalidKind')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const user = this.db.getUserFromName(tok.who);
|
const user = this.db.getUser(tok.who);
|
||||||
if (isNullish(user)) {
|
if (isNullish(user)) {
|
||||||
return res
|
return res
|
||||||
.clearCookie('token')
|
.clearCookie('token')
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@ import * as bcrypt from 'bcrypt';
|
||||||
export interface IUserDb extends Database {
|
export interface IUserDb extends Database {
|
||||||
getUser(id: UserId): User | undefined,
|
getUser(id: UserId): User | undefined,
|
||||||
getUserFromName(name: string): User | undefined,
|
getUserFromName(name: string): User | undefined,
|
||||||
getUserFromRawId(id: number): User | undefined,
|
getUser(id: string): User | undefined,
|
||||||
getUserOtpSecret(id: UserId): string | undefined,
|
getUserOtpSecret(id: UserId): string | undefined,
|
||||||
|
createUser(name: string, password: string | undefined, guest: boolean): Promise<User | undefined>,
|
||||||
createUser(name: string, password: string | undefined): Promise<User | undefined>,
|
createUser(name: string, password: string | undefined): Promise<User | undefined>,
|
||||||
setUserPassword(id: UserId, password: string | undefined): Promise<User | undefined>,
|
setUserPassword(id: UserId, password: string | undefined): Promise<User | undefined>,
|
||||||
ensureUserOtpSecret(id: UserId): string | undefined,
|
ensureUserOtpSecret(id: UserId): string | undefined,
|
||||||
|
|
@ -17,17 +18,6 @@ export interface IUserDb extends Database {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserImpl: Omit<IUserDb, keyof Database> = {
|
export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
/**
|
|
||||||
* Get a user from an [UserId]
|
|
||||||
*
|
|
||||||
* @param id the userid to fetch
|
|
||||||
*
|
|
||||||
* @returns The user if it exists, undefined otherwise
|
|
||||||
*/
|
|
||||||
getUser(this: IUserDb, id: UserId): User | undefined {
|
|
||||||
return this.getUserFromRawId(id);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a user from a username [string]
|
* Get a user from a username [string]
|
||||||
*
|
*
|
||||||
|
|
@ -50,7 +40,7 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
*
|
*
|
||||||
* @returns The user if it exists, undefined otherwise
|
* @returns The user if it exists, undefined otherwise
|
||||||
*/
|
*/
|
||||||
getUserFromRawId(this: IUserDb, id: number): User | undefined {
|
getUser(this: IUserDb, id: string): User | undefined {
|
||||||
return userFromRow(
|
return userFromRow(
|
||||||
this.prepare('SELECT * FROM user WHERE id = @id LIMIT 1').get({
|
this.prepare('SELECT * FROM user WHERE id = @id LIMIT 1').get({
|
||||||
id,
|
id,
|
||||||
|
|
@ -113,7 +103,7 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserId = number & { readonly __brand: unique symbol };
|
export type UserId = UUID;
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
readonly id: UserId;
|
readonly id: UserId;
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ bOtpDisable.addEventListener('click', async () => {
|
||||||
bWhoami.addEventListener('click', async () => {
|
bWhoami.addEventListener('click', async () => {
|
||||||
let username = '';
|
let username = '';
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/auth/whoami');
|
const res = await fetch('/api/user/info/me');
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
setResponse(json);
|
setResponse(json);
|
||||||
if (json?.kind === 'success') {username = json?.payload?.name;}
|
if (json?.kind === 'success') {username = json?.payload?.name;}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@
|
||||||
"@sinclair/typebox": "^0.34.40",
|
"@sinclair/typebox": "^0.34.40",
|
||||||
"fastify": "^5.0.0",
|
"fastify": "^5.0.0",
|
||||||
"fastify-cli": "^7.4.0",
|
"fastify-cli": "^7.4.0",
|
||||||
"fastify-plugin": "^5.0.0",
|
"fastify-plugin": "^5.0.0"
|
||||||
"sharp": "^0.34.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.1.0",
|
"@types/node": "^22.1.0",
|
||||||
|
|
|
||||||
1011
src/auth/src/plugins/files/adjectives.txt
Normal file
1011
src/auth/src/plugins/files/adjectives.txt
Normal file
File diff suppressed because it is too large
Load diff
2877
src/auth/src/plugins/files/nouns.txt
Normal file
2877
src/auth/src/plugins/files/nouns.txt
Normal file
File diff suppressed because it is too large
Load diff
40
src/auth/src/plugins/words.ts
Normal file
40
src/auth/src/plugins/words.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Why does this file exists ?
|
||||||
|
// We want to make random-ish username for the guest, but still reconizable usernames
|
||||||
|
// So we do `${adjective}_${nouns}`
|
||||||
|
// there is around 30k combinaison, so we should be fine :)
|
||||||
|
|
||||||
|
import fp from 'fastify-plugin';
|
||||||
|
|
||||||
|
// @ts-expect-error: Ts can't load raw txt files - vite does it
|
||||||
|
import _adjectives from './files/adjectives.txt?raw';
|
||||||
|
// @ts-expect-error: Ts can't load raw txt files - vite does it
|
||||||
|
import _nouns from './files/nouns.txt?raw';
|
||||||
|
|
||||||
|
|
||||||
|
type WordsCategory = 'adjectives' | 'nouns';
|
||||||
|
type Words = { [k in WordsCategory]: string[] };
|
||||||
|
|
||||||
|
function toTitleCase(str: string) {
|
||||||
|
return str.replace(
|
||||||
|
/\w\S*/g,
|
||||||
|
text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// strong typing those import :)
|
||||||
|
const RAW_WORDS: { [k in WordsCategory]: string } = { adjectives: _adjectives, nouns: _nouns };
|
||||||
|
const WORDS: Words = Object.fromEntries(Object.entries(RAW_WORDS).map(([k, v]) => {
|
||||||
|
const words = v.split('\n').map(s => s.trim()).filter(s => !(s.startsWith('#') || s.length === 0)).map(toTitleCase);
|
||||||
|
return [k, words];
|
||||||
|
})) as Words;
|
||||||
|
|
||||||
|
export default fp<object>(async (fastify) => {
|
||||||
|
fastify.decorate('words', WORDS);
|
||||||
|
});
|
||||||
|
|
||||||
|
// When using .decorate you have to specify added properties for Typescript
|
||||||
|
declare module 'fastify' {
|
||||||
|
export interface FastifyInstance {
|
||||||
|
words: Words;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/auth/src/routes/guestLogin.ts
Normal file
54
src/auth/src/routes/guestLogin.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
|
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
import { typeResponse, makeResponse, isNullish } from '@shared/utils';
|
||||||
|
|
||||||
|
export const GuestLoginRes = Type.Union([
|
||||||
|
typeResponse('failed', 'login.failed.generic'),
|
||||||
|
typeResponse('success', 'login.success', {
|
||||||
|
token: Type.String({
|
||||||
|
description: 'JWT that represent a logged in user',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type GuestLoginRes = Static<typeof GuestLoginRes>;
|
||||||
|
|
||||||
|
const getRandomFromList = (list: string[]): string => {
|
||||||
|
return list[Math.floor(Math.random() * list.length)];
|
||||||
|
};
|
||||||
|
|
||||||
|
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||||
|
void _opts;
|
||||||
|
fastify.post(
|
||||||
|
'/api/auth/guest',
|
||||||
|
{ schema: { response: { '2xx': GuestLoginRes } } },
|
||||||
|
async function(req, res) {
|
||||||
|
void req;
|
||||||
|
void res;
|
||||||
|
try {
|
||||||
|
const adjective = getRandomFromList(fastify.words.adjectives);
|
||||||
|
const noun = getRandomFromList(fastify.words.nouns);
|
||||||
|
|
||||||
|
const user = await this.db.createUser(
|
||||||
|
`${adjective} ${noun}`,
|
||||||
|
// no password
|
||||||
|
undefined,
|
||||||
|
// is a guest
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
if (isNullish(user)) {
|
||||||
|
return makeResponse('failed', 'login.failed.generic');
|
||||||
|
}
|
||||||
|
return makeResponse('success', 'login.success', {
|
||||||
|
token: this.signJwt('auth', user.id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return makeResponse('failed', 'login.failed.generic');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default route;
|
||||||
|
|
@ -44,7 +44,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the Otp sercret from the db
|
// get the Otp sercret from the db
|
||||||
const user = this.db.getUserFromName(dJwt.who);
|
const user = this.db.getUser(dJwt.who);
|
||||||
if (isNullish(user?.otp)) {
|
if (isNullish(user?.otp)) {
|
||||||
// oops, either no user, or user without otpSecret
|
// oops, either no user, or user without otpSecret
|
||||||
// fuck off
|
// fuck off
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import { FastifyPluginAsync } from 'fastify';
|
|
||||||
|
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
|
||||||
import { isNullish, makeResponse, typeResponse } from '@shared/utils';
|
|
||||||
|
|
||||||
|
|
||||||
export const WhoAmIRes = Type.Union([
|
|
||||||
typeResponse('success', 'whoami.success', { name: Type.String() }),
|
|
||||||
typeResponse('failure', 'whoami.failure.generic'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export type WhoAmIRes = Static<typeof WhoAmIRes>;
|
|
||||||
|
|
||||||
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
|
||||||
void _opts;
|
|
||||||
fastify.get(
|
|
||||||
'/api/auth/whoami',
|
|
||||||
{ schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } },
|
|
||||||
async function(req, _res) {
|
|
||||||
void _res;
|
|
||||||
if (isNullish(req.authUser)) {return makeResponse('failure', 'whoami.failure.generic');}
|
|
||||||
return makeResponse('success', 'whoami.success', { name: req.authUser.name });
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default route;
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"./@shared",
|
"./@shared",
|
||||||
"./icons",
|
"./icons",
|
||||||
|
"./user",
|
||||||
"./auth"
|
"./auth"
|
||||||
],
|
],
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|
|
||||||
2
src/user/.dockerignore
Normal file
2
src/user/.dockerignore
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
8
src/user/entrypoint.sh
Normal file
8
src/user/entrypoint.sh
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
# do anything here
|
||||||
|
|
||||||
|
# run the CMD [ ... ] from the dockerfile
|
||||||
|
exec "$@"
|
||||||
0
src/user/extra/.gitkeep
Normal file
0
src/user/extra/.gitkeep
Normal file
36
src/user/package.json
Normal file
36
src/user/package.json
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"type": "module",
|
||||||
|
"private": false,
|
||||||
|
"name": "user",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "This project was bootstrapped with Fastify-CLI.",
|
||||||
|
"main": "app.ts",
|
||||||
|
"directories": {
|
||||||
|
"test": "test"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "npm run build && node dist/run.js",
|
||||||
|
"build": "vite build",
|
||||||
|
"build:prod": "vite build --outDir=/dist --minify=true --sourcemap=false"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/autoload": "^6.3.1",
|
||||||
|
"@fastify/formbody": "^8.0.2",
|
||||||
|
"@fastify/multipart": "^9.0.3",
|
||||||
|
"@fastify/sensible": "^6.0.0",
|
||||||
|
"@fastify/static": "^8.2.0",
|
||||||
|
"@sinclair/typebox": "^0.34.40",
|
||||||
|
"fastify": "^5.0.0",
|
||||||
|
"fastify-cli": "^7.4.0",
|
||||||
|
"fastify-plugin": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.1.0",
|
||||||
|
"rollup-plugin-node-externals": "^8.0.1",
|
||||||
|
"vite": "^7.0.6",
|
||||||
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/user/src/app.ts
Normal file
38
src/user/src/app.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
|
import fastifyFormBody from '@fastify/formbody';
|
||||||
|
import fastifyMultipart from '@fastify/multipart';
|
||||||
|
import * as db from '@shared/database';
|
||||||
|
import * as auth from '@shared/auth';
|
||||||
|
|
||||||
|
// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this...
|
||||||
|
const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true });
|
||||||
|
// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this...
|
||||||
|
const routes = import.meta.glob('./routes/**/*.ts', { eager: true });
|
||||||
|
|
||||||
|
// When using .decorate you have to specify added properties for Typescript
|
||||||
|
declare module 'fastify' {
|
||||||
|
export interface FastifyInstance {
|
||||||
|
image_store: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const app: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||||
|
void opts;
|
||||||
|
await fastify.register(db.useDatabase as FastifyPluginAsync, {});
|
||||||
|
await fastify.register(auth.jwtPlugin as FastifyPluginAsync, {});
|
||||||
|
await fastify.register(auth.authPlugin as FastifyPluginAsync, {});
|
||||||
|
|
||||||
|
// Place here your custom code!
|
||||||
|
for (const plugin of Object.values(plugins)) {
|
||||||
|
void fastify.register(plugin as FastifyPluginAsync, {});
|
||||||
|
}
|
||||||
|
for (const route of Object.values(routes)) {
|
||||||
|
void fastify.register(route as FastifyPluginAsync, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void fastify.register(fastifyFormBody, {});
|
||||||
|
void fastify.register(fastifyMultipart, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default app;
|
||||||
|
export { app };
|
||||||
16
src/user/src/plugins/README.md
Normal file
16
src/user/src/plugins/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Plugins Folder
|
||||||
|
|
||||||
|
Plugins define behavior that is common to all the routes in your
|
||||||
|
application. Authentication, caching, templates, and all the other cross
|
||||||
|
cutting concerns should be handled by plugins placed in this folder.
|
||||||
|
|
||||||
|
Files in this folder are typically defined through the
|
||||||
|
[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module,
|
||||||
|
making them non-encapsulated. They can define decorators and set hooks
|
||||||
|
that will then be used in the rest of your application.
|
||||||
|
|
||||||
|
Check out:
|
||||||
|
|
||||||
|
* [The hitchhiker's guide to plugins](https://fastify.dev/docs/latest/Guides/Plugins-Guide/)
|
||||||
|
* [Fastify decorators](https://fastify.dev/docs/latest/Reference/Decorators/).
|
||||||
|
* [Fastify lifecycle](https://fastify.dev/docs/latest/Reference/Lifecycle/).
|
||||||
11
src/user/src/plugins/sensible.ts
Normal file
11
src/user/src/plugins/sensible.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import fp from 'fastify-plugin';
|
||||||
|
import sensible, { FastifySensibleOptions } from '@fastify/sensible';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This plugins adds some utilities to handle http errors
|
||||||
|
*
|
||||||
|
* @see https://github.com/fastify/fastify-sensible
|
||||||
|
*/
|
||||||
|
export default fp<FastifySensibleOptions>(async (fastify) => {
|
||||||
|
fastify.register(sensible);
|
||||||
|
});
|
||||||
56
src/user/src/routes/info.ts
Normal file
56
src/user/src/routes/info.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
|
|
||||||
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
import { isNullish, makeResponse, typeResponse } from '@shared/utils';
|
||||||
|
|
||||||
|
|
||||||
|
export const UserInfoRes = Type.Union([
|
||||||
|
typeResponse('success', 'userinfo.success', { name: Type.String(), id: Type.String(), guest: Type.Boolean() }),
|
||||||
|
typeResponse('failure', ['userinfo.failure.generic', 'userinfo.failure.unknownUser', 'userinfo.failure.notLoggedIn']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type UserInfoRes = Static<typeof UserInfoRes>;
|
||||||
|
|
||||||
|
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||||
|
void _opts;
|
||||||
|
fastify.get<{ Params: { user: string } }>(
|
||||||
|
'/api/user/info/:user',
|
||||||
|
{ schema: { response: { '2xx': UserInfoRes } }, config: { requireAuth: true } },
|
||||||
|
async function(req, _res) {
|
||||||
|
void _res;
|
||||||
|
if (isNullish(req.authUser)) { return makeResponse('failure', 'userinfo.failure.notLoggedIn'); }
|
||||||
|
if (isNullish(req.params.user) || req.params.user.length === 0) {
|
||||||
|
return makeResponse('failure', 'userinfo.failure.unknownUser');
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the param is the special value `me`, then just get the id from the currently auth'ed user
|
||||||
|
if (req.params.user === 'me') {
|
||||||
|
req.params.user = req.authUser.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = this.db.getUser(req.params.user);
|
||||||
|
if (isNullish(user)) {
|
||||||
|
return makeResponse('failure', 'userinfo.failure.unknownUser');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: user.name,
|
||||||
|
id: user.id,
|
||||||
|
// the !! converts a value from <something> to either `true` or `false`
|
||||||
|
// it uses the same convention from using <something> in a if, meaning that
|
||||||
|
// ```
|
||||||
|
// let val;
|
||||||
|
// if (something) { val = true; }
|
||||||
|
// else { val = false; }
|
||||||
|
// ```
|
||||||
|
// is the same as `val = !!something`
|
||||||
|
guest: !!user.guest,
|
||||||
|
};
|
||||||
|
|
||||||
|
return makeResponse('success', 'userinfo.success', payload);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default route;
|
||||||
35
src/user/src/run.ts
Normal file
35
src/user/src/run.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
// this sould only be used by the docker file !
|
||||||
|
|
||||||
|
import fastify, { FastifyInstance } from 'fastify';
|
||||||
|
import app from './app';
|
||||||
|
|
||||||
|
const start = async () => {
|
||||||
|
const envToLogger = {
|
||||||
|
development: {
|
||||||
|
transport: {
|
||||||
|
target: 'pino-pretty',
|
||||||
|
options: {
|
||||||
|
translateTime: 'HH:MM:ss Z',
|
||||||
|
ignore: 'pid,hostname',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
production: true,
|
||||||
|
test: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const f: FastifyInstance = fastify({ logger: envToLogger.development });
|
||||||
|
process.on('SIGTERM', () => {
|
||||||
|
f.log.info('Requested to shutdown');
|
||||||
|
process.exit(134);
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await f.register(app, {});
|
||||||
|
await f.listen({ port: 80, host: '0.0.0.0' });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
f.log.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
start();
|
||||||
5
src/user/tsconfig.json
Normal file
5
src/user/tsconfig.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.base.json",
|
||||||
|
"compilerOptions": {},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
51
src/user/vite.config.js
Normal file
51
src/user/vite.config.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||||
|
import nodeExternals from 'rollup-plugin-node-externals';
|
||||||
|
import path from 'node:path';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
function collectDeps(...pkgJsonPaths) {
|
||||||
|
const allDeps = new Set();
|
||||||
|
for (const pkgPath of pkgJsonPaths) {
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
||||||
|
for (const dep of Object.keys(pkg.dependencies || {})) {
|
||||||
|
allDeps.add(dep);
|
||||||
|
}
|
||||||
|
for (const peer of Object.keys(pkg.peerDependencies || {})) {
|
||||||
|
allDeps.add(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(allDeps);
|
||||||
|
}
|
||||||
|
|
||||||
|
const externals = collectDeps(
|
||||||
|
'./package.json',
|
||||||
|
'../@shared/package.json',
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
root: __dirname,
|
||||||
|
// service root
|
||||||
|
plugins: [tsconfigPaths(), nodeExternals()],
|
||||||
|
build: {
|
||||||
|
ssr: true,
|
||||||
|
outDir: 'dist',
|
||||||
|
emptyOutDir: true,
|
||||||
|
lib: {
|
||||||
|
entry: path.resolve(__dirname, 'src/run.ts'),
|
||||||
|
// adjust main entry
|
||||||
|
formats: ['cjs'],
|
||||||
|
// CommonJS for Node.js
|
||||||
|
fileName: () => 'index.js',
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: externals,
|
||||||
|
},
|
||||||
|
target: 'node22',
|
||||||
|
// or whatever Node version you use
|
||||||
|
sourcemap: true,
|
||||||
|
minify: false,
|
||||||
|
// for easier debugging
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue