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