restructure directories

* set log level based on env
This commit is contained in:
Philipinho
2024-06-09 15:57:52 +01:00
parent 2e61fb7c11
commit d4eefa48a8
49 changed files with 124 additions and 82 deletions

View File

@ -0,0 +1,8 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const AuthUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user.user;
},
);

View File

@ -0,0 +1,8 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const AuthWorkspace = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user.workspace;
},
);

View File

@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

View File

@ -0,0 +1,35 @@
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
import { Reflector } from '@nestjs/core';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
handleRequest(err: any, user: any, info: any) {
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}

View File

@ -0,0 +1,2 @@
export const APP_DATA_PATH = 'data';
export const LOCAL_STORAGE_PATH = `${APP_DATA_PATH}/storage`;

View File

@ -0,0 +1,7 @@
import * as mime from 'mime-types';
import * as path from 'node:path';
export function getMimeType(filePath: string): string {
const ext = path.extname(filePath);
return mime.contentType(ext) || 'application/octet-stream';
}

View File

@ -0,0 +1,4 @@
export * from './utils';
export * from './nanoid.utils';
export * from './file.helper';
export * from './constants';

View File

@ -0,0 +1,9 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { customAlphabet } = require('fix-esm').require('nanoid');
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
export const nanoIdGen = customAlphabet(alphabet, 10);
const slugIdAlphabet =
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
export const generateSlugId = customAlphabet(slugIdAlphabet, 12);

View File

@ -0,0 +1,16 @@
export enum UserRole {
OWNER = 'owner',
ADMIN = 'admin', // can have owner permissions but cannot delete workspace
MEMBER = 'member',
}
export enum SpaceRole {
ADMIN = 'admin', // can manage space settings, members, and delete space
WRITER = 'writer', // can read and write pages in space
READER = 'reader', // can only read pages in space
}
export enum SpaceVisibility {
OPEN = 'open', // any workspace member can see that it exists and join.
PRIVATE = 'private', // only added space users can see
}

View File

@ -0,0 +1,36 @@
import * as path from 'path';
import * as bcrypt from 'bcrypt';
export const envPath = path.resolve(process.cwd(), '..', '..', '.env');
export async function hashPassword(password: string) {
const saltRounds = 12;
return bcrypt.hash(password, saltRounds);
}
export async function comparePasswordHash(
plainPassword: string,
passwordHash: string,
): Promise<boolean> {
return bcrypt.compare(plainPassword, passwordHash);
}
export type RedisConfig = {
host: string;
port: number;
password?: string;
};
export function parseRedisUrl(redisUrl: string): RedisConfig {
// format - redis[s]://[[username][:password]@][host][:port][/db-number]
const { hostname, port, password } = new URL(redisUrl);
const portInt = parseInt(port, 10);
return { host: hostname, port: portInt, password };
}
export function createRetryStrategy() {
return function (times: number): number {
return Math.max(Math.min(Math.exp(times), 20000), 3000);
};
}

View File

@ -0,0 +1,27 @@
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { map, Observable } from 'rxjs';
export interface Response<T> {
data: T;
}
@Injectable()
export class TransformHttpResponseInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler<T>,
): Observable<Response<T>> {
return next.handle().pipe(
map((data) => {
const status = context.switchToHttp().getResponse().statusCode;
return { data, success: true, status };
}),
);
}
}

View File

@ -0,0 +1,58 @@
import { ConsoleLogger } from '@nestjs/common';
export class InternalLogFilter extends ConsoleLogger {
static contextsToIgnore = [
'InstanceLoader',
'RoutesResolver',
'RouterExplorer',
'WebSocketsController',
];
private allowedLogLevels: string[];
constructor() {
super();
this.allowedLogLevels =
process.env.NODE_ENV === 'production'
? ['log', 'error', 'fatal']
: ['log', 'debug', 'verbose', 'warn', 'error', 'fatal'];
}
private isLogLevelAllowed(level: string): boolean {
return this.allowedLogLevels.includes(level);
}
log(_: any, context?: string): void {
if (
this.isLogLevelAllowed('log') &&
(process.env.NODE_ENV !== 'production' ||
!InternalLogFilter.contextsToIgnore.includes(context))
) {
super.log.apply(this, arguments);
}
}
warn(_: any, context?: string): void {
if (this.isLogLevelAllowed('warn')) {
super.warn.apply(this, arguments);
}
}
error(_: any, stack?: string, context?: string): void {
if (this.isLogLevelAllowed('error')) {
super.error.apply(this, arguments);
}
}
debug(_: any, context?: string): void {
if (this.isLogLevelAllowed('debug')) {
super.debug.apply(this, arguments);
}
}
verbose(_: any, context?: string): void {
if (this.isLogLevelAllowed('verbose')) {
super.verbose.apply(this, arguments);
}
}
}

View File

@ -0,0 +1,41 @@
import { Injectable, NestMiddleware, NotFoundException } from '@nestjs/common';
import { FastifyRequest, FastifyReply } from 'fastify';
import { EnvironmentService } from '../../integrations/environment/environment.service';
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
@Injectable()
export class DomainMiddleware implements NestMiddleware {
constructor(
private workspaceRepo: WorkspaceRepo,
private environmentService: EnvironmentService,
) {}
async use(
req: FastifyRequest['raw'],
res: FastifyReply['raw'],
next: () => void,
) {
if (this.environmentService.isSelfHosted()) {
const workspace = await this.workspaceRepo.findFirst();
if (!workspace) {
//throw new NotFoundException('Workspace not found');
(req as any).workspaceId = null;
return next();
}
(req as any).workspaceId = workspace.id;
} else if (this.environmentService.isCloud()) {
const header = req.headers.host;
const subdomain = header.split('.')[0];
const workspace = await this.workspaceRepo.findByHostname(subdomain);
if (!workspace) {
throw new NotFoundException('Workspace not found');
}
(req as any).workspaceId = workspace.id;
}
next();
}
}