Change failure behaviour
This commit is contained in:
parent
ba47d0bff4
commit
c8722d8944
|
@ -1,6 +1,6 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
|
||||
import { FindResult } from 'picsur-shared/dist/types/find-result';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { EImageDerivativeBackend } from '../../models/entities/image-derivative.entity';
|
||||
|
@ -28,7 +28,7 @@ export class ImageDBService {
|
|||
try {
|
||||
imageEntity = await this.imageRepo.save(imageEntity, { reload: true });
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
|
||||
return imageEntity;
|
||||
|
@ -43,10 +43,10 @@ export class ImageDBService {
|
|||
where: { id, user_id: userid },
|
||||
});
|
||||
|
||||
if (!found) return Fail('Image not found');
|
||||
if (!found) return Fail(FT.NotFound, 'Image not found');
|
||||
return found;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,8 +55,8 @@ export class ImageDBService {
|
|||
page: number,
|
||||
userid: string | undefined,
|
||||
): AsyncFailable<FindResult<EImageBackend>> {
|
||||
if (count < 1 || page < 0) return Fail('Invalid page');
|
||||
if (count > 100) return Fail('Too many results');
|
||||
if (count < 1 || page < 0) return Fail(FT.UsrValidation, 'Invalid page');
|
||||
if (count > 100) return Fail(FT.UsrValidation, 'Too many results');
|
||||
|
||||
try {
|
||||
const [found, amount] = await this.imageRepo.findAndCount({
|
||||
|
@ -67,7 +67,7 @@ export class ImageDBService {
|
|||
},
|
||||
});
|
||||
|
||||
if (found === undefined) return Fail('Images not found');
|
||||
if (found === undefined) return Fail(FT.NotFound, 'Images not found');
|
||||
|
||||
return {
|
||||
results: found,
|
||||
|
@ -76,7 +76,7 @@ export class ImageDBService {
|
|||
pages: Math.ceil(amount / count),
|
||||
};
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ export class ImageDBService {
|
|||
userid: string | undefined,
|
||||
): AsyncFailable<EImageBackend[]> {
|
||||
if (ids.length === 0) return [];
|
||||
if (ids.length > 500) return Fail('Too many results');
|
||||
if (ids.length > 500) return Fail(FT.UsrValidation, 'Too many results');
|
||||
|
||||
try {
|
||||
const deletable_images = await this.imageRepo.find({
|
||||
|
@ -97,7 +97,7 @@ export class ImageDBService {
|
|||
|
||||
const available_ids = deletable_images.map((i) => i.id);
|
||||
|
||||
if (available_ids.length === 0) return Fail('Images not found');
|
||||
if (available_ids.length === 0) return Fail(FT.NotFound, 'Images not found');
|
||||
|
||||
await Promise.all([
|
||||
this.imageDerivativeRepo.delete({
|
||||
|
@ -112,20 +112,20 @@ export class ImageDBService {
|
|||
|
||||
return deletable_images;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteAll(IAmSure: boolean): AsyncFailable<true> {
|
||||
if (!IAmSure)
|
||||
return Fail('You must confirm that you want to delete all images');
|
||||
return Fail(FT.SysValidation, 'You must confirm that you want to delete all images');
|
||||
|
||||
try {
|
||||
await this.imageDerivativeRepo.delete({});
|
||||
await this.imageFileRepo.delete({});
|
||||
await this.imageRepo.delete({});
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.enum';
|
||||
import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
|
||||
import { LessThan, Repository } from 'typeorm';
|
||||
import { EImageDerivativeBackend } from '../../models/entities/image-derivative.entity';
|
||||
import { EImageFileBackend } from '../../models/entities/image-file.entity';
|
||||
|
@ -35,7 +35,7 @@ export class ImageFileDBService {
|
|||
conflictPaths: ['image_id', 'type'],
|
||||
});
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -50,10 +50,10 @@ export class ImageFileDBService {
|
|||
where: { image_id: imageId ?? '', type: type ?? '' },
|
||||
});
|
||||
|
||||
if (!found) return Fail('Image not found');
|
||||
if (!found) return Fail(FT.NotFound, 'Image not found');
|
||||
return found;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ export class ImageFileDBService {
|
|||
select: ['type', 'mime'],
|
||||
});
|
||||
|
||||
if (!found) return Fail('Image not found');
|
||||
if (!found) return Fail(FT.NotFound, 'Image not found');
|
||||
|
||||
const result: { [key in ImageFileType]?: string } = {};
|
||||
for (const file of found) {
|
||||
|
@ -76,7 +76,7 @@ export class ImageFileDBService {
|
|||
|
||||
return result;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ export class ImageFileDBService {
|
|||
try {
|
||||
return await this.imageDerivativeRepo.save(imageDerivative);
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ export class ImageFileDBService {
|
|||
|
||||
return derivative;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ export class ImageFileDBService {
|
|||
|
||||
return result.affected ?? 0;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
AsyncFailable,
|
||||
Fail,
|
||||
Failable,
|
||||
FT,
|
||||
HasFailed
|
||||
} from 'picsur-shared/dist/types';
|
||||
|
||||
|
@ -60,7 +61,7 @@ export class PreferenceCommonService {
|
|||
};
|
||||
}
|
||||
|
||||
return Fail('Invalid preference value');
|
||||
return Fail(FT.UsrValidation, 'Invalid preference value');
|
||||
}
|
||||
|
||||
public async EncodePref<E extends Enum>(
|
||||
|
@ -88,7 +89,7 @@ export class PreferenceCommonService {
|
|||
): Failable<V> {
|
||||
const keysList = Object.values(prefType);
|
||||
if (!keysList.includes(key)) {
|
||||
return Fail('Invalid preference key');
|
||||
return Fail(FT.UsrValidation, 'Invalid preference key');
|
||||
}
|
||||
|
||||
return key as V;
|
||||
|
@ -100,7 +101,7 @@ export class PreferenceCommonService {
|
|||
): Failable<string> {
|
||||
const type = typeof value;
|
||||
if (type != expectedType) {
|
||||
return Fail('Invalid preference value');
|
||||
return Fail(FT.UsrValidation, 'Invalid preference value');
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
|
@ -112,6 +113,6 @@ export class PreferenceCommonService {
|
|||
return value ? 'true' : 'false';
|
||||
}
|
||||
|
||||
return Fail('Invalid preference value');
|
||||
return Fail(FT.UsrValidation, 'Invalid preference value');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
PrefValueTypeStrings
|
||||
} from 'picsur-shared/dist/dto/preferences.dto';
|
||||
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
|
||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { Repository } from 'typeorm';
|
||||
import {
|
||||
SysPreferenceList,
|
||||
|
@ -46,7 +46,7 @@ export class SysPreferenceService {
|
|||
conflictPaths: ['key'],
|
||||
});
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -74,13 +74,13 @@ export class SysPreferenceService {
|
|||
});
|
||||
if (!existing) return null;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
|
||||
// Validate
|
||||
const result = ESysPreferenceSchema.safeParse(existing);
|
||||
if (!result.success) {
|
||||
return Fail(result.error);
|
||||
return Fail(FT.SysValidation, result.error);
|
||||
}
|
||||
|
||||
// Return
|
||||
|
@ -113,7 +113,7 @@ export class SysPreferenceService {
|
|||
): AsyncFailable<PrefValueType> {
|
||||
let pref = await this.getPreference(key);
|
||||
if (HasFailed(pref)) return pref;
|
||||
if (pref.type !== type) return Fail('Invalid preference type');
|
||||
if (pref.type !== type) return Fail(FT.UsrValidation, 'Invalid preference type');
|
||||
|
||||
return pref.value;
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ export class SysPreferenceService {
|
|||
SysPreferenceList.map((key) => this.getPreference(key)),
|
||||
);
|
||||
if (internalSysPrefs.some((pref) => HasFailed(pref))) {
|
||||
return Fail('Could not get all preferences');
|
||||
return Fail(FT.Internal, 'Could not get all preferences');
|
||||
}
|
||||
|
||||
return internalSysPrefs as DecodedSysPref[];
|
||||
|
@ -157,7 +157,7 @@ export class SysPreferenceService {
|
|||
// It should already be valid, but these two validators might go out of sync
|
||||
const result = ESysPreferenceSchema.safeParse(verifySysPreference);
|
||||
if (!result.success) {
|
||||
return Fail(result.error);
|
||||
return Fail(FT.UsrValidation, result.error);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
PrefValueTypeStrings
|
||||
} from 'picsur-shared/dist/dto/preferences.dto';
|
||||
import { UsrPreference } from 'picsur-shared/dist/dto/usr-preferences.enum';
|
||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { Repository } from 'typeorm';
|
||||
import {
|
||||
UsrPreferenceList,
|
||||
|
@ -47,7 +47,7 @@ export class UsrPreferenceService {
|
|||
conflictPaths: ['key', 'user_id'],
|
||||
});
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
|
||||
// Return
|
||||
|
@ -80,13 +80,13 @@ export class UsrPreferenceService {
|
|||
});
|
||||
if (!existing) return null;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
|
||||
// Validate
|
||||
const result = EUsrPreferenceSchema.safeParse(existing);
|
||||
if (!result.success) {
|
||||
return Fail(result.error);
|
||||
return Fail(FT.SysValidation, result.error);
|
||||
}
|
||||
|
||||
// Return
|
||||
|
@ -146,7 +146,8 @@ export class UsrPreferenceService {
|
|||
): AsyncFailable<PrefValueType> {
|
||||
let pref = await this.getPreference(userid, key);
|
||||
if (HasFailed(pref)) return pref;
|
||||
if (pref.type !== type) return Fail('Invalid preference type');
|
||||
if (pref.type !== type)
|
||||
return Fail(FT.UsrValidation, 'Invalid preference type');
|
||||
|
||||
return pref.value;
|
||||
}
|
||||
|
@ -159,7 +160,7 @@ export class UsrPreferenceService {
|
|||
UsrPreferenceList.map((key) => this.getPreference(userid, key)),
|
||||
);
|
||||
if (internalSysPrefs.some((pref) => HasFailed(pref))) {
|
||||
return Fail('Could not get all preferences');
|
||||
return Fail(FT.Internal, 'Could not get all preferences');
|
||||
}
|
||||
|
||||
return internalSysPrefs as DecodedUsrPref[];
|
||||
|
@ -199,7 +200,7 @@ export class UsrPreferenceService {
|
|||
// It should already be valid, but these two validators might go out of sync
|
||||
const result = EUsrPreferenceSchema.safeParse(verifySysPreference);
|
||||
if (!result.success) {
|
||||
return Fail(result.error);
|
||||
return Fail(FT.UsrValidation, result.error);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
|
|
|
@ -6,7 +6,7 @@ import { HostConfigService } from '../../config/early/host.config.service';
|
|||
import {
|
||||
ImmutableRolesList,
|
||||
SystemRoleDefaults,
|
||||
UndeletableRolesList
|
||||
SystemRolesList
|
||||
} from '../../models/constants/roles.const';
|
||||
import { ERoleBackend } from '../../models/entities/role.entity';
|
||||
import { RolesService } from './role-db.service';
|
||||
|
@ -44,8 +44,7 @@ export class RolesModule implements OnModuleInit {
|
|||
}
|
||||
|
||||
private async ensureSystemRolesExist() {
|
||||
// The UndeletableRolesList is also the list of systemroles
|
||||
for (const systemRole of UndeletableRolesList) {
|
||||
for (const systemRole of SystemRolesList) {
|
||||
this.logger.verbose(`Ensuring system role "${systemRole}" exists`);
|
||||
|
||||
const exists = await this.rolesService.exists(systemRole);
|
||||
|
@ -76,7 +75,7 @@ export class RolesModule implements OnModuleInit {
|
|||
const result = await this.rolesService.setPermissions(
|
||||
immutableRole,
|
||||
SystemRoleDefaults[immutableRole],
|
||||
true,
|
||||
true, // Manual bypass for immutable roles
|
||||
);
|
||||
if (HasFailed(result)) {
|
||||
this.logger.error(
|
||||
|
|
|
@ -4,6 +4,7 @@ import { ERoleSchema } from 'picsur-shared/dist/entities/role.entity';
|
|||
import {
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
HasSuccess
|
||||
} from 'picsur-shared/dist/types';
|
||||
|
@ -29,7 +30,8 @@ export class RolesService {
|
|||
name: string,
|
||||
permissions: Permissions,
|
||||
): AsyncFailable<ERoleBackend> {
|
||||
if (await this.exists(name)) return Fail('Role already exists');
|
||||
if (await this.exists(name))
|
||||
return Fail(FT.Conflict, 'Role already exists');
|
||||
|
||||
let role = new ERoleBackend();
|
||||
role.name = name;
|
||||
|
@ -38,7 +40,7 @@ export class RolesService {
|
|||
try {
|
||||
return await this.rolesRepository.save(role, { reload: true });
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,13 +49,13 @@ export class RolesService {
|
|||
if (HasFailed(roleToModify)) return roleToModify;
|
||||
|
||||
if (UndeletableRolesList.includes(roleToModify.name)) {
|
||||
return Fail('Cannot delete system role');
|
||||
return Fail(FT.Permission, 'Cannot delete system role');
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.rolesRepository.remove(roleToModify);
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +111,7 @@ export class RolesService {
|
|||
if (HasFailed(roleToModify)) return roleToModify;
|
||||
|
||||
if (!allowImmutable && ImmutableRolesList.includes(roleToModify.name)) {
|
||||
return Fail('Cannot modify immutable role');
|
||||
return Fail(FT.Permission, 'Cannot modify immutable role');
|
||||
}
|
||||
|
||||
roleToModify.permissions = makeUnique(permissions);
|
||||
|
@ -117,7 +119,7 @@ export class RolesService {
|
|||
try {
|
||||
return await this.rolesRepository.save(roleToModify);
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,10 +129,10 @@ export class RolesService {
|
|||
where: { name },
|
||||
});
|
||||
|
||||
if (!found) return Fail('Role not found');
|
||||
if (!found) return Fail(FT.NotFound, 'Role not found');
|
||||
return found;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,20 +142,20 @@ export class RolesService {
|
|||
where: { name: In(names) },
|
||||
});
|
||||
|
||||
if (!found) return Fail('No roles found');
|
||||
if (!found) return Fail(FT.NotFound, 'No roles found');
|
||||
return found;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
public async findAll(): AsyncFailable<ERoleBackend[]> {
|
||||
try {
|
||||
const found = await this.rolesRepository.find();
|
||||
if (!found) return Fail('No roles found');
|
||||
if (!found) return Fail(FT.NotFound, 'No roles found');
|
||||
return found;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,14 +165,17 @@ export class RolesService {
|
|||
|
||||
public async nukeSystemRoles(IAmSure: boolean = false): AsyncFailable<true> {
|
||||
if (!IAmSure)
|
||||
return Fail('You must confirm that you want to delete all roles');
|
||||
return Fail(
|
||||
FT.SysValidation,
|
||||
'You must confirm that you want to delete all roles',
|
||||
);
|
||||
|
||||
try {
|
||||
await this.rolesRepository.delete({
|
||||
name: In(UndeletableRolesList),
|
||||
});
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -183,7 +188,7 @@ export class RolesService {
|
|||
} else {
|
||||
const result = ERoleSchema.safeParse(role);
|
||||
if (!result.success) {
|
||||
return Fail(result.error);
|
||||
return Fail(FT.SysValidation, result.error);
|
||||
}
|
||||
// This is safe
|
||||
return result.data as ERoleBackend;
|
||||
|
|
|
@ -5,6 +5,7 @@ import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
|
|||
import {
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
HasSuccess
|
||||
} from 'picsur-shared/dist/types';
|
||||
|
@ -46,7 +47,8 @@ export class UsersService {
|
|||
// Add option to create "invalid" users, should only be used by system
|
||||
byPassRoleCheck?: boolean,
|
||||
): AsyncFailable<EUserBackend> {
|
||||
if (await this.exists(username)) return Fail('User already exists');
|
||||
if (await this.exists(username))
|
||||
return Fail(FT.Conflict, 'User already exists');
|
||||
|
||||
const strength = await this.getBCryptStrength();
|
||||
const hashedPassword = await bcrypt.hash(password, strength);
|
||||
|
@ -66,7 +68,7 @@ export class UsersService {
|
|||
try {
|
||||
return await this.usersRepository.save(user);
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,13 +77,13 @@ export class UsersService {
|
|||
if (HasFailed(userToModify)) return userToModify;
|
||||
|
||||
if (UndeletableUsersList.includes(userToModify.username)) {
|
||||
return Fail('Cannot delete system user');
|
||||
return Fail(FT.Permission, 'Cannot delete system user');
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.usersRepository.remove(userToModify);
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +112,7 @@ export class UsersService {
|
|||
try {
|
||||
return await this.usersRepository.save(userToModify);
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +127,7 @@ export class UsersService {
|
|||
.where('roles @> ARRAY[:role]', { role })
|
||||
.execute();
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -151,7 +153,7 @@ export class UsersService {
|
|||
try {
|
||||
userToModify = await this.usersRepository.save(userToModify);
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
|
||||
return userToModify;
|
||||
|
@ -168,11 +170,11 @@ export class UsersService {
|
|||
|
||||
if (LockedLoginUsersList.includes(user.username)) {
|
||||
// Error should be kept in backend
|
||||
return Fail('Wrong username');
|
||||
return Fail(FT.Authentication, 'Wrong username');
|
||||
}
|
||||
|
||||
if (!(await bcrypt.compare(password, user.hashed_password ?? '')))
|
||||
return Fail('Wrong password');
|
||||
return Fail(FT.Authentication, 'Wrong password');
|
||||
|
||||
return await this.findOne(user.id ?? '');
|
||||
}
|
||||
|
@ -191,10 +193,10 @@ export class UsersService {
|
|||
select: getPrivate ? GetCols(this.usersRepository) : undefined,
|
||||
});
|
||||
|
||||
if (!found) return Fail('User not found');
|
||||
if (!found) return Fail(FT.NotFound, 'User not found');
|
||||
return found;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,10 +206,10 @@ export class UsersService {
|
|||
where: { id: uuid },
|
||||
});
|
||||
|
||||
if (!found) return Fail('User not found');
|
||||
if (!found) return Fail(FT.NotFound, 'User not found');
|
||||
return found as EUserBackend;
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,8 +217,8 @@ export class UsersService {
|
|||
count: number,
|
||||
page: number,
|
||||
): AsyncFailable<FindResult<EUserBackend>> {
|
||||
if (count < 1 || page < 0) return Fail('Invalid page');
|
||||
if (count > 100) return Fail('Too many results');
|
||||
if (count < 1 || page < 0) return Fail(FT.UsrValidation, 'Invalid page');
|
||||
if (count > 100) return Fail(FT.UsrValidation, 'Too many results');
|
||||
|
||||
try {
|
||||
const [users, amount] = await this.usersRepository.findAndCount({
|
||||
|
@ -224,7 +226,7 @@ export class UsersService {
|
|||
skip: count * page,
|
||||
});
|
||||
|
||||
if (users === undefined) return Fail('Users not found');
|
||||
if (users === undefined) return Fail(FT.NotFound, 'Users not found');
|
||||
|
||||
return {
|
||||
results: users,
|
||||
|
@ -233,7 +235,7 @@ export class UsersService {
|
|||
pages: Math.ceil(amount / count),
|
||||
};
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Database, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
ExceptionFilter,
|
||||
HttpException,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
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';
|
||||
|
||||
// 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)
|
||||
|
@ -17,22 +12,35 @@ export class MainExceptionFilter implements ExceptionFilter {
|
|||
private static readonly logger = new Logger('MainExceptionFilter');
|
||||
|
||||
catch(exception: unknown, host: ArgumentsHost) {
|
||||
if (exception instanceof Error) {
|
||||
MainExceptionFilter.logger.warn(exception.message);
|
||||
MainExceptionFilter.logger.debug(exception.stack);
|
||||
} else {
|
||||
MainExceptionFilter.logger.warn(exception);
|
||||
}
|
||||
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<FastifyReply>();
|
||||
const request = ctx.getRequest<FastifyRequest>();
|
||||
const status =
|
||||
exception instanceof HttpException ? exception.getStatus() : 500;
|
||||
const message =
|
||||
exception instanceof HttpException
|
||||
? exception.message
|
||||
: 'Internal server error';
|
||||
|
||||
const traceString = `(${request.ip} -> ${request.method} ${request.url})`;
|
||||
|
||||
if (!IsFailure(exception)) {
|
||||
MainExceptionFilter.logger.error(
|
||||
traceString + ' Unkown exception: ' + exception,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception.isImportant()) {
|
||||
MainExceptionFilter.logger.error(
|
||||
`${traceString} ${exception.getName()}: ${exception.getReason()}`,
|
||||
);
|
||||
if (exception.getStack()) {
|
||||
MainExceptionFilter.logger.debug(exception.getStack());
|
||||
}
|
||||
} else {
|
||||
MainExceptionFilter.logger.warn(
|
||||
`${traceString} ${exception.getName()}: ${exception.getReason()}`,
|
||||
);
|
||||
}
|
||||
|
||||
const status = exception.getCode();
|
||||
const type = exception.getType();
|
||||
const message = exception.getReason();
|
||||
|
||||
const toSend: ApiErrorResponse = {
|
||||
success: false,
|
||||
|
@ -40,6 +48,7 @@ export class MainExceptionFilter implements ExceptionFilter {
|
|||
timestamp: new Date().toISOString(),
|
||||
|
||||
data: {
|
||||
type,
|
||||
message,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
|||
import { JwtService } from '@nestjs/jwt';
|
||||
import { JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
|
||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||
import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
|
||||
|
||||
@Injectable()
|
||||
export class AuthManagerService {
|
||||
|
@ -19,13 +19,13 @@ export class AuthManagerService {
|
|||
// in case of any failures
|
||||
const result = JwtDataSchema.safeParse(jwtData);
|
||||
if (!result.success) {
|
||||
return Fail('Invalid JWT: ' + result.error);
|
||||
return Fail(FT.SysValidation, 'Invalid JWT: ' + result.error);
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.jwtService.signAsync(result.data);
|
||||
} catch (e) {
|
||||
return Fail("Couldn't create JWT: " + e);
|
||||
return Fail(FT.Internal, "Couldn't create JWT: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import {
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
ExecutionContext, Injectable,
|
||||
InternalServerErrorException,
|
||||
Logger
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { EUser, EUserSchema } from 'picsur-shared/dist/entities/user.entity';
|
||||
import { Fail, Failable, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { Fail, Failable, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { UsersService } from '../../../collections/user-db/user-db.service';
|
||||
import { Permissions } from '../../../models/constants/permissions.const';
|
||||
import { isPermissionsArray } from '../../../models/validators/permissions.validator';
|
||||
|
@ -66,7 +64,7 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
|
|||
|
||||
if (permissions.every((permission) => userPermissions.includes(permission)))
|
||||
return true;
|
||||
else throw new ForbiddenException('Permission denied');
|
||||
else throw Fail(FT.Permission, 'Permission denied');
|
||||
}
|
||||
|
||||
private extractPermissions(context: ExecutionContext): Failable<Permissions> {
|
||||
|
@ -79,11 +77,15 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
|
|||
|
||||
if (permissions === undefined)
|
||||
return Fail(
|
||||
FT.Internal,
|
||||
`${handlerName} does not have any permissions defined, denying access`,
|
||||
);
|
||||
|
||||
if (!isPermissionsArray(permissions))
|
||||
return Fail(`Permissions for ${handlerName} is not a string array`);
|
||||
return Fail(
|
||||
FT.Internal,
|
||||
`Permissions for ${handlerName} is not a string array`,
|
||||
);
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
SupportedMimeCategory
|
||||
} from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
|
||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { SysPreferenceService } from '../../collections/preference-db/sys-preference-db.service';
|
||||
import { SharpWrapper } from '../../workers/sharp.wrapper';
|
||||
import { ImageResult } from './imageresult';
|
||||
|
@ -22,7 +22,10 @@ export class ImageConverterService {
|
|||
options: ImageRequestParams,
|
||||
): AsyncFailable<ImageResult> {
|
||||
if (sourcemime.type !== targetmime.type) {
|
||||
return Fail("Can't convert from animated to still or vice versa");
|
||||
return Fail(
|
||||
FT.Impossible,
|
||||
"Can't convert from animated to still or vice versa",
|
||||
);
|
||||
}
|
||||
|
||||
if (sourcemime.mime === targetmime.mime) {
|
||||
|
@ -37,7 +40,7 @@ export class ImageConverterService {
|
|||
} else if (targetmime.type === SupportedMimeCategory.Animation) {
|
||||
return this.convertAnimation(image, targetmime, options);
|
||||
} else {
|
||||
return Fail('Unsupported mime type');
|
||||
return Fail(FT.SysValidation, 'Unsupported mime type');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +55,7 @@ export class ImageConverterService {
|
|||
this.sysPref.getStringPreference(SysPreference.ConversionTimeLimit),
|
||||
]);
|
||||
if (HasFailed(memLimit) || HasFailed(timeLimit)) {
|
||||
return Fail('Failed to get conversion limits');
|
||||
return Fail(FT.Internal, 'Failed to get conversion limits');
|
||||
}
|
||||
const timeLimitMS = ms(timeLimit);
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ import { Injectable } from '@nestjs/common';
|
|||
import {
|
||||
FullMime,
|
||||
ImageMime,
|
||||
SupportedMimeCategory,
|
||||
SupportedMimeCategory
|
||||
} from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
|
||||
import { QOIColorSpace, QOIencode } from 'qoi-img';
|
||||
import { ImageResult } from './imageresult';
|
||||
import { UniversalSharp } from './universal-sharp';
|
||||
|
@ -20,7 +20,7 @@ export class ImageProcessorService {
|
|||
} else if (mime.type === SupportedMimeCategory.Animation) {
|
||||
return await this.processAnimation(image, mime);
|
||||
} else {
|
||||
return Fail('Unsupported mime type');
|
||||
return Fail(FT.SysValidation, 'Unsupported mime type');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ export class ImageProcessorService {
|
|||
processedImage.info.width >= 32768 ||
|
||||
processedImage.info.height >= 32768
|
||||
) {
|
||||
return Fail('Image too large');
|
||||
return Fail(FT.UsrValidation, 'Image too large');
|
||||
}
|
||||
|
||||
// Png can be more efficient than QOI, but its just sooooooo slow
|
||||
|
|
|
@ -6,7 +6,7 @@ import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.enum';
|
|||
import { FullMime } from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
|
||||
import { UsrPreference } from 'picsur-shared/dist/dto/usr-preferences.enum';
|
||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { FindResult } from 'picsur-shared/dist/types/find-result';
|
||||
import { ParseMime } from 'picsur-shared/dist/util/parse-mime';
|
||||
import { IsQOI } from 'qoi-img';
|
||||
|
@ -168,12 +168,10 @@ export class ImageManagerService {
|
|||
}
|
||||
|
||||
public async getMasterMime(imageId: string): AsyncFailable<FullMime> {
|
||||
const mime = await this.imageFilesService.getFileMimes(
|
||||
imageId
|
||||
);
|
||||
const mime = await this.imageFilesService.getFileMimes(imageId);
|
||||
if (HasFailed(mime)) return mime;
|
||||
|
||||
if (mime.master === undefined) return Fail('No master file');
|
||||
if (mime.master === undefined) return Fail(FT.NotFound, 'No master file');
|
||||
|
||||
return ParseMime(mime.master);
|
||||
}
|
||||
|
@ -183,12 +181,11 @@ export class ImageManagerService {
|
|||
}
|
||||
|
||||
public async getOriginalMime(imageId: string): AsyncFailable<FullMime> {
|
||||
const mime = await this.imageFilesService.getFileMimes(
|
||||
imageId
|
||||
);
|
||||
const mime = await this.imageFilesService.getFileMimes(imageId);
|
||||
if (HasFailed(mime)) return mime;
|
||||
|
||||
if (mime.original === undefined) return Fail('No original file');
|
||||
if (mime.original === undefined)
|
||||
return Fail(FT.NotFound, 'No original file');
|
||||
|
||||
return ParseMime(mime.original);
|
||||
}
|
||||
|
@ -201,7 +198,7 @@ export class ImageManagerService {
|
|||
if (HasFailed(result)) return result;
|
||||
|
||||
if (result[ImageFileType.MASTER] === undefined) {
|
||||
return Fail('No master file found');
|
||||
return Fail(FT.NotFound, 'No master file found');
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -20,6 +20,9 @@ export const SoulBoundRolesList: string[] = SoulBoundRolesTuple;
|
|||
export const ImmutableRolesList: string[] = ImmutableRolesTuple;
|
||||
export const UndeletableRolesList: string[] = UndeletableRolesTuple;
|
||||
|
||||
// Yes this is the undeletableroles list
|
||||
export const SystemRolesList = UndeletableRolesList;
|
||||
|
||||
// Defaults
|
||||
type SystemRole = typeof UndeletableRolesTuple[number];
|
||||
const SystemRoleDefaultsTyped: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { MultipartFile } from '@fastify/multipart';
|
||||
import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const MultiPartFileDtoSchema = z.object({
|
||||
|
@ -26,7 +26,7 @@ export async function CreateMultiPartFileDto(
|
|||
file: file.file,
|
||||
};
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Internal, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Returns } from '../../../decorators/returns.decorator';
|
|||
import type AuthFasityRequest from '../../../models/interfaces/authrequest.dto';
|
||||
|
||||
@Controller('api/experiment')
|
||||
//@NoPermissions()
|
||||
@RequiredPermissions(Permission.Settings)
|
||||
export class ExperimentController {
|
||||
@Get()
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
AsyncFailable,
|
||||
Fail,
|
||||
Failable,
|
||||
FT,
|
||||
HasFailed
|
||||
} from 'picsur-shared/dist/types';
|
||||
import { Sharp } from 'sharp';
|
||||
|
@ -97,7 +98,7 @@ export class SharpWrapper {
|
|||
...parameters: Parameters<Sharp[Operation]>
|
||||
): Failable<true> {
|
||||
if (!this.worker) {
|
||||
return Fail('Worker is not initialized');
|
||||
return Fail(FT.Internal, 'Worker is not initialized');
|
||||
}
|
||||
|
||||
const hasSent = this.sendToWorker({
|
||||
|
@ -120,7 +121,7 @@ export class SharpWrapper {
|
|||
options?: SharpWorkerFinishOptions,
|
||||
): AsyncFailable<SharpResult> {
|
||||
if (!this.worker) {
|
||||
return Fail('Worker is not initialized');
|
||||
return Fail(FT.Internal, 'Worker is not initialized');
|
||||
}
|
||||
|
||||
const hasSent = this.sendToWorker({
|
||||
|
@ -158,7 +159,7 @@ export class SharpWrapper {
|
|||
return result.result;
|
||||
} catch (error) {
|
||||
this.purge();
|
||||
return Fail(error);
|
||||
return Fail(FT.Internal, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,13 +177,13 @@ export class SharpWrapper {
|
|||
await pTimeout(waitReadyPromise, this.instance_timeout);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return Fail(error);
|
||||
return Fail(FT.Internal, error);
|
||||
}
|
||||
}
|
||||
|
||||
private sendToWorker(message: SharpWorkerSendMessage): Failable<true> {
|
||||
if (!this.worker) {
|
||||
return Fail('Worker is not initialized');
|
||||
return Fail(FT.Internal, 'Worker is not initialized');
|
||||
}
|
||||
|
||||
this.worker.send(message);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { FormControl } from '@angular/forms';
|
||||
import { Fail, Failable } from 'picsur-shared/dist/types';
|
||||
import { Fail, Failable, FT } from 'picsur-shared/dist/types';
|
||||
import { UserPassModel } from '../forms-dto/userpass.dto';
|
||||
import {
|
||||
CreatePasswordError,
|
||||
CreateUsernameError,
|
||||
PasswordValidators,
|
||||
UsernameValidators,
|
||||
UsernameValidators
|
||||
} from '../validators/user.validator';
|
||||
|
||||
export class LoginControl {
|
||||
|
@ -23,7 +23,7 @@ export class LoginControl {
|
|||
// This getter firstly verifies the form, RawData does not
|
||||
public getData(): Failable<UserPassModel> {
|
||||
if (this.username.errors || this.password.errors)
|
||||
return Fail('Invalid username or password');
|
||||
return Fail(FT.Authentication, 'Invalid username or password');
|
||||
else return this.getRawData();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { FormControl } from '@angular/forms';
|
||||
import { Fail, Failable } from 'picsur-shared/dist/types';
|
||||
import { Fail, Failable, FT } from 'picsur-shared/dist/types';
|
||||
import { UserPassModel } from '../forms-dto/userpass.dto';
|
||||
import { Compare } from '../validators/compare.validator';
|
||||
import {
|
||||
CreatePasswordError,
|
||||
CreateUsernameError,
|
||||
PasswordValidators,
|
||||
UsernameValidators,
|
||||
UsernameValidators
|
||||
} from '../validators/user.validator';
|
||||
|
||||
export class RegisterControl {
|
||||
|
@ -36,7 +36,7 @@ export class RegisterControl {
|
|||
this.password.errors ||
|
||||
this.passwordConfirm.errors
|
||||
)
|
||||
return Fail('Invalid username or password');
|
||||
return Fail(FT.Authentication, 'Invalid username or password');
|
||||
else return this.getRawData();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Inject, Injectable } from '@angular/core';
|
|||
import { WINDOW } from '@ng-web-apis/common';
|
||||
import { ApiResponseSchema } from 'picsur-shared/dist/dto/api/api.dto';
|
||||
import { Mime2Ext } from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
|
||||
import { Subject } from 'rxjs';
|
||||
import { ApiBuffer } from 'src/app/models/dto/api-buffer.dto';
|
||||
|
@ -59,7 +59,7 @@ export class ApiService {
|
|||
const validateResult = sendSchema.safeParse(data);
|
||||
if (!validateResult.success) {
|
||||
this.logger.error(validateResult.error);
|
||||
return Fail('Something went wrong');
|
||||
return Fail(FT.SysValidation, 'Something went wrong');
|
||||
}
|
||||
|
||||
return this.fetchSafeJson(receiveType, url, {
|
||||
|
@ -93,10 +93,11 @@ export class ApiService {
|
|||
const validateResult = resultSchema.safeParse(result);
|
||||
if (!validateResult.success) {
|
||||
this.logger.error(validateResult.error);
|
||||
return Fail('Something went wrong');
|
||||
return Fail(FT.SysValidation, 'Something went wrong');
|
||||
}
|
||||
|
||||
if (validateResult.data.success === false) return Fail(result.data.message);
|
||||
if (validateResult.data.success === false)
|
||||
return Fail(FT.Unknown, result.data.message);
|
||||
|
||||
return validateResult.data.data;
|
||||
}
|
||||
|
@ -113,7 +114,7 @@ export class ApiService {
|
|||
return await response.json();
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
return Fail('Something went wrong');
|
||||
return Fail(FT.Internal, 'Something went wrong');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +125,7 @@ export class ApiService {
|
|||
const response = await this.fetch(url, options);
|
||||
if (HasFailed(response)) return response;
|
||||
|
||||
if (!response.ok) return Fail('Recieved a non-ok response');
|
||||
if (!response.ok) return Fail(FT.Network, 'Recieved a non-ok response');
|
||||
|
||||
const mimeType = response.headers.get('Content-Type') ?? 'other/unknown';
|
||||
let name = response.headers.get('Content-Disposition');
|
||||
|
@ -150,7 +151,7 @@ export class ApiService {
|
|||
};
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
return Fail('Something went wrong');
|
||||
return Fail(FT.Internal, 'Something went wrong');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,7 +162,7 @@ export class ApiService {
|
|||
const response = await this.fetch(url, options);
|
||||
if (HasFailed(response)) return response;
|
||||
|
||||
if (!response.ok) return Fail('Recieved a non-ok response');
|
||||
if (!response.ok) return Fail(FT.Network, 'Recieved a non-ok response');
|
||||
|
||||
return response.headers;
|
||||
}
|
||||
|
@ -186,7 +187,7 @@ export class ApiService {
|
|||
error: e,
|
||||
url,
|
||||
});
|
||||
return Fail('Network Error');
|
||||
return Fail(FT.Network, 'Network Error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import { ImageLinks } from 'picsur-shared/dist/dto/image-links.class';
|
|||
import { Mime2Ext } from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
||||
import { AsyncFailable } from 'picsur-shared/dist/types';
|
||||
import { Fail, HasFailed, Open } from 'picsur-shared/dist/types/failable';
|
||||
import { Fail, FT, HasFailed, Open } from 'picsur-shared/dist/types/failable';
|
||||
import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto';
|
||||
import { ApiService } from './api.service';
|
||||
import { UserService } from './user.service';
|
||||
|
@ -68,7 +68,7 @@ export class ImageService {
|
|||
): AsyncFailable<ImageListResponse> {
|
||||
const userID = await this.userService.snapshot?.id;
|
||||
if (userID === undefined) {
|
||||
return Fail('User not logged in');
|
||||
return Fail(FT.Authentication, 'User not logged in');
|
||||
}
|
||||
|
||||
return await this.ListAllImages(count, page, userID);
|
||||
|
@ -93,6 +93,7 @@ export class ImageService {
|
|||
|
||||
if (result.images.length !== 1) {
|
||||
return Fail(
|
||||
FT.Unknown,
|
||||
`Image ${image} was not deleted, probably lacking permissions`,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Inject, Injectable } from '@angular/core';
|
||||
import { HISTORY } from '@ng-web-apis/common';
|
||||
import { InfoResponse } from 'picsur-shared/dist/dto/api/info.dto';
|
||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { SemVerRegex } from 'picsur-shared/dist/util/common-regex';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto';
|
||||
|
@ -53,7 +53,10 @@ export class InfoService {
|
|||
const clientVersion = this.getFrontendVersion();
|
||||
|
||||
if (!SemVerRegex.test(serverVersion) || !SemVerRegex.test(clientVersion)) {
|
||||
return Fail(`Not a valid semver: ${serverVersion} or ${clientVersion}`);
|
||||
return Fail(
|
||||
FT.SysValidation,
|
||||
`Not a valid semver: ${serverVersion} or ${clientVersion}`,
|
||||
);
|
||||
}
|
||||
|
||||
const serverDecoded = serverVersion.split('.');
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
DecodedPref,
|
||||
PrefValueType
|
||||
} from 'picsur-shared/dist/dto/preferences.dto';
|
||||
import { AsyncFailable, Fail, HasFailed, Map } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT, HasFailed, Map } from 'picsur-shared/dist/types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto';
|
||||
import { Throttle } from 'src/app/util/throttle';
|
||||
|
@ -59,7 +59,10 @@ export class SysPrefService {
|
|||
|
||||
public async getPreferences(): AsyncFailable<DecodedPref[]> {
|
||||
if (!this.hasPermission)
|
||||
return Fail('You do not have permission to edit system preferences');
|
||||
return Fail(
|
||||
FT.Permission,
|
||||
'You do not have permission to edit system preferences',
|
||||
);
|
||||
|
||||
const response = await this.api.get(
|
||||
MultiplePreferencesResponse,
|
||||
|
@ -76,7 +79,10 @@ export class SysPrefService {
|
|||
key: string,
|
||||
): AsyncFailable<GetPreferenceResponse> {
|
||||
if (!this.hasPermission)
|
||||
return Fail('You do not have permission to edit system preferences');
|
||||
return Fail(
|
||||
FT.Permission,
|
||||
'You do not have permission to edit system preferences',
|
||||
);
|
||||
|
||||
const response = await this.api.get(
|
||||
GetPreferenceResponse,
|
||||
|
@ -92,7 +98,10 @@ export class SysPrefService {
|
|||
value: PrefValueType,
|
||||
): AsyncFailable<UpdatePreferenceResponse> {
|
||||
if (!this.hasPermission)
|
||||
return Fail('You do not have permission to edit system preferences');
|
||||
return Fail(
|
||||
FT.Permission,
|
||||
'You do not have permission to edit system preferences',
|
||||
);
|
||||
|
||||
const response = await this.api.post(
|
||||
UpdatePreferenceRequest,
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||
import { JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
|
||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Logger } from '../logger/logger.service';
|
||||
import { KeyService } from '../storage/key.service';
|
||||
|
@ -108,7 +108,7 @@ export class UserService {
|
|||
this.userSubject.next(null);
|
||||
|
||||
if (value === null) {
|
||||
return Fail('Not logged in');
|
||||
return Fail(FT.Impossible, 'Not logged in');
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
@ -120,13 +120,13 @@ export class UserService {
|
|||
try {
|
||||
decoded = jwt_decode(token);
|
||||
} catch (e) {
|
||||
return Fail('Invalid token');
|
||||
return Fail(FT.UsrValidation, 'Invalid token');
|
||||
}
|
||||
|
||||
const result = JwtDataSchema.safeParse(decoded);
|
||||
if (!result.success) {
|
||||
this.logger.error(result.error);
|
||||
return Fail('Invalid token data');
|
||||
return Fail(FT.UsrValidation, 'Invalid token data');
|
||||
}
|
||||
|
||||
return result.data.user;
|
||||
|
|
|
@ -11,7 +11,13 @@ import {
|
|||
DecodedPref,
|
||||
PrefValueType
|
||||
} from 'picsur-shared/dist/dto/preferences.dto';
|
||||
import { AsyncFailable, Fail, HasFailed, Map } from 'picsur-shared/dist/types';
|
||||
import {
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
Map
|
||||
} from 'picsur-shared/dist/types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto';
|
||||
import { Throttle } from 'src/app/util/throttle';
|
||||
|
@ -59,7 +65,10 @@ export class UsrPrefService {
|
|||
|
||||
public async getPreferences(): AsyncFailable<DecodedPref[]> {
|
||||
if (!this.hasPermission)
|
||||
return Fail('You do not have permission to edit user preferences');
|
||||
return Fail(
|
||||
FT.Permission,
|
||||
'You do not have permission to edit user preferences',
|
||||
);
|
||||
|
||||
const response = await this.api.get(
|
||||
MultiplePreferencesResponse,
|
||||
|
@ -76,7 +85,10 @@ export class UsrPrefService {
|
|||
key: string,
|
||||
): AsyncFailable<GetPreferenceResponse> {
|
||||
if (!this.hasPermission)
|
||||
return Fail('You do not have permission to edit user preferences');
|
||||
return Fail(
|
||||
FT.Permission,
|
||||
'You do not have permission to edit user preferences',
|
||||
);
|
||||
|
||||
const response = await this.api.get(
|
||||
GetPreferenceResponse,
|
||||
|
@ -92,7 +104,10 @@ export class UsrPrefService {
|
|||
value: PrefValueType,
|
||||
): AsyncFailable<UpdatePreferenceResponse> {
|
||||
if (!this.hasPermission)
|
||||
return Fail('You do not have permission to edit user preferences');
|
||||
return Fail(
|
||||
FT.Permission,
|
||||
'You do not have permission to edit user preferences',
|
||||
);
|
||||
|
||||
const response = await this.api.post(
|
||||
UpdatePreferenceRequest,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
|
||||
import { QOIdecodeJS } from '../util/qoi/qoi-decode';
|
||||
import { QOIImage } from './qoi-worker.dto';
|
||||
|
||||
|
@ -13,7 +13,7 @@ export default async function qoiDecodeJob(
|
|||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
return Fail('Could not fetch image');
|
||||
return Fail(FT.Network, 'Could not fetch image');
|
||||
}
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
|
@ -32,6 +32,6 @@ export default async function qoiDecodeJob(
|
|||
height: image.height,
|
||||
};
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
return Fail(FT.Internal, e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ const ApiErrorResponse = ApiResponseBase.merge(
|
|||
z.object({
|
||||
success: z.literal(false),
|
||||
data: z.object({
|
||||
type: z.string(),
|
||||
message: z.string(),
|
||||
}),
|
||||
}),
|
||||
|
|
|
@ -3,20 +3,108 @@
|
|||
// Since now they dont just come out of nowhere
|
||||
// -> Side effects go brrr
|
||||
|
||||
// Failuretype
|
||||
export enum FT {
|
||||
Unknown = 'unknown',
|
||||
Database = 'database',
|
||||
SysValidation = 'sysvalidation',
|
||||
UsrValidation = 'usrvalidation',
|
||||
Permission = 'permission',
|
||||
NotFound = 'notFound',
|
||||
Conflict = 'conflict',
|
||||
Internal = 'internal',
|
||||
Authentication = 'authentication',
|
||||
Impossible = 'impossible',
|
||||
Network = 'network',
|
||||
}
|
||||
|
||||
interface FTProp {
|
||||
important: boolean;
|
||||
code: number;
|
||||
}
|
||||
|
||||
const FTProps: {
|
||||
[key in FT]: FTProp;
|
||||
} = {
|
||||
[FT.Unknown]: {
|
||||
important: false,
|
||||
code: 500,
|
||||
},
|
||||
[FT.Internal]: {
|
||||
important: true,
|
||||
code: 500,
|
||||
},
|
||||
[FT.Database]: {
|
||||
important: true,
|
||||
code: 500,
|
||||
},
|
||||
[FT.Network]: {
|
||||
important: true,
|
||||
code: 500,
|
||||
},
|
||||
[FT.SysValidation]: {
|
||||
important: true,
|
||||
code: 500,
|
||||
},
|
||||
[FT.UsrValidation]: {
|
||||
important: false,
|
||||
code: 400,
|
||||
},
|
||||
[FT.Permission]: {
|
||||
important: false,
|
||||
code: 403,
|
||||
},
|
||||
[FT.NotFound]: {
|
||||
important: false,
|
||||
code: 404,
|
||||
},
|
||||
[FT.Conflict]: {
|
||||
important: false,
|
||||
code: 409,
|
||||
},
|
||||
[FT.Authentication]: {
|
||||
important: false,
|
||||
code: 200,
|
||||
} ,
|
||||
[FT.Impossible]: {
|
||||
important: true,
|
||||
code: 422,
|
||||
} ,
|
||||
};
|
||||
|
||||
export class Failure {
|
||||
private __68351953531423479708__id_failure = 1148363914;
|
||||
|
||||
constructor(
|
||||
private readonly reason?: string,
|
||||
private readonly stack?: string,
|
||||
private readonly type: FT = FT.Unknown,
|
||||
) {}
|
||||
|
||||
getReason(): string {
|
||||
return this.reason ?? 'Unknown';
|
||||
}
|
||||
|
||||
getStack(): string {
|
||||
return this.stack ?? 'None';
|
||||
getStack(): string | undefined {
|
||||
return this.stack;
|
||||
}
|
||||
|
||||
getType(): FT {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
const capitalizedType =
|
||||
this.type.charAt(0).toUpperCase() + this.type.slice(1);
|
||||
return `${capitalizedType}Failure`;
|
||||
}
|
||||
|
||||
getCode(): number {
|
||||
return FTProps[this.type].code;
|
||||
}
|
||||
|
||||
isImportant() {
|
||||
return FTProps[this.type].important;
|
||||
}
|
||||
|
||||
static deserialize(data: any): Failure {
|
||||
|
@ -24,22 +112,26 @@ export class Failure {
|
|||
throw new Error('Invalid failure data');
|
||||
}
|
||||
|
||||
return new Failure(data.reason, data.stack);
|
||||
return new Failure(data.reason, data.stack, data.type);
|
||||
}
|
||||
}
|
||||
|
||||
export function Fail(reason?: any): Failure {
|
||||
export function Fail(type: FT, reason: any): Failure {
|
||||
if (typeof reason === 'string') {
|
||||
return new Failure(reason);
|
||||
return new Failure(reason, undefined, type);
|
||||
} else if (reason instanceof Error) {
|
||||
return new Failure(reason.message, reason.stack);
|
||||
return new Failure(reason.message, reason.stack, type);
|
||||
} else if (reason instanceof Failure) {
|
||||
return reason;
|
||||
throw new Error('Cannot fail with a failure, just return it');
|
||||
} else {
|
||||
return new Failure('Converted(' + reason + ')');
|
||||
return new Failure('Unkown reason', undefined, type);
|
||||
}
|
||||
}
|
||||
|
||||
export function IsFailure(value: any): value is Failure {
|
||||
return value.__68351953531423479708__id_failure === 1148363914;
|
||||
}
|
||||
|
||||
export type Failable<T> = T | Failure;
|
||||
|
||||
export type AsyncFailable<T> = Promise<Failable<T>>;
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
SupportedImageMimes,
|
||||
SupportedMimeCategory
|
||||
} from '../dto/mimes.dto';
|
||||
import { Fail, Failable } from '../types';
|
||||
import { Fail, Failable, FT } from '../types';
|
||||
|
||||
export function ParseMime(mime: string): Failable<FullMime> {
|
||||
if (SupportedImageMimes.includes(mime))
|
||||
|
@ -13,5 +13,5 @@ export function ParseMime(mime: string): Failable<FullMime> {
|
|||
if (SupportedAnimMimes.includes(mime))
|
||||
return { mime, type: SupportedMimeCategory.Animation };
|
||||
|
||||
return Fail('Unsupported mime type');
|
||||
return Fail(FT.Validation, 'Unsupported mime type');
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue