change error behaviour

This commit is contained in:
rubikscraft 2022-08-26 20:40:16 +02:00
parent 659a41cd28
commit ab600c20b7
No known key found for this signature in database
GPG key ID: 1463EBE9200A5CD4
16 changed files with 166 additions and 257 deletions

View file

@ -25,7 +25,7 @@ const imageCorsConfig = cors({
maxAge: 30 * 24 * 60 * 60, maxAge: 30 * 24 * 60 * 60,
}); });
const imageCorpOverride = ( const imageCorsOverride = (
req: IncomingMessage, req: IncomingMessage,
res: ServerResponse, res: ServerResponse,
next: Function, next: Function,
@ -54,6 +54,6 @@ const imageCorpOverride = (
export class AppModule implements NestModule { export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {
consumer.apply(mainCorsConfig).exclude('/i').forRoutes('/'); consumer.apply(mainCorsConfig).exclude('/i').forRoutes('/');
consumer.apply(imageCorsConfig, imageCorpOverride).forRoutes('/i'); consumer.apply(imageCorsConfig, imageCorsOverride).forRoutes('/i');
} }
} }

View file

@ -56,7 +56,7 @@ export class PreferenceCommonService {
case 'boolean': case 'boolean':
return { return {
key: preference.key, key: preference.key,
value: preference.value == 'true', value: preference.value === 'true',
type: 'boolean', type: 'boolean',
}; };
} }
@ -100,7 +100,7 @@ export class PreferenceCommonService {
expectedType: PrefValueTypeStrings, expectedType: PrefValueTypeStrings,
): Failable<string> { ): Failable<string> {
const type = typeof value; const type = typeof value;
if (type != expectedType) { if (type !== expectedType) {
return Fail(FT.UsrValidation, 'Invalid preference value'); return Fail(FT.UsrValidation, 'Invalid preference value');
} }

View file

@ -1,6 +1,6 @@
import { FactoryProvider, Injectable, Logger } from '@nestjs/common'; import { FactoryProvider, Injectable, Logger } from '@nestjs/common';
import { JwtModuleOptions, JwtOptionsFactory } from '@nestjs/jwt'; import { JwtModuleOptions, JwtOptionsFactory } from '@nestjs/jwt';
import { HasFailed } from 'picsur-shared/dist/types'; import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { SysPreferenceService } from '../../collections/preference-db/sys-preference-db.service'; import { SysPreferenceService } from '../../collections/preference-db/sys-preference-db.service';
@Injectable() @Injectable()
@ -19,20 +19,18 @@ export class JwtConfigService implements JwtOptionsFactory {
} }
public async getJwtSecret(): Promise<string> { public async getJwtSecret(): Promise<string> {
const secret = await this.prefService.getStringPreference('jwt_secret'); const secret = ThrowIfFailed(
if (HasFailed(secret)) { await this.prefService.getStringPreference('jwt_secret'),
throw new Error('JWT secret could not be retrieved'); );
}
return secret; return secret;
} }
public async getJwtExpiresIn(): Promise<string> { public async getJwtExpiresIn(): Promise<string> {
const expiresIn = await this.prefService.getStringPreference( const expiresIn = ThrowIfFailed(
'jwt_expires_in', await this.prefService.getStringPreference('jwt_expires_in'),
); );
if (HasFailed(expiresIn)) {
throw new Error('JWT expiresIn could not be retrieved');
}
return expiresIn; return expiresIn;
} }

View file

@ -2,8 +2,9 @@ import {
createParamDecorator, createParamDecorator,
ExecutionContext, ExecutionContext,
SetMetadata, SetMetadata,
UseGuards, UseGuards
} from '@nestjs/common'; } from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import { CombineFCDecorators } from 'picsur-shared/dist/util/decorator'; import { CombineFCDecorators } from 'picsur-shared/dist/util/decorator';
import { LocalAuthGuard } from '../managers/auth/guards/local-auth.guard'; import { LocalAuthGuard } from '../managers/auth/guards/local-auth.guard';
import { Permission, Permissions } from '../models/constants/permissions.const'; import { Permission, Permissions } from '../models/constants/permissions.const';
@ -28,7 +29,7 @@ export const HasPermission = createParamDecorator(
const req: AuthFasityRequest = ctx.switchToHttp().getRequest(); const req: AuthFasityRequest = ctx.switchToHttp().getRequest();
const permissions = req.userPermissions; const permissions = req.userPermissions;
if (!permissions) { if (!permissions) {
throw new Error('Permissions are missing from request'); throw Fail(FT.Internal, undefined, 'Permissions are missing from request');
} }
return permissions.includes(data); return permissions.includes(data);
@ -40,7 +41,7 @@ export const GetPermissions = createParamDecorator(
const req: AuthFasityRequest = ctx.switchToHttp().getRequest(); const req: AuthFasityRequest = ctx.switchToHttp().getRequest();
const permissions = req.userPermissions; const permissions = req.userPermissions;
if (!permissions) { if (!permissions) {
throw new Error('Permissions are missing from request'); throw Fail(FT.Internal, undefined, 'Permissions are missing from request');
} }
return permissions; return permissions;

View file

@ -1,4 +1,5 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import AuthFasityRequest from '../models/interfaces/authrequest.dto'; import AuthFasityRequest from '../models/interfaces/authrequest.dto';
export const ReqUser = createParamDecorator( export const ReqUser = createParamDecorator(
@ -12,7 +13,7 @@ export const ReqUserID = createParamDecorator(
(input: any, ctx: ExecutionContext) => { (input: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest() as AuthFasityRequest; const request = ctx.switchToHttp().getRequest() as AuthFasityRequest;
const id = request.user.id; const id = request.user.id;
if (!id) throw new Error('User ID is not set'); if (!id) throw Fail(FT.Internal, undefined, 'User ID is not set');
return id; return id;
}, },
); );

View file

@ -1,7 +1,12 @@
import { ArgumentsHost, Catch, ExceptionFilter, Logger } from '@nestjs/common'; import { ArgumentsHost, Catch, ExceptionFilter, Logger } from '@nestjs/common';
import { FastifyReply, FastifyRequest } from 'fastify'; import { FastifyReply, FastifyRequest } from 'fastify';
import { ApiErrorResponse } from 'picsur-shared/dist/dto/api/api.dto'; import { ApiErrorResponse } from 'picsur-shared/dist/dto/api/api.dto';
import { IsFailure } from 'picsur-shared/dist/types/failable'; import {
Fail,
Failure,
FT,
IsFailure
} from 'picsur-shared/dist/types/failable';
// This will catch any exception that is made in any request // This will catch any exception that is made in any request
// (As long as its within nest, the earlier fastify stages are not handled here) // (As long as its within nest, the earlier fastify stages are not handled here)
@ -11,7 +16,7 @@ import { IsFailure } from 'picsur-shared/dist/types/failable';
export class MainExceptionFilter implements ExceptionFilter { export class MainExceptionFilter implements ExceptionFilter {
private static readonly logger = new Logger('MainExceptionFilter'); private static readonly logger = new Logger('MainExceptionFilter');
catch(exception: unknown, host: ArgumentsHost) { catch(exception: Failure, host: ArgumentsHost) {
const ctx = host.switchToHttp(); const ctx = host.switchToHttp();
const response = ctx.getResponse<FastifyReply>(); const response = ctx.getResponse<FastifyReply>();
const request = ctx.getRequest<FastifyRequest>(); const request = ctx.getRequest<FastifyRequest>();
@ -22,7 +27,7 @@ export class MainExceptionFilter implements ExceptionFilter {
MainExceptionFilter.logger.error( MainExceptionFilter.logger.error(
traceString + ' Unkown exception: ' + exception, traceString + ' Unkown exception: ' + exception,
); );
return; exception = Fail(FT.Internal, 'Unknown exception', exception);
} }
const status = exception.getCode(); const status = exception.getCode();
@ -50,6 +55,7 @@ export class MainExceptionFilter implements ExceptionFilter {
success: false, success: false,
statusCode: status, statusCode: status,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
timeMs: Math.round(response.getResponseTime()),
data: { data: {
type, type,

View file

@ -1,11 +1,13 @@
import { import {
CallHandler, CallHandler,
ExecutionContext, ExecutionContext,
Injectable, Logger, Injectable,
Logger,
NestInterceptor, NestInterceptor,
Optional Optional
} from '@nestjs/common'; } from '@nestjs/common';
import { Reflector } from '@nestjs/core'; import { Reflector } from '@nestjs/core';
import { FastifyReply } from 'fastify';
import { ApiAnySuccessResponse } from 'picsur-shared/dist/dto/api/api.dto'; import { ApiAnySuccessResponse } from 'picsur-shared/dist/dto/api/api.dto';
import { Fail, FT } from 'picsur-shared/dist/types'; import { Fail, FT } from 'picsur-shared/dist/types';
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto'; import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
@ -81,15 +83,17 @@ export class SuccessInterceptor<T> implements NestInterceptor {
context: ExecutionContext, context: ExecutionContext,
data: unknown, data: unknown,
): ApiAnySuccessResponse { ): ApiAnySuccessResponse {
const status = context.switchToHttp().getResponse().statusCode; const response = context.switchToHttp().getResponse<FastifyReply>();
const response = {
const newResponse: ApiAnySuccessResponse = {
success: true as true, // really typescript success: true as true, // really typescript
statusCode: status, statusCode: response.statusCode,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
timeMs: Math.round(response.getResponseTime()),
data, data,
}; };
return response; return newResponse;
} }
} }

View file

@ -1,9 +1,7 @@
import { import {
Body, Body,
Controller, Controller,
Get, Get, Logger,
InternalServerErrorException,
Logger,
Param, Param,
Post Post
} from '@nestjs/common'; } from '@nestjs/common';
@ -13,7 +11,7 @@ import {
UpdatePreferenceRequest, UpdatePreferenceRequest,
UpdatePreferenceResponse UpdatePreferenceResponse
} from 'picsur-shared/dist/dto/api/pref.dto'; } from 'picsur-shared/dist/dto/api/pref.dto';
import { HasFailed } from 'picsur-shared/dist/types'; import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { SysPreferenceService } from '../../../collections/preference-db/sys-preference-db.service'; import { SysPreferenceService } from '../../../collections/preference-db/sys-preference-db.service';
import { RequiredPermissions } from '../../../decorators/permissions.decorator'; import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator'; import { Returns } from '../../../decorators/returns.decorator';
@ -29,11 +27,7 @@ export class SysPrefController {
@Get() @Get()
@Returns(MultiplePreferencesResponse) @Returns(MultiplePreferencesResponse)
async getAllSysPrefs(): Promise<MultiplePreferencesResponse> { async getAllSysPrefs(): Promise<MultiplePreferencesResponse> {
const prefs = await this.prefService.getAllPreferences(); const prefs = ThrowIfFailed(await this.prefService.getAllPreferences());
if (HasFailed(prefs)) {
this.logger.warn(prefs.getReason());
throw new InternalServerErrorException('Could not get preferences');
}
return { return {
results: prefs, results: prefs,
@ -44,11 +38,7 @@ export class SysPrefController {
@Get(':key') @Get(':key')
@Returns(GetPreferenceResponse) @Returns(GetPreferenceResponse)
async getSysPref(@Param('key') key: string): Promise<GetPreferenceResponse> { async getSysPref(@Param('key') key: string): Promise<GetPreferenceResponse> {
const pref = await this.prefService.getPreference(key); const pref = ThrowIfFailed(await this.prefService.getPreference(key));
if (HasFailed(pref)) {
this.logger.warn(pref.getReason());
throw new InternalServerErrorException('Could not get preference');
}
return pref; return pref;
} }
@ -61,11 +51,9 @@ export class SysPrefController {
): Promise<UpdatePreferenceResponse> { ): Promise<UpdatePreferenceResponse> {
const value = body.value; const value = body.value;
const pref = await this.prefService.setPreference(key, value); const pref = ThrowIfFailed(
if (HasFailed(pref)) { await this.prefService.setPreference(key, value),
this.logger.warn(pref.getReason()); );
throw new InternalServerErrorException('Could not set preference');
}
return { return {
key, key,

View file

@ -1,9 +1,7 @@
import { import {
Body, Body,
Controller, Controller,
Get, Get, Logger,
InternalServerErrorException,
Logger,
Param, Param,
Post Post
} from '@nestjs/common'; } from '@nestjs/common';
@ -13,7 +11,7 @@ import {
UpdatePreferenceRequest, UpdatePreferenceRequest,
UpdatePreferenceResponse UpdatePreferenceResponse
} from 'picsur-shared/dist/dto/api/pref.dto'; } from 'picsur-shared/dist/dto/api/pref.dto';
import { HasFailed } from 'picsur-shared/dist/types'; import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { UsrPreferenceService } from '../../../collections/preference-db/usr-preference-db.service'; import { UsrPreferenceService } from '../../../collections/preference-db/usr-preference-db.service';
import { RequiredPermissions } from '../../../decorators/permissions.decorator'; import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { ReqUserID } from '../../../decorators/request-user.decorator'; import { ReqUserID } from '../../../decorators/request-user.decorator';
@ -32,11 +30,9 @@ export class UsrPrefController {
async getAllSysPrefs( async getAllSysPrefs(
@ReqUserID() userid: string, @ReqUserID() userid: string,
): Promise<MultiplePreferencesResponse> { ): Promise<MultiplePreferencesResponse> {
const prefs = await this.prefService.getAllPreferences(userid); const prefs = ThrowIfFailed(
if (HasFailed(prefs)) { await this.prefService.getAllPreferences(userid),
this.logger.warn(prefs.getReason()); );
throw new InternalServerErrorException('Could not get preferences');
}
return { return {
results: prefs, results: prefs,
@ -50,11 +46,9 @@ export class UsrPrefController {
@Param('key') key: string, @Param('key') key: string,
@ReqUserID() userid: string, @ReqUserID() userid: string,
): Promise<GetPreferenceResponse> { ): Promise<GetPreferenceResponse> {
const pref = await this.prefService.getPreference(userid, key); const pref = ThrowIfFailed(
if (HasFailed(pref)) { await this.prefService.getPreference(userid, key),
this.logger.warn(pref.getReason()); );
throw new InternalServerErrorException('Could not get preference');
}
return pref; return pref;
} }
@ -68,11 +62,9 @@ export class UsrPrefController {
): Promise<UpdatePreferenceResponse> { ): Promise<UpdatePreferenceResponse> {
const value = body.value; const value = body.value;
const pref = await this.prefService.setPreference(userid, key, value); const pref = ThrowIfFailed(
if (HasFailed(pref)) { await this.prefService.setPreference(userid, key, value),
this.logger.warn(pref.getReason()); );
throw new InternalServerErrorException('Could not set preference');
}
return { return {
key, key,

View file

@ -1,9 +1,7 @@
import { import {
Body, Body,
Controller, Controller,
Get, Get, Logger,
InternalServerErrorException,
Logger,
Post Post
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
@ -18,7 +16,7 @@ import {
RoleUpdateResponse, RoleUpdateResponse,
SpecialRolesResponse SpecialRolesResponse
} from 'picsur-shared/dist/dto/api/roles.dto'; } from 'picsur-shared/dist/dto/api/roles.dto';
import { HasFailed } from 'picsur-shared/dist/types'; import { Fail, FT, ThrowIfFailed } from 'picsur-shared/dist/types';
import { RolesService } from '../../../collections/role-db/role-db.service'; import { RolesService } from '../../../collections/role-db/role-db.service';
import { UsersService } from '../../../collections/user-db/user-db.service'; import { UsersService } from '../../../collections/user-db/user-db.service';
import { RequiredPermissions } from '../../../decorators/permissions.decorator'; import { RequiredPermissions } from '../../../decorators/permissions.decorator';
@ -45,11 +43,7 @@ export class RolesController {
@Get('list') @Get('list')
@Returns(RoleListResponse) @Returns(RoleListResponse)
async getRoles(): Promise<RoleListResponse> { async getRoles(): Promise<RoleListResponse> {
const roles = await this.rolesService.findAll(); const roles = ThrowIfFailed(await this.rolesService.findAll());
if (HasFailed(roles)) {
this.logger.warn(roles.getReason());
throw new InternalServerErrorException('Could not list roles');
}
return { return {
results: roles, results: roles,
@ -60,11 +54,7 @@ export class RolesController {
@Post('info') @Post('info')
@Returns(RoleInfoResponse) @Returns(RoleInfoResponse)
async getRole(@Body() body: RoleInfoRequest): Promise<RoleInfoResponse> { async getRole(@Body() body: RoleInfoRequest): Promise<RoleInfoResponse> {
const role = await this.rolesService.findOne(body.name); const role = ThrowIfFailed(await this.rolesService.findOne(body.name));
if (HasFailed(role)) {
this.logger.warn(role.getReason());
throw new InternalServerErrorException('Could not find role');
}
return role; return role;
} }
@ -76,17 +66,12 @@ export class RolesController {
): Promise<RoleUpdateResponse> { ): Promise<RoleUpdateResponse> {
const permissions = body.permissions; const permissions = body.permissions;
if (!isPermissionsArray(permissions)) { if (!isPermissionsArray(permissions)) {
throw new InternalServerErrorException('Invalid permissions'); throw Fail(FT.UsrValidation, 'Invalid permissions array');
} }
const updatedRole = await this.rolesService.setPermissions( const updatedRole = ThrowIfFailed(
body.name, await this.rolesService.setPermissions(body.name, permissions),
permissions,
); );
if (HasFailed(updatedRole)) {
this.logger.warn(updatedRole.getReason());
throw new InternalServerErrorException('Could not set role permissions');
}
return updatedRole; return updatedRole;
} }
@ -98,14 +83,12 @@ export class RolesController {
): Promise<RoleCreateResponse> { ): Promise<RoleCreateResponse> {
const permissions = role.permissions; const permissions = role.permissions;
if (!isPermissionsArray(permissions)) { if (!isPermissionsArray(permissions)) {
throw new InternalServerErrorException('Invalid permissions array'); throw Fail(FT.UsrValidation, 'Invalid permissions array');
} }
const newRole = await this.rolesService.create(role.name, permissions); const newRole = ThrowIfFailed(
if (HasFailed(newRole)) { await this.rolesService.create(role.name, permissions),
this.logger.warn(newRole.getReason()); );
throw new InternalServerErrorException('Could not create role');
}
return newRole; return newRole;
} }
@ -115,19 +98,13 @@ export class RolesController {
async deleteRole( async deleteRole(
@Body() role: RoleDeleteRequest, @Body() role: RoleDeleteRequest,
): Promise<RoleDeleteResponse> { ): Promise<RoleDeleteResponse> {
const deletedRole = await this.rolesService.delete(role.name); const deletedRole = ThrowIfFailed(
if (HasFailed(deletedRole)) { await this.rolesService.delete(role.name),
this.logger.warn(deletedRole.getReason()); );
throw new InternalServerErrorException('Could not delete role');
} ThrowIfFailed(
await this.usersService.removeRoleEveryone(role.name),
const success = await this.usersService.removeRoleEveryone(role.name);
if (HasFailed(success)) {
this.logger.warn(success.getReason());
throw new InternalServerErrorException(
'Could not remove role from users',
); );
}
return deletedRole; return deletedRole;
} }

View file

@ -1,9 +1,7 @@
import { import {
Body, Body,
Controller, Controller,
Get, Get, Logger,
InternalServerErrorException,
Logger,
Post Post
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
@ -19,7 +17,7 @@ import {
UserUpdateRequest, UserUpdateRequest,
UserUpdateResponse UserUpdateResponse
} from 'picsur-shared/dist/dto/api/user-manage.dto'; } from 'picsur-shared/dist/dto/api/user-manage.dto';
import { HasFailed } from 'picsur-shared/dist/types'; import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { UsersService } from '../../../collections/user-db/user-db.service'; import { UsersService } from '../../../collections/user-db/user-db.service';
import { RequiredPermissions } from '../../../decorators/permissions.decorator'; import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator'; import { Returns } from '../../../decorators/returns.decorator';
@ -43,11 +41,9 @@ export class UserAdminController {
async listUsersPaged( async listUsersPaged(
@Body() body: UserListRequest, @Body() body: UserListRequest,
): Promise<UserListResponse> { ): Promise<UserListResponse> {
const found = await this.usersService.findMany(body.count, body.page); const found = ThrowIfFailed(
if (HasFailed(found)) { await this.usersService.findMany(body.count, body.page),
this.logger.warn(found.getReason()); );
throw new InternalServerErrorException('Could not list users');
}
found.results = found.results.map(EUserBackend2EUser); found.results = found.results.map(EUserBackend2EUser);
return found; return found;
@ -58,15 +54,13 @@ export class UserAdminController {
async register( async register(
@Body() create: UserCreateRequest, @Body() create: UserCreateRequest,
): Promise<UserCreateResponse> { ): Promise<UserCreateResponse> {
const user = await this.usersService.create( const user = ThrowIfFailed(
await this.usersService.create(
create.username, create.username,
create.password, create.password,
create.roles, create.roles,
),
); );
if (HasFailed(user)) {
this.logger.warn(user.getReason());
throw new InternalServerErrorException('Could not create user');
}
return EUserBackend2EUser(user); return EUserBackend2EUser(user);
} }
@ -74,11 +68,7 @@ export class UserAdminController {
@Post('delete') @Post('delete')
@Returns(UserDeleteResponse) @Returns(UserDeleteResponse)
async delete(@Body() body: UserDeleteRequest): Promise<UserDeleteResponse> { async delete(@Body() body: UserDeleteRequest): Promise<UserDeleteResponse> {
const user = await this.usersService.delete(body.id); const user = ThrowIfFailed(await this.usersService.delete(body.id));
if (HasFailed(user)) {
this.logger.warn(user.getReason());
throw new InternalServerErrorException('Could not delete user');
}
return EUserBackend2EUser(user); return EUserBackend2EUser(user);
} }
@ -86,11 +76,7 @@ export class UserAdminController {
@Post('info') @Post('info')
@Returns(UserInfoResponse) @Returns(UserInfoResponse)
async getUser(@Body() body: UserInfoRequest): Promise<UserInfoResponse> { async getUser(@Body() body: UserInfoRequest): Promise<UserInfoResponse> {
const user = await this.usersService.findOne(body.id); const user = ThrowIfFailed(await this.usersService.findOne(body.id));
if (HasFailed(user)) {
this.logger.warn(user.getReason());
throw new InternalServerErrorException('Could not find user');
}
return EUserBackend2EUser(user); return EUserBackend2EUser(user);
} }
@ -100,26 +86,18 @@ export class UserAdminController {
async setPermissions( async setPermissions(
@Body() body: UserUpdateRequest, @Body() body: UserUpdateRequest,
): Promise<UserUpdateResponse> { ): Promise<UserUpdateResponse> {
let user = await this.usersService.findOne(body.id); let user = ThrowIfFailed(await this.usersService.findOne(body.id));
if (HasFailed(user)) {
this.logger.warn(user.getReason());
throw new InternalServerErrorException('Could not find user');
}
if (body.roles) { if (body.roles) {
user = await this.usersService.setRoles(body.id, body.roles); user = ThrowIfFailed(
if (HasFailed(user)) { await this.usersService.setRoles(body.id, body.roles),
this.logger.warn(user.getReason()); );
throw new InternalServerErrorException('Could not update user');
}
} }
if (body.password) { if (body.password) {
user = await this.usersService.updatePassword(body.id, body.password); user = ThrowIfFailed(
if (HasFailed(user)) { await this.usersService.updatePassword(body.id, body.password),
this.logger.warn(user.getReason()); );
throw new InternalServerErrorException('Could not update user');
}
} }
return EUserBackend2EUser(user); return EUserBackend2EUser(user);

View file

@ -1,9 +1,7 @@
import { import {
Body, Body,
Controller, Controller,
Get, Get, Logger,
InternalServerErrorException,
Logger,
Post Post
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
@ -14,7 +12,7 @@ import {
UserRegisterResponse UserRegisterResponse
} from 'picsur-shared/dist/dto/api/user.dto'; } from 'picsur-shared/dist/dto/api/user.dto';
import type { EUser } from 'picsur-shared/dist/entities/user.entity'; import type { EUser } from 'picsur-shared/dist/entities/user.entity';
import { HasFailed } from 'picsur-shared/dist/types'; import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { UsersService } from '../../../collections/user-db/user-db.service'; import { UsersService } from '../../../collections/user-db/user-db.service';
import { import {
NoPermissions, NoPermissions,
@ -40,11 +38,7 @@ export class UserController {
@Returns(UserLoginResponse) @Returns(UserLoginResponse)
@UseLocalAuth(Permission.UserLogin) @UseLocalAuth(Permission.UserLogin)
async login(@ReqUser() user: EUser): Promise<UserLoginResponse> { async login(@ReqUser() user: EUser): Promise<UserLoginResponse> {
const jwt_token = await this.authService.createToken(user); const jwt_token = ThrowIfFailed(await this.authService.createToken(user));
if (HasFailed(jwt_token)) {
this.logger.warn(jwt_token.getReason());
throw new InternalServerErrorException('Could not get new token');
}
return { jwt_token }; return { jwt_token };
} }
@ -55,14 +49,9 @@ export class UserController {
async register( async register(
@Body() register: UserRegisterRequest, @Body() register: UserRegisterRequest,
): Promise<UserRegisterResponse> { ): Promise<UserRegisterResponse> {
const user = await this.usersService.create( const user = ThrowIfFailed(
register.username, await this.usersService.create(register.username, register.password),
register.password,
); );
if (HasFailed(user)) {
this.logger.warn(user.getReason());
throw new InternalServerErrorException('Could not register user');
}
return EUserBackend2EUser(user); return EUserBackend2EUser(user);
} }
@ -71,20 +60,11 @@ export class UserController {
@Returns(UserMeResponse) @Returns(UserMeResponse)
@RequiredPermissions(Permission.UserKeepLogin) @RequiredPermissions(Permission.UserKeepLogin)
async me(@ReqUserID() userid: string): Promise<UserMeResponse> { async me(@ReqUserID() userid: string): Promise<UserMeResponse> {
const backenduser = await this.usersService.findOne(userid); const backenduser = ThrowIfFailed(await this.usersService.findOne(userid));
if (HasFailed(backenduser)) {
this.logger.warn(backenduser.getReason());
throw new InternalServerErrorException('Could not get user');
}
const user = EUserBackend2EUser(backenduser); const user = EUserBackend2EUser(backenduser);
const token = await this.authService.createToken(user); const token = ThrowIfFailed(await this.authService.createToken(user));
if (HasFailed(token)) {
this.logger.warn(token.getReason());
throw new InternalServerErrorException('Could not get new token');
}
return { user, token }; return { user, token };
} }
@ -96,11 +76,7 @@ export class UserController {
async refresh( async refresh(
@ReqUserID() userid: string, @ReqUserID() userid: string,
): Promise<UserMePermissionsResponse> { ): Promise<UserMePermissionsResponse> {
const permissions = await this.usersService.getPermissions(userid); const permissions = ThrowIfFailed(await this.usersService.getPermissions(userid));
if (HasFailed(permissions)) {
this.logger.warn(permissions.getReason());
throw new InternalServerErrorException('Could not get permissions');
}
return { permissions }; return { permissions };
} }

View file

@ -1,8 +1,6 @@
import { import {
BadRequestException,
Body, Body,
Controller, Controller,
InternalServerErrorException,
Logger, Logger,
Post Post
} from '@nestjs/common'; } from '@nestjs/common';
@ -14,7 +12,7 @@ import {
ImageUploadResponse ImageUploadResponse
} from 'picsur-shared/dist/dto/api/image-manage.dto'; } from 'picsur-shared/dist/dto/api/image-manage.dto';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { HasFailed } from 'picsur-shared/dist/types'; import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { MultiPart } from '../../decorators/multipart/multipart.decorator'; import { MultiPart } from '../../decorators/multipart/multipart.decorator';
import { import {
HasPermission, HasPermission,
@ -38,14 +36,9 @@ export class ImageManageController {
@MultiPart() multipart: ImageUploadDto, @MultiPart() multipart: ImageUploadDto,
@ReqUserID() userid: string, @ReqUserID() userid: string,
): Promise<ImageUploadResponse> { ): Promise<ImageUploadResponse> {
const image = await this.imagesService.upload( const image = ThrowIfFailed(
multipart.image.buffer, await this.imagesService.upload(multipart.image.buffer, userid),
userid,
); );
if (HasFailed(image)) {
this.logger.warn(image.getReason(), image.getStack());
throw new InternalServerErrorException('Could not upload image');
}
return image; return image;
} }
@ -61,15 +54,9 @@ export class ImageManageController {
body.user_id = userid; body.user_id = userid;
} }
const found = await this.imagesService.findMany( const found = ThrowIfFailed(
body.count, await this.imagesService.findMany(body.count, body.page, body.user_id),
body.page,
body.user_id,
); );
if (HasFailed(found)) {
this.logger.warn(found.getReason());
throw new InternalServerErrorException('Could not list images');
}
return found; return found;
} }
@ -81,14 +68,12 @@ export class ImageManageController {
@ReqUserID() userid: string, @ReqUserID() userid: string,
@HasPermission(Permission.ImageAdmin) isImageAdmin: boolean, @HasPermission(Permission.ImageAdmin) isImageAdmin: boolean,
): Promise<ImageDeleteResponse> { ): Promise<ImageDeleteResponse> {
const deletedImages = await this.imagesService.deleteMany( const deletedImages = ThrowIfFailed(
await this.imagesService.deleteMany(
body.ids, body.ids,
isImageAdmin ? undefined : userid, isImageAdmin ? undefined : userid,
),
); );
if (HasFailed(deletedImages)) {
this.logger.warn(deletedImages.getReason());
throw new BadRequestException('Could not delete images');
}
return { return {
images: deletedImages, images: deletedImages,

View file

@ -1,11 +1,7 @@
import { import {
Controller, Controller,
Get, Get,
Head, Head, Logger, Query,
InternalServerErrorException,
Logger,
NotFoundException,
Query,
Res Res
} from '@nestjs/common'; } from '@nestjs/common';
import type { FastifyReply } from 'fastify'; import type { FastifyReply } from 'fastify';
@ -13,7 +9,7 @@ import {
ImageMetaResponse, ImageMetaResponse,
ImageRequestParams ImageRequestParams
} from 'picsur-shared/dist/dto/api/image.dto'; } from 'picsur-shared/dist/dto/api/image.dto';
import { HasFailed } from 'picsur-shared/dist/types'; import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { UsersService } from '../../collections/user-db/user-db.service'; import { UsersService } from '../../collections/user-db/user-db.service';
import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator'; import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator';
import { ImageIdParam } from '../../decorators/image-id/image-id.decorator'; import { ImageIdParam } from '../../decorators/image-id/image-id.decorator';
@ -41,11 +37,9 @@ export class ImageController {
@ImageFullIdParam() fullid: ImageFullId, @ImageFullIdParam() fullid: ImageFullId,
) { ) {
if (fullid.type === 'original') { if (fullid.type === 'original') {
const fullmime = await this.imagesService.getOriginalMime(fullid.id); const fullmime = ThrowIfFailed(
if (HasFailed(fullmime)) { await this.imagesService.getOriginalMime(fullid.id),
this.logger.warn(fullmime.getReason()); );
throw new NotFoundException('Could not find image');
}
res.type(fullmime.mime); res.type(fullmime.mime);
return; return;
@ -63,25 +57,17 @@ export class ImageController {
@Query() params: ImageRequestParams, @Query() params: ImageRequestParams,
): Promise<Buffer> { ): Promise<Buffer> {
if (fullid.type === 'original') { if (fullid.type === 'original') {
const image = await this.imagesService.getOriginal(fullid.id); const image = ThrowIfFailed(
if (HasFailed(image)) { await this.imagesService.getOriginal(fullid.id),
this.logger.warn(image.getReason()); );
throw new NotFoundException('Could not find image');
}
res.type(image.mime); res.type(image.mime);
return image.data; return image.data;
} }
const image = await this.imagesService.getConverted( const image = ThrowIfFailed(
fullid.id, await this.imagesService.getConverted(fullid.id, fullid.mime, params),
fullid.mime,
params,
); );
if (HasFailed(image)) {
this.logger.warn(image.getReason());
throw new NotFoundException('Failed to get image');
}
res.type(image.mime); res.type(image.mime);
return image.data; return image.data;
@ -90,24 +76,15 @@ export class ImageController {
@Get('meta/:id') @Get('meta/:id')
@Returns(ImageMetaResponse) @Returns(ImageMetaResponse)
async getImageMeta(@ImageIdParam() id: string): Promise<ImageMetaResponse> { async getImageMeta(@ImageIdParam() id: string): Promise<ImageMetaResponse> {
const image = await this.imagesService.findOne(id); const image = ThrowIfFailed(await this.imagesService.findOne(id));
if (HasFailed(image)) {
this.logger.warn(image.getReason());
throw new NotFoundException('Could not find image');
}
const [fileMimes, imageUser] = await Promise.all([ const [fileMimesRes, imageUserRes] = await Promise.all([
this.imagesService.getFileMimes(id), this.imagesService.getFileMimes(id),
this.userService.findOne(image.user_id), this.userService.findOne(image.user_id),
]); ])
if (HasFailed(fileMimes)) {
this.logger.warn(fileMimes.getReason()); const fileMimes = ThrowIfFailed(fileMimesRes);
throw new InternalServerErrorException('Could not get image mime'); const imageUser = ThrowIfFailed(imageUserRes);
}
if (HasFailed(imageUser)) {
this.logger.warn(imageUser.getReason());
throw new InternalServerErrorException('Could not get image user');
}
return { image, user: EUserBackend2EUser(imageUser), fileMimes }; return { image, user: EUserBackend2EUser(imageUser), fileMimes };
} }

View file

@ -1,8 +1,9 @@
import z from 'zod'; import z from 'zod';
const ApiResponseBase = z.object({ const ApiResponseBase = z.object({
statusCode: z.number().min(0).max(600), statusCode: z.number().min(0).max(600).int(),
timestamp: z.string(), timestamp: z.string(),
timeMs: z.number().min(0).int(),
}); });
const ApiSuccessResponse = <T extends z.AnyZodObject>(data: T) => const ApiSuccessResponse = <T extends z.AnyZodObject>(data: T) =>

View file

@ -134,34 +134,51 @@ export class Failure {
} }
export function Fail(type: FT, reason?: any, dbgReason?: any): Failure { export function Fail(type: FT, reason?: any, dbgReason?: any): Failure {
const strReason = reason.toString(); if (IsFailure(reason) || IsFailure(dbgReason)) {
throw new Error('Cannot fail with another failure, just return it');
}
if (typeof dbgReason === 'string') { if (dbgReason === undefined || dbgReason === null) {
return new Failure(type, strReason, undefined, dbgReason); if (reason === undefined || reason === null) {
} else if (dbgReason instanceof Error) { // If both are null, just return a default error message
return new Failure(type, strReason, dbgReason.stack, dbgReason.message); return new Failure(type, FTProps[type].message, undefined, undefined);
} else if (dbgReason instanceof Failure) { } else if (typeof reason === 'string') {
throw new Error('Cannot fail with a failure, just return it'); // If it is a string, this was intentionally specified, so pass it through
} else { return new Failure(type, reason, undefined, undefined);
if (typeof reason === 'string') {
return new Failure(type, strReason, undefined, undefined);
} else if (reason instanceof Error) { } else if (reason instanceof Error) {
// In case of an error, we want to keep that hidden, so return the default message
// Only send the specifics to debug
return new Failure( return new Failure(
type, type,
FTProps[type].message, FTProps[type].message,
reason.stack, reason.stack,
reason.message, reason.message,
); );
} else if (dbgReason instanceof Failure) {
throw new Error('Cannot fail with a failure, just return it');
} else { } else {
return new Failure(type, FTProps[type].message, undefined, undefined); // No clue what it is, so just transform it to a string and return the default message
return new Failure(
type,
FTProps[type].message,
undefined,
String(reason),
);
}
} else {
// In this case we only accept strings for the reason
const strReason = reason?.toString() ?? FTProps[type].message;
if (typeof dbgReason === 'string') {
return new Failure(type, strReason, undefined, dbgReason);
} else if (dbgReason instanceof Error) {
return new Failure(type, strReason, dbgReason.stack, dbgReason.message);
} else {
return new Failure(type, strReason, undefined, String(dbgReason));
} }
} }
} }
export function IsFailure(value: any): value is Failure { export function IsFailure(value: any): value is Failure {
return value.__68351953531423479708__id_failure === 1148363914; return value?.__68351953531423479708__id_failure === 1148363914;
} }
export type Failable<T> = T | Failure; export type Failable<T> = T | Failure;
@ -178,6 +195,14 @@ export function HasSuccess<T>(failable: Failable<T>): failable is T {
return (failable as any).__68351953531423479708__id_failure !== 1148363914; return (failable as any).__68351953531423479708__id_failure !== 1148363914;
} }
export function ThrowIfFailed<V>(failable: Failable<V>): V {
if (HasFailed(failable)) {
throw failable;
}
return failable;
}
export function Map<T, U>( export function Map<T, U>(
failable: Failable<T>, failable: Failable<T>,
mapper: (value: T) => U, mapper: (value: T) => U,