mirror of
https://github.com/docmost/docmost.git
synced 2025-11-16 10:11:11 +10:00
restructure directories
* set log level based on env
This commit is contained in:
8
apps/server/src/common/decorators/auth-user.decorator.ts
Normal file
8
apps/server/src/common/decorators/auth-user.decorator.ts
Normal 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;
|
||||
},
|
||||
);
|
||||
@ -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;
|
||||
},
|
||||
);
|
||||
4
apps/server/src/common/decorators/public.decorator.ts
Normal file
4
apps/server/src/common/decorators/public.decorator.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const IS_PUBLIC_KEY = 'isPublic';
|
||||
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
||||
35
apps/server/src/common/guards/jwt-auth.guard.ts
Normal file
35
apps/server/src/common/guards/jwt-auth.guard.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
2
apps/server/src/common/helpers/constants.ts
Normal file
2
apps/server/src/common/helpers/constants.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const APP_DATA_PATH = 'data';
|
||||
export const LOCAL_STORAGE_PATH = `${APP_DATA_PATH}/storage`;
|
||||
7
apps/server/src/common/helpers/file.helper.ts
Normal file
7
apps/server/src/common/helpers/file.helper.ts
Normal 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';
|
||||
}
|
||||
4
apps/server/src/common/helpers/index.ts
Normal file
4
apps/server/src/common/helpers/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './utils';
|
||||
export * from './nanoid.utils';
|
||||
export * from './file.helper';
|
||||
export * from './constants';
|
||||
9
apps/server/src/common/helpers/nanoid.utils.ts
Normal file
9
apps/server/src/common/helpers/nanoid.utils.ts
Normal 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);
|
||||
16
apps/server/src/common/helpers/types/permission.ts
Normal file
16
apps/server/src/common/helpers/types/permission.ts
Normal 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
|
||||
}
|
||||
36
apps/server/src/common/helpers/utils.ts
Normal file
36
apps/server/src/common/helpers/utils.ts
Normal 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);
|
||||
};
|
||||
}
|
||||
@ -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 };
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
58
apps/server/src/common/logger/internal-log-filter.ts
Normal file
58
apps/server/src/common/logger/internal-log-filter.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
apps/server/src/common/middlewares/domain.middleware.ts
Normal file
41
apps/server/src/common/middlewares/domain.middleware.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user