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,
});
const imageCorpOverride = (
const imageCorsOverride = (
req: IncomingMessage,
res: ServerResponse,
next: Function,
@ -54,6 +54,6 @@ const imageCorpOverride = (
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
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':
return {
key: preference.key,
value: preference.value == 'true',
value: preference.value === 'true',
type: 'boolean',
};
}
@ -100,7 +100,7 @@ export class PreferenceCommonService {
expectedType: PrefValueTypeStrings,
): Failable<string> {
const type = typeof value;
if (type != expectedType) {
if (type !== expectedType) {
return Fail(FT.UsrValidation, 'Invalid preference value');
}

View file

@ -1,6 +1,6 @@
import { FactoryProvider, Injectable, Logger } from '@nestjs/common';
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';
@Injectable()
@ -19,20 +19,18 @@ export class JwtConfigService implements JwtOptionsFactory {
}
public async getJwtSecret(): Promise<string> {
const secret = await this.prefService.getStringPreference('jwt_secret');
if (HasFailed(secret)) {
throw new Error('JWT secret could not be retrieved');
}
const secret = ThrowIfFailed(
await this.prefService.getStringPreference('jwt_secret'),
);
return secret;
}
public async getJwtExpiresIn(): Promise<string> {
const expiresIn = await this.prefService.getStringPreference(
'jwt_expires_in',
const expiresIn = ThrowIfFailed(
await this.prefService.getStringPreference('jwt_expires_in'),
);
if (HasFailed(expiresIn)) {
throw new Error('JWT expiresIn could not be retrieved');
}
return expiresIn;
}

View file

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

View file

@ -1,4 +1,5 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import AuthFasityRequest from '../models/interfaces/authrequest.dto';
export const ReqUser = createParamDecorator(
@ -12,7 +13,7 @@ export const ReqUserID = createParamDecorator(
(input: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest() as AuthFasityRequest;
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;
},
);

View file

@ -1,7 +1,12 @@
import { ArgumentsHost, Catch, ExceptionFilter, Logger } from '@nestjs/common';
import { FastifyReply, FastifyRequest } from 'fastify';
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
// (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 {
private static readonly logger = new Logger('MainExceptionFilter');
catch(exception: unknown, host: ArgumentsHost) {
catch(exception: Failure, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<FastifyReply>();
const request = ctx.getRequest<FastifyRequest>();
@ -22,7 +27,7 @@ export class MainExceptionFilter implements ExceptionFilter {
MainExceptionFilter.logger.error(
traceString + ' Unkown exception: ' + exception,
);
return;
exception = Fail(FT.Internal, 'Unknown exception', exception);
}
const status = exception.getCode();
@ -50,6 +55,7 @@ export class MainExceptionFilter implements ExceptionFilter {
success: false,
statusCode: status,
timestamp: new Date().toISOString(),
timeMs: Math.round(response.getResponseTime()),
data: {
type,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,9 @@
import z from 'zod';
const ApiResponseBase = z.object({
statusCode: z.number().min(0).max(600),
statusCode: z.number().min(0).max(600).int(),
timestamp: z.string(),
timeMs: z.number().min(0).int(),
});
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 {
const strReason = reason.toString();
if (IsFailure(reason) || IsFailure(dbgReason)) {
throw new Error('Cannot fail with another failure, just return it');
}
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 if (dbgReason instanceof Failure) {
throw new Error('Cannot fail with a failure, just return it');
} else {
if (typeof reason === 'string') {
return new Failure(type, strReason, undefined, undefined);
if (dbgReason === undefined || dbgReason === null) {
if (reason === undefined || reason === null) {
// If both are null, just return a default error message
return new Failure(type, FTProps[type].message, undefined, undefined);
} else if (typeof reason === 'string') {
// If it is a string, this was intentionally specified, so pass it through
return new Failure(type, reason, undefined, undefined);
} 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(
type,
FTProps[type].message,
reason.stack,
reason.message,
);
} else if (dbgReason instanceof Failure) {
throw new Error('Cannot fail with a failure, just return it');
} 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 {
return value.__68351953531423479708__id_failure === 1148363914;
return value?.__68351953531423479708__id_failure === 1148363914;
}
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;
}
export function ThrowIfFailed<V>(failable: Failable<V>): V {
if (HasFailed(failable)) {
throw failable;
}
return failable;
}
export function Map<T, U>(
failable: Failable<T>,
mapper: (value: T) => U,