Compare commits

...

13 commits

Author SHA1 Message Date
Caramel 1e9dad9ac2
Update README.md 2023-07-06 13:51:47 +02:00
Caramel 441e7d2af1
fix docker build 2023-06-15 14:22:58 +02:00
Caramel 5195f1f5d8
Up version number to 0.5.2 2023-06-15 13:28:28 +02:00
Caramel b0c53850db
update packages 2023-06-15 13:27:30 +02:00
Caramel e2de37a6dd
Fix build for node 20 2023-06-15 13:24:34 +02:00
Caramel b8db72bac4
Change some typescript compilation options and fix eslint 2023-06-15 13:09:23 +02:00
Caramel 3b6244461e
Fix eslint config 2023-06-09 10:35:40 +02:00
Caramel 2a11f162b3
update packages to mitigate security issue 2023-06-07 23:12:00 +02:00
Caramel e09f661cdf
Fix clipboard on webkit 2023-06-06 00:39:59 +02:00
Caramel 409c3af475
Fix icons 2023-06-02 17:00:12 +02:00
Caramel 18bee2e0cd
Update packages 2023-06-02 16:35:31 +02:00
Caramel 5e6c12ce6d
Update README.md 2023-05-11 15:17:35 +02:00
Caramel cea5443309
Fix link in readme 2023-04-20 19:44:27 +02:00
187 changed files with 5438 additions and 3524 deletions

View file

@ -1,24 +1,22 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
ignorePatterns: ['.eslintrc.cjs', 'dist', '*.exclude.*'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
},
root: true,
};

2
.nvmrc
View file

@ -1 +1 @@
v18.8
v20

4
.yarn/versions/e0bbb8ad.yml vendored Normal file
View file

@ -0,0 +1,4 @@
undecided:
- root-workspace-0b6124
- picsur-backend
- picsur-frontend

View file

@ -107,6 +107,10 @@ QOI is a new lossless image format that is designed to be very fast to encode an
You can [read more about QOI here](https://qoiformat.org/).
### What is the default admin login?
The default username is `admin`, and the default password is set from the `PICSUR_ADMIN_PASSWORD` environment variable.
## Running your own instance
You easily run this service yourself via Docker. Here is an example docker-compose file:
@ -129,6 +133,7 @@ services:
# PICSUR_DB_PASSWORD: picsur
# PICSUR_DB_DATABASE: picsur
## The default username is admin, this is not modifyable
# PICSUR_ADMIN_PASSWORD: picsur
## Optional, random secret will be generated if not set
@ -159,12 +164,13 @@ volumes:
## Thanks
- @chennin for monthly donating 4$
- @awg13 for donating 5$
## Api
Here is a usually up to date documentation of the api:
[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/rubikscraft-team/workspace/picsur/collection/1841871-78e559b6-4f39-4092-87c3-92fa29547d03)
[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/caramel-team/workspace/picsur/collection/1841871-78e559b6-4f39-4092-87c3-92fa29547d03)
If you wish to build your own frontend or app for picsur, this will surely come in handy. Also take a look at the `./shared` folder in the source code, as it contains typescript schema definitions for the api.

9
backend/.eslintrc.cjs Normal file
View file

@ -0,0 +1,9 @@
module.exports = {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
extends: ['../.eslintrc.cjs'],
root: false,
};

View file

@ -1,6 +1,6 @@
{
"name": "picsur-backend",
"version": "0.5.1",
"version": "0.5.2",
"description": "Backend for Picsur",
"license": "GPL-3.0",
"repository": "https://github.com/caramelfur/Picsur",
@ -10,81 +10,76 @@
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"start": "nest start --exec \"node --es-module-specifier-resolution=node\"",
"start:dev": "yarn clean && nest start --watch --exec \"node --es-module-specifier-resolution=node\"",
"start:debug": "nest start --debug --watch --exec \"node --es-module-specifier-resolution=node\"",
"start:prod": "node --es-module-specifier-resolution=node dist/main",
"start": "nest start --exec \"node --experimental-loader=extensionless\"",
"start:dev": "yarn clean && nest start --watch --exec \"node --experimental-loader=extensionless\"",
"start:debug": "nest start --debug --watch --exec \"node --experimental-loader=extensionless\"",
"start:prod": "node --experimental-loader=extensionless dist/main",
"typeorm": "typeorm-ts-node-esm",
"migrate": "PICSUR_PRODUCTION=\"true\" yarn typeorm migration:generate -d ./src/datasource.ts",
"format": "prettier --write \"src/**/*.ts\"",
"clean": "rimraf dist",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"purge": "rm -rf dist && rm -rf node_modules"
},
"dependencies": {
"@fastify/helmet": "^10.1.0",
"@fastify/multipart": "^7.5.0",
"@fastify/reply-from": "^9.0.1",
"@fastify/static": "^6.9.0",
"@nestjs/common": "^9.3.9",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.3.9",
"@nestjs/jwt": "^10.0.2",
"@fastify/helmet": "^10.1.1",
"@fastify/multipart": "^7.6.1",
"@fastify/reply-from": "^9.3.0",
"@fastify/static": "^6.10.2",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^2.3.4",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.1.0",
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-fastify": "^9.3.9",
"@nestjs/schedule": "^2.2.0",
"@nestjs/serve-static": "^3.0.1",
"@nestjs/platform-fastify": "^10.0.0",
"@nestjs/schedule": "^3.0.0",
"@nestjs/serve-static": "^4.0.0",
"@nestjs/throttler": "^4.0.0",
"@nestjs/typeorm": "^9.0.1",
"bcrypt": "^5.1.0",
"bmp-img": "^1.2.1",
"cors": "^2.8.5",
"file-type": "^18.2.1",
"extensionless": "^1.4.5",
"file-type": "^18.5.0",
"is-docker": "^3.0.0",
"ms": "^2.1.3",
"ms": "2.1.3",
"node-fetch": "^3.3.1",
"p-timeout": "^6.1.1",
"p-timeout": "^6.1.2",
"passport": "^0.6.0",
"passport-headerapikey": "^1.2.2",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"passport-strategy": "^1.0.0",
"pg": "^8.10.0",
"pg": "^8.11.0",
"picsur-shared": "*",
"posix.js": "^0.1.1",
"qoi-img": "^2.1.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^4.4.0",
"rxjs": "^7.8.0",
"sharp": "^0.31.3",
"rimraf": "^5.0.1",
"rxjs": "^7.8.1",
"sharp": "^0.32.1",
"stream-parser": "^0.3.1",
"thunks": "^4.9.6",
"typeorm": "0.3.12",
"typeorm": "0.3.16",
"zod": "^3.21.4"
},
"devDependencies": {
"@nestjs/cli": "^9.2.0",
"@nestjs/schematics": "^9.0.4",
"@nestjs/testing": "^9.3.9",
"@nestjs/cli": "^10.0.1",
"@nestjs/schematics": "^10.0.1",
"@nestjs/testing": "^10.0.0",
"@types/bcrypt": "^5.0.0",
"@types/cors": "^2.8.13",
"@types/multer": "^1.4.7",
"@types/node": "^18.15.3",
"@types/node": "^20.3.1",
"@types/passport-jwt": "^3.0.8",
"@types/passport-local": "^1.0.35",
"@types/passport-strategy": "^0.2.35",
"@types/sharp": "^0.31.1",
"@types/sharp": "^0.32.0",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.7.0",
"eslint-plugin-prettier": "^4.2.1",
"prettier": "^2.8.4",
"prettier": "^2.8.8",
"source-map-support": "^0.5.21",
"ts-loader": "^9.4.2",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.2",
"typescript": "^4.9.5"
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
}
}

View file

@ -30,7 +30,7 @@ const imageCorsConfig = cors({
const imageCorsOverride = (
req: IncomingMessage,
res: ServerResponse,
next: Function,
next: () => void,
) => {
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');

View file

@ -1,6 +1,11 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
} from 'picsur-shared/dist/types/failable';
import { FindResult } from 'picsur-shared/dist/types/find-result';
import { generateRandomString } from 'picsur-shared/dist/util/random';
import { Repository } from 'typeorm';

View file

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types/failable';
import { FindResult } from 'picsur-shared/dist/types/find-result';
import { generateRandomString } from 'picsur-shared/dist/util/random';
import { In, LessThan, Repository } from 'typeorm';

View file

@ -1,7 +1,12 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ImageEntryVariant } from 'picsur-shared/dist/dto/image-entry-variant.enum';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
} from 'picsur-shared/dist/types/failable';
import { LessThan, Repository } from 'typeorm';
import { EImageDerivativeBackend } from '../../database/entities/images/image-derivative.entity';
import { EImageFileBackend } from '../../database/entities/images/image-file.entity';

View file

@ -10,7 +10,7 @@ import {
Failable,
FT,
HasFailed,
} from 'picsur-shared/dist/types';
} from 'picsur-shared/dist/types/failable';
type Enum = Record<string, string>;
type EnumValue<E> = E[keyof E];

View file

@ -11,7 +11,12 @@ import {
SysPreferenceValidators,
SysPreferenceValueTypes,
} from 'picsur-shared/dist/dto/sys-preferences.enum';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
} from 'picsur-shared/dist/types/failable';
import { Repository } from 'typeorm';
import {
ESysPreferenceBackend,
@ -37,7 +42,7 @@ export class SysPreferenceDbService {
value: PrefValueType,
): AsyncFailable<DecodedSysPref> {
// Validate
let sysPreference = await this.encodeSysPref(key, value);
const sysPreference = await this.encodeSysPref(key, value);
if (HasFailed(sysPreference)) return sysPreference;
// Set
@ -60,7 +65,7 @@ export class SysPreferenceDbService {
public async getPreference(key: string): AsyncFailable<DecodedSysPref> {
// Validate
let validatedKey = this.prefCommon.validatePrefKey(key, SysPreference);
const validatedKey = this.prefCommon.validatePrefKey(key, SysPreference);
if (HasFailed(validatedKey)) return validatedKey;
// See the comment in 'mutex-fallback.ts' for why we are using a mutex here
@ -112,7 +117,7 @@ export class SysPreferenceDbService {
key: string,
type: PrefValueTypeStrings,
): AsyncFailable<PrefValueType> {
let pref = await this.getPreference(key);
const pref = await this.getPreference(key);
if (HasFailed(pref)) return pref;
if (pref.type !== type)
return Fail(FT.UsrValidation, 'Invalid preference type');
@ -122,7 +127,7 @@ export class SysPreferenceDbService {
public async getAllPreferences(): AsyncFailable<DecodedSysPref[]> {
// TODO: We are fetching each value invidually, we should fetch all at once
let internalSysPrefs = await Promise.all(
const internalSysPrefs = await Promise.all(
SysPreferenceList.map((key) => this.getPreference(key)),
);
if (internalSysPrefs.some((pref) => HasFailed(pref))) {
@ -158,7 +163,7 @@ export class SysPreferenceDbService {
return Fail(FT.UsrValidation, undefined, valueValidated.error);
}
let verifySysPreference = new ESysPreferenceBackend();
const verifySysPreference = new ESysPreferenceBackend();
verifySysPreference.key = validated.key;
verifySysPreference.value = validated.value;

View file

@ -11,7 +11,12 @@ import {
UsrPreferenceValidators,
UsrPreferenceValueTypes,
} from 'picsur-shared/dist/dto/usr-preferences.enum';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
} from 'picsur-shared/dist/types/failable';
import { Repository } from 'typeorm';
import {
EUsrPreferenceBackend,
@ -38,7 +43,7 @@ export class UsrPreferenceDbService {
value: PrefValueType,
): AsyncFailable<DecodedUsrPref> {
// Validate
let usrPreference = await this.encodeUsrPref(userid, key, value);
const usrPreference = await this.encodeUsrPref(userid, key, value);
if (HasFailed(usrPreference)) return usrPreference;
// Set
@ -66,7 +71,7 @@ export class UsrPreferenceDbService {
key: string,
): AsyncFailable<DecodedUsrPref> {
// Validate
let validatedKey = this.prefCommon.validatePrefKey(key, UsrPreference);
const validatedKey = this.prefCommon.validatePrefKey(key, UsrPreference);
if (HasFailed(validatedKey)) return validatedKey;
// See the comment in 'mutex-fallback.ts' for why we are using a mutex here
@ -145,7 +150,7 @@ export class UsrPreferenceDbService {
key: string,
type: PrefValueTypeStrings,
): AsyncFailable<PrefValueType> {
let pref = await this.getPreference(userid, key);
const pref = await this.getPreference(userid, key);
if (HasFailed(pref)) return pref;
if (pref.type !== type)
return Fail(FT.UsrValidation, 'Invalid preference type');
@ -157,7 +162,7 @@ export class UsrPreferenceDbService {
userid: string,
): AsyncFailable<DecodedUsrPref[]> {
// TODO: We are fetching each value invidually, we should fetch all at once
let internalSysPrefs = await Promise.all(
const internalSysPrefs = await Promise.all(
UsrPreferenceList.map((key) => this.getPreference(userid, key)),
);
if (internalSysPrefs.some((pref) => HasFailed(pref))) {
@ -199,7 +204,7 @@ export class UsrPreferenceDbService {
return Fail(FT.UsrValidation, undefined, valueValidated.error);
}
let verifySysPreference = new EUsrPreferenceBackend();
const verifySysPreference = new EUsrPreferenceBackend();
verifySysPreference.key = validated.key;
verifySysPreference.value = validated.value;
verifySysPreference.user_id = userid;

View file

@ -1,6 +1,6 @@
import { Logger, Module, OnModuleInit } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { HasFailed } from 'picsur-shared/dist/types';
import { HasFailed } from 'picsur-shared/dist/types/failable';
import { EarlyConfigModule } from '../../config/early/early-config.module';
import { HostConfigService } from '../../config/early/host.config.service';
import { ERoleBackend } from '../../database/entities/users/role.entity';

View file

@ -7,7 +7,7 @@ import {
FT,
HasFailed,
HasSuccess,
} from 'picsur-shared/dist/types';
} from 'picsur-shared/dist/types/failable';
import { makeUnique } from 'picsur-shared/dist/util/unique';
import { In, Repository } from 'typeorm';
import { ERoleBackend } from '../../database/entities/users/role.entity';
@ -33,7 +33,7 @@ export class RoleDbService {
if (await this.exists(name))
return Fail(FT.Conflict, 'Role already exists');
let role = new ERoleBackend();
const role = new ERoleBackend();
role.name = name;
role.permissions = permissions;
@ -105,7 +105,7 @@ export class RoleDbService {
role: string | ERoleBackend,
permissions: Permissions,
// Extra bypass for internal use
allowImmutable: boolean = false,
allowImmutable = false,
): AsyncFailable<ERoleBackend> {
const roleToModify = await this.resolve(role);
if (HasFailed(roleToModify)) return roleToModify;
@ -166,7 +166,7 @@ export class RoleDbService {
return HasSuccess(await this.findOne(name));
}
public async nukeSystemRoles(IAmSure: boolean = false): AsyncFailable<true> {
public async nukeSystemRoles(IAmSure = false): AsyncFailable<true> {
if (!IAmSure)
return Fail(
FT.SysValidation,

View file

@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types/failable';
import { Repository } from 'typeorm';
import { ESystemStateBackend } from '../../database/entities/system/system-state.entity';

View file

@ -1,6 +1,6 @@
import { Logger, Module, OnModuleInit } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { HasFailed } from 'picsur-shared/dist/types';
import { HasFailed } from 'picsur-shared/dist/types/failable';
import { generateRandomString } from 'picsur-shared/dist/util/random';
import { AuthConfigService } from '../../config/early/auth.config.service';
import { EarlyConfigModule } from '../../config/early/early-config.module';

View file

@ -8,7 +8,7 @@ import {
FT,
HasFailed,
HasSuccess,
} from 'picsur-shared/dist/types';
} from 'picsur-shared/dist/types/failable';
import { FindResult } from 'picsur-shared/dist/types/find-result';
import { makeUnique } from 'picsur-shared/dist/util/unique';
import { Repository } from 'typeorm';
@ -53,7 +53,7 @@ export class UserDbService {
const strength = await this.getBCryptStrength();
const hashedPassword = await bcrypt.hash(password, strength);
let user = new EUserBackend();
const user = new EUserBackend();
user.username = username;
user.hashed_password = hashedPassword;
if (byPassRoleCheck) {
@ -208,7 +208,7 @@ export class UserDbService {
username: string,
// Also fetch fields that aren't normally sent to the client
// (e.g. hashed password)
getPrivate: boolean = false,
getPrivate = false,
): AsyncFailable<EUserBackend> {
try {
const found = await this.usersRepository.findOne({

View file

@ -2,8 +2,8 @@ import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';
import { ParseInt, ParseString } from 'picsur-shared/dist/util/parse-simple';
import { EntityList } from '../../database/entities';
import { MigrationList } from '../../database/migrations';
import { EntityList } from '../../database/entities/index';
import { MigrationList } from '../../database/migrations/index';
import { DefaultName, EnvPrefix } from '../config.static';
import { HostConfigService } from './host.config.service';
@ -48,10 +48,10 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory {
return varOptions;
}
public createTypeOrmOptions(connectionName?: string) {
public createTypeOrmOptions() {
const varOptions = this.getTypeOrmServerOptions();
return {
type: 'postgres' as 'postgres',
type: 'postgres' as const,
synchronize: !this.hostService.isProduction(),
migrationsRun: true,

View file

@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { HasFailed } from 'picsur-shared/dist/types';
import { HasFailed } from 'picsur-shared/dist/types/failable';
import { SysPreferenceDbService } from '../../collections/preference-db/sys-preference-db.service';
@Injectable()

View file

@ -1,7 +1,7 @@
import { FactoryProvider, Injectable, Logger } from '@nestjs/common';
import { JwtModuleOptions, JwtOptionsFactory } from '@nestjs/jwt';
import ms from 'ms';
import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { SysPreferenceDbService } from '../../collections/preference-db/sys-preference-db.service';
@Injectable()

View file

@ -1,6 +1,11 @@
import { Injectable } from '@nestjs/common';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
} from 'picsur-shared/dist/types/failable';
import { URLRegex, UUIDRegex } from 'picsur-shared/dist/util/common-regex';
import { SysPreferenceDbService } from '../../collections/preference-db/sys-preference-db.service';
import { ReportInterval, ReportUrl } from '../config.static';

View file

@ -1,6 +1,6 @@
import { IsEntityID } from 'picsur-shared/dist/validators/entity-id.validator';
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
import z from 'zod';
import * as z from 'zod';
export const ESysPreferenceSchema = z.object({
id: IsEntityID().optional(),

View file

@ -8,7 +8,7 @@ import {
PrimaryGeneratedColumn,
Unique,
} from 'typeorm';
import z from 'zod';
import * as z from 'zod';
import { EUserBackend } from '../users/user.entity';
export const EUsrPreferenceSchema = z.object({

View file

@ -1,3 +1,4 @@
import { MigrationInterface } from 'typeorm';
import { V030A1661692206479 } from './1661692206479-V_0_3_0_a';
import { V032A1662029904716 } from './1662029904716-V_0_3_2_a';
import { V040A1662314197741 } from './1662314197741-V_0_4_0_a';
@ -5,8 +6,9 @@ import { V040B1662485374471 } from './1662485374471-V_0_4_0_b';
import { V040C1662535484200 } from './1662535484200-V_0_4_0_c';
import { V040D1662728275448 } from './1662728275448-V_0_4_0_d';
import { V050A1672154027079 } from './1672154027079-V_0_5_0_a';
import { Newable } from 'picsur-shared/dist/types/newable.js';
export const MigrationList: Function[] = [
export const MigrationList: Newable<MigrationInterface>[] = [
V030A1661692206479,
V032A1662029904716,
V040A1662314197741,

View file

@ -1,12 +1,12 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import { Injectable, PipeTransform } from '@nestjs/common';
import { Ext2FileType } from 'picsur-shared/dist/dto/mimes.dto';
import { Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { FT, Fail, HasFailed } from 'picsur-shared/dist/types/failable';
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
import { ImageFullId } from '../../models/constants/image-full-id.const';
@Injectable()
export class ImageFullIdPipe implements PipeTransform<string, ImageFullId> {
transform(value: string, metadata: ArgumentMetadata): ImageFullId {
transform(value: string): ImageFullId {
const split = value.split('.');
if (split.length === 2) {
const [id, ext] = split;

View file

@ -1,10 +1,10 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import { Injectable, PipeTransform } from '@nestjs/common';
import { FT, Fail } from 'picsur-shared/dist/types/failable';
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
@Injectable()
export class ImageIdPipe implements PipeTransform<string, string> {
transform(value: string, metadata: ArgumentMetadata): string {
transform(value: string): string {
if (UUIDRegex.test(value)) return value;
throw Fail(FT.UsrValidation, 'Invalid image id');
}

View file

@ -1,7 +1,7 @@
import { Multipart, MultipartFile } from '@fastify/multipart';
import { Injectable, Logger, PipeTransform, Scope } from '@nestjs/common';
import { FastifyRequest } from 'fastify';
import { Fail, FT } from 'picsur-shared/dist/types';
import { Fail, FT } from 'picsur-shared/dist/types/failable';
import { MultipartConfigService } from '../../config/early/multipart.config.service';
@Injectable({ scope: Scope.REQUEST })
@ -12,7 +12,7 @@ export class PostFilePipe implements PipeTransform {
private readonly multipartConfigService: MultipartConfigService,
) {}
async transform({ request, data }: { data: any; request: FastifyRequest }) {
async transform({ request }: { request: FastifyRequest }) {
if (!request.isMultipart()) throw Fail(FT.UsrValidation, 'Invalid file');
// Only one file is allowed

View file

@ -1,13 +1,7 @@
import { MultipartFile } from '@fastify/multipart';
import {
ArgumentMetadata,
Injectable,
Logger,
PipeTransform,
Scope,
} from '@nestjs/common';
import { Injectable, Logger, PipeTransform, Scope } from '@nestjs/common';
import { FastifyRequest } from 'fastify';
import { Fail, FT } from 'picsur-shared/dist/types';
import { FT, Fail } from 'picsur-shared/dist/types/failable';
import { MultipartConfigService } from '../../config/early/multipart.config.service';
export type FileIterator = AsyncIterableIterator<MultipartFile>;
@ -20,10 +14,7 @@ export class MultiPartPipe implements PipeTransform {
private readonly multipartConfigService: MultipartConfigService,
) {}
async transform<T extends Object>(
{ request, data }: { data: any; request: FastifyRequest },
metadata: ArgumentMetadata,
) {
async transform({ request, data }: { data: any; request: FastifyRequest }) {
const filesLimit = typeof data === 'number' ? data : undefined;
if (!request.isMultipart()) throw Fail(FT.UsrValidation, 'Invalid files');

View file

@ -4,7 +4,7 @@ import {
SetMetadata,
UseGuards,
} from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import { Fail, FT } from 'picsur-shared/dist/types/failable';
import { CombineFCDecorators } from 'picsur-shared/dist/util/decorator';
import { LocalAuthGuard } from '../managers/auth/guards/local-auth.guard';
import { Permission, Permissions } from '../models/constants/permissions.const';

View file

@ -1,5 +1,5 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import { Fail, FT } from 'picsur-shared/dist/types/failable';
import AuthFastifyRequest from '../models/interfaces/authrequest.dto';
export const ReqUser = createParamDecorator(

View file

@ -6,12 +6,12 @@ import { Newable } from 'picsur-shared/dist/types/newable';
type ReturnsMethodDecorator<ReturnType> = <
T extends (...args: any) => ReturnType | Promise<ReturnType>,
>(
target: Object,
target: object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T>,
) => TypedPropertyDescriptor<T> | void;
export function Returns<N extends Object>(
export function Returns<N extends object>(
newable: Newable<N>,
): ReturnsMethodDecorator<N> {
return SetMetadata('returns', newable);

View file

@ -23,7 +23,7 @@ import {
@Catch()
export class MainExceptionFilter implements ExceptionFilter {
private static readonly logger = new Logger(MainExceptionFilter.name);
private static readonly logger = new Logger('MainExceptionFilter');
catch(exception: Failure, host: ArgumentsHost) {
const ctx = host.switchToHttp();

View file

@ -9,7 +9,7 @@ import {
import { Reflector } from '@nestjs/core';
import { FastifyReply } from 'fastify';
import { ApiAnySuccessResponse } from 'picsur-shared/dist/dto/api/api.dto';
import { Fail, FT } from 'picsur-shared/dist/types';
import { Fail, FT } from 'picsur-shared/dist/types/failable';
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
import { map, Observable } from 'rxjs';
@ -20,7 +20,7 @@ export interface ZodValidationInterceptorOptions {
}
@Injectable()
export class SuccessInterceptor<T> implements NestInterceptor {
export class SuccessInterceptor implements NestInterceptor {
private readonly logger = new Logger();
// TODO: make work
@ -82,7 +82,7 @@ export class SuccessInterceptor<T> implements NestInterceptor {
);
}
let schema = schemaStatic.zodSchema;
const schema = schemaStatic.zodSchema;
const parseResult = schema.safeParse(data);
if (!parseResult.success) {
@ -105,7 +105,7 @@ export class SuccessInterceptor<T> implements NestInterceptor {
const response = context.switchToHttp().getResponse<FastifyReply>();
const newResponse: ApiAnySuccessResponse = {
success: true as true, // really typescript
success: true as const, // really typescript
statusCode: response.statusCode,
timestamp: new Date().toISOString(),
timeMs: Math.round(response.getResponseTime()),

View file

@ -1,10 +1,10 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';
import { Fail, FT } from 'picsur-shared/dist/types';
import { FT, Fail } from 'picsur-shared/dist/types/failable';
@Injectable()
export class PicsurThrottlerGuard extends ThrottlerGuard {
protected override throwThrottlingException(context: ExecutionContext): void {
protected override throwThrottlingException(): void {
throw Fail(FT.RateLimit);
}
}

View file

@ -9,7 +9,7 @@ import {
Optional,
PipeTransform,
} from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import { Fail, FT } from 'picsur-shared/dist/types/failable';
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
export interface ZodValidationPipeOptions {
@ -30,7 +30,7 @@ export class ZodValidationPipe implements PipeTransform {
public transform(value: unknown, metadata: ArgumentMetadata): unknown {
if (!this.validateCustom && metadata.type === 'custom') return value;
let zodSchema = (metadata?.metatype as ZodDtoStatic)?.zodSchema;
const zodSchema = (metadata?.metatype as ZodDtoStatic)?.zodSchema;
if (zodSchema) {
const parseResult = zodSchema.safeParse(value);

View file

@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { JwtData, JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types/failable';
@Injectable()
export class AuthManagerService {

View file

@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { HeaderAPIKeyStrategy } from 'passport-headerapikey';
import { EUser, EUserSchema } from 'picsur-shared/dist/entities/user.entity';
import { HasFailed } from 'picsur-shared/dist/types';
import { HasFailed } from 'picsur-shared/dist/types/failable';
import { IsApiKey } from 'picsur-shared/dist/validators/api-key.validator';
import { ApiKeyDbService } from '../../../collections/apikey-db/apikey-db.service';
import { EUserBackend2EUser } from '../../../models/transformers/user.transformer';
@ -23,7 +23,7 @@ export class ApiKeyStrategy extends PassportStrategy(
false,
(
apikey: string,
verified: (err: Error | null, user?: Object, info?: Object) => void,
verified: (err: Error | null, user?: object, info?: object) => void,
) => {
this.validate(apikey)
.then((user) => {

View file

@ -8,11 +8,12 @@ import { ReqType } from './reqtype';
class GuestPassportStrategy extends Strategy {
// Will be overridden by the nest implementation
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async validate(req: ReqType): Promise<any> {
return undefined;
}
override async authenticate(req: ReqType, options?: any) {
override async authenticate(req: ReqType) {
const user = await this.validate(req);
this.success(user);
}
@ -28,7 +29,7 @@ export class GuestStrategy extends PassportStrategy(
}
// Return the guest user created by the guestservice
override async validate(payload: any): Promise<EUser> {
override async validate(): Promise<EUser> {
return EUserBackend2EUser(await this.guestService.getGuestUser());
}
}

View file

@ -3,7 +3,7 @@ import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy as JwtPassportStrategy } from 'passport-jwt';
import { JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { UserDbService } from '../../../collections/user-db/user-db.service';
import { EUserBackend2EUser } from '../../../models/transformers/user.transformer';

View file

@ -2,7 +2,10 @@ import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { AsyncFailable, ThrowIfFailed } from 'picsur-shared/dist/types';
import {
AsyncFailable,
ThrowIfFailed,
} from 'picsur-shared/dist/types/failable';
import { UserDbService } from '../../../collections/user-db/user-db.service';
import { EUserBackend2EUser } from '../../../models/transformers/user.transformer';

View file

@ -9,7 +9,7 @@ import {
FT,
HasFailed,
ThrowIfFailed,
} from 'picsur-shared/dist/types';
} from 'picsur-shared/dist/types/failable';
import { makeUnique } from 'picsur-shared/dist/util/unique';
import { UserDbService } from '../../../collections/user-db/user-db.service';
import { Permissions } from '../../../models/constants/permissions.const';

View file

@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { HasFailed } from 'picsur-shared/dist/types';
import { HasFailed } from 'picsur-shared/dist/types/failable';
import { UserDbService } from '../../collections/user-db/user-db.service';
import { EUserBackend } from '../../database/entities/users/user.entity';

View file

@ -6,7 +6,12 @@ import {
SupportedFileTypeCategory,
} from 'picsur-shared/dist/dto/mimes.dto';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
} from 'picsur-shared/dist/types/failable';
import { SharpOptions } from 'sharp';
import { SysPreferenceDbService } from '../../collections/preference-db/sys-preference-db.service';
import { SharpWrapper } from '../../workers/sharp.wrapper';

View file

@ -2,7 +2,7 @@ import { Logger, Module, OnModuleInit } from '@nestjs/common';
import { Interval } from '@nestjs/schedule';
import ms from 'ms';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { HasFailed } from 'picsur-shared/dist/types';
import { HasFailed } from 'picsur-shared/dist/types/failable';
import { ImageDBModule } from '../../collections/image-db/image-db.module';
import { ImageDBService } from '../../collections/image-db/image-db.service';
import { ImageFileDBService } from '../../collections/image-db/image-file-db.service';

View file

@ -5,7 +5,12 @@ import {
SupportedFileTypeCategory,
} from 'picsur-shared/dist/dto/mimes.dto';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
} from 'picsur-shared/dist/types/failable';
import { ParseFileType } from 'picsur-shared/dist/util/parse-mime';
import { ImageConverterService } from './image-converter.service';
import { ImageResult } from './imageresult';

View file

@ -1,5 +1,5 @@
import { Injectable, Logger } from '@nestjs/common';
import Crypto from 'crypto';
import { createHash } from 'crypto';
import { fileTypeFromBuffer, FileTypeResult } from 'file-type';
import { ImageRequestParams } from 'picsur-shared/dist/dto/api/image.dto';
import { ImageEntryVariant } from 'picsur-shared/dist/dto/image-entry-variant.enum';
@ -11,7 +11,12 @@ import {
} 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, FT, HasFailed } from 'picsur-shared/dist/types';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
} from 'picsur-shared/dist/types/failable';
import { FindResult } from 'picsur-shared/dist/types/find-result';
import { ParseFileType } from 'picsur-shared/dist/util/parse-mime';
import { IsQOI } from 'qoi-img';
@ -229,7 +234,7 @@ export class ImageManagerService {
}
return {
[ImageEntryVariant.MASTER]: result[ImageEntryVariant.MASTER]!,
[ImageEntryVariant.MASTER]: result[ImageEntryVariant.MASTER],
[ImageEntryVariant.ORIGINAL]: result[ImageEntryVariant.ORIGINAL],
};
}
@ -268,7 +273,7 @@ export class ImageManagerService {
private getConvertHash(options: object) {
// Return a sha256 hash of the stringified options
const stringified = JSON.stringify(options);
const hash = Crypto.createHash('sha256');
const hash = createHash('sha256');
hash.update(stringified);
const digest = hash.digest('hex');
return digest;

View file

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-empty-function */
// @ts-nocheck
/*
@ -310,8 +312,8 @@ export class WebPInfo extends StreamParserWritable {
['VP8', 'VP8L', 'ANMF'].map((t) => [t, true] as [ChunkType, true]),
);
private offset: number = 0;
private maxSeekableOffset: number = -1; // same as file size - 1
private offset = 0;
private maxSeekableOffset = -1; // same as file size - 1
private pending?: {
size: number;

View file

@ -1,8 +1,8 @@
import { Injectable, Logger } from '@nestjs/common';
import isDocker from 'is-docker';
import fetch from 'node-fetch';
import os from 'os';
import { FallbackIfFailed, HasFailed } from 'picsur-shared/dist/types';
import * as os from 'os';
import { FallbackIfFailed, HasFailed } from 'picsur-shared/dist/types/failable';
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
import { ImageDBService } from '../../collections/image-db/image-db.service';
import { SystemStateDbService } from '../../collections/system-state-db/system-state-db.service';

View file

@ -24,7 +24,7 @@ export const UndeletableRolesList: string[] = UndeletableRolesTuple;
export const SystemRolesList = UndeletableRolesList;
// Defaults
type SystemRole = typeof UndeletableRolesTuple[number];
type SystemRole = (typeof UndeletableRolesTuple)[number];
const SystemRoleDefaultsTyped: {
[key in SystemRole]: Permissions;
} = {

View file

@ -12,7 +12,7 @@ import {
ApiKeyUpdateResponse,
} from 'picsur-shared/dist/dto/api/apikeys.dto';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { ApiKeyDbService } from '../../../collections/apikey-db/apikey-db.service';
import {
HasPermission,

View file

@ -3,8 +3,6 @@ import { NoPermissions } from '../../../decorators/permissions.decorator';
@Controller('api/experiment')
@NoPermissions()
export class ExperimentController {
constructor() {}
// @Get()
// @Returns(UserInfoResponse)
// async testRoute(

View file

@ -11,7 +11,7 @@ import {
SupportedImageFileTypes,
} from 'picsur-shared/dist/dto/mimes.dto';
import { TrackingState } from 'picsur-shared/dist/dto/tracking-state.enum';
import { FallbackIfFailed } from 'picsur-shared/dist/types';
import { FallbackIfFailed } from 'picsur-shared/dist/types/failable';
import { HostConfigService } from '../../../config/early/host.config.service';
import { InfoConfigService } from '../../../config/late/info.config.service';
import { UsageConfigService } from '../../../config/late/usage.config.service';

View file

@ -6,7 +6,7 @@ import {
UpdatePreferenceRequest,
UpdatePreferenceResponse,
} from 'picsur-shared/dist/dto/api/pref.dto';
import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { SysPreferenceDbService } from '../../../collections/preference-db/sys-preference-db.service';
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator';

View file

@ -6,7 +6,7 @@ import {
UpdatePreferenceRequest,
UpdatePreferenceResponse,
} from 'picsur-shared/dist/dto/api/pref.dto';
import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { UsrPreferenceDbService } from '../../../collections/preference-db/usr-preference-db.service';
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { ReqUserID } from '../../../decorators/request-user.decorator';

View file

@ -12,7 +12,7 @@ import {
RoleUpdateResponse,
SpecialRolesResponse,
} from 'picsur-shared/dist/dto/api/roles.dto';
import { Fail, FT, ThrowIfFailed } from 'picsur-shared/dist/types';
import { Fail, FT, ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { RoleDbService } from '../../../collections/role-db/role-db.service';
import { UserDbService } from '../../../collections/user-db/user-db.service';
import { RequiredPermissions } from '../../../decorators/permissions.decorator';

View file

@ -1,7 +1,7 @@
import { Controller, Logger, Post, Req, Res } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { Fail, FT, ThrowIfFailed } from 'picsur-shared/dist/types';
import { Fail, FT, ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { UsageConfigService } from '../../../config/late/usage.config.service';
import { NoPermissions } from '../../../decorators/permissions.decorator';
import { ReturnsAnything } from '../../../decorators/returns.decorator';

View file

@ -13,7 +13,7 @@ import {
UserUpdateRequest,
UserUpdateResponse,
} from 'picsur-shared/dist/dto/api/user-manage.dto';
import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { UserDbService } from '../../../collections/user-db/user-db.service';
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator';

View file

@ -10,7 +10,7 @@ import {
UserRegisterResponse,
} from 'picsur-shared/dist/dto/api/user.dto';
import type { EUser } from 'picsur-shared/dist/entities/user.entity';
import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { UserDbService } from '../../../collections/user-db/user-db.service';
import {
NoPermissions,

View file

@ -21,7 +21,12 @@ import {
ImageUploadResponse,
} from 'picsur-shared/dist/dto/api/image-manage.dto';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { Fail, FT, HasFailed, ThrowIfFailed } from 'picsur-shared/dist/types';
import {
Fail,
FT,
HasFailed,
ThrowIfFailed,
} from 'picsur-shared/dist/types/failable';
import { PostFiles } from '../../decorators/multipart/multipart.decorator';
import type { FileIterator } from '../../decorators/multipart/postfiles.pipe';
import {

View file

@ -7,7 +7,11 @@ import {
} from 'picsur-shared/dist/dto/api/image.dto';
import { ImageEntryVariant } from 'picsur-shared/dist/dto/image-entry-variant.enum';
import { FileType2Mime } from 'picsur-shared/dist/dto/mimes.dto';
import { FT, IsFailure, ThrowIfFailed } from 'picsur-shared/dist/types';
import {
FT,
IsFailure,
ThrowIfFailed,
} from 'picsur-shared/dist/types/failable';
import { UserDbService } from '../../collections/user-db/user-db.service';
import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator';
import { ImageIdParam } from '../../decorators/image-id/image-id.decorator';

View file

@ -1,6 +1,6 @@
import { readFile } from 'fs/promises';
import { resolve } from 'path';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types/failable';
import { PackageRoot } from '../config/config.static';
export const BrandingPath = resolve(PackageRoot, '../branding');

View file

@ -1,4 +1,4 @@
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types/failable';
export async function GetNextAsync<T>(
iterator: AsyncIterableIterator<T>,

View file

@ -1,7 +1,7 @@
import { Logger } from '@nestjs/common';
import { ChildProcess, fork } from 'child_process';
import pTimeout from 'p-timeout';
import path from 'path';
import { dirname, join as pathJoin } from 'path';
import { FileType } from 'picsur-shared/dist/dto/mimes.dto';
import {
AsyncFailable,
@ -9,7 +9,7 @@ import {
Failable,
FT,
HasFailed,
} from 'picsur-shared/dist/types';
} from 'picsur-shared/dist/types/failable';
import { Sharp, SharpOptions } from 'sharp';
import {
SharpWorkerFinishOptions,
@ -22,13 +22,13 @@ import {
import { SharpResult } from './sharp/universal-sharp';
const moduleURL = new URL(import.meta.url);
const __dirname = path.dirname(moduleURL.pathname);
const __dirname = dirname(moduleURL.pathname);
export class SharpWrapper {
private readonly workerID: number = Math.floor(Math.random() * 100000);
private readonly logger: Logger = new Logger('SharpWrapper' + this.workerID);
private static readonly WORKER_PATH = path.join(
private static readonly WORKER_PATH = pathJoin(
__dirname,
'./sharp',
'sharp.worker.js',

View file

@ -1,5 +1,5 @@
import { FileType } from 'picsur-shared/dist/dto/mimes.dto';
import posix from 'posix.js';
import { setrlimit } from 'posix.js';
import { Sharp } from 'sharp';
import {
SharpWorkerFinishOptions,
@ -11,7 +11,7 @@ import {
import { UniversalSharpIn, UniversalSharpOut } from './universal-sharp';
export class SharpWorker {
private startTime: number = 0;
private startTime = 0;
private sharpi: Sharp | null = null;
constructor() {
@ -29,7 +29,7 @@ export class SharpWorker {
return this.purge('MEMORY_LIMIT_MB environment variable is not set');
}
posix.setrlimit('data', {
setrlimit('data', {
soft: 1000 * 1000 * memoryLimit,
hard: 1000 * 1000 * memoryLimit,
});

View file

@ -3,15 +3,13 @@
"include": ["src/**/*.ts", "src/**/*.d.ts"],
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"],
"compilerOptions": {
"target": "es2020",
"module": "es2020",
"lib": ["es2022"],
"module": "ES2022",
"target": "es2022",
"outDir": "./dist",
"declaration": true,
"sourceMap": true,
"emitDecoratorMetadata": true
},
"ts-node": {
"experimentalSpecifierResolution": "node"
}
}

View file

@ -8,8 +8,8 @@
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Chrome version
last 2 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions

9
frontend/.eslintrc.cjs Normal file
View file

@ -0,0 +1,9 @@
module.exports = {
parserOptions: {
project: './tsconfig.base.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
ignorePatterns: ['src/environments', 'custom-webpack.config.js'],
root: false,
};

View file

@ -6,4 +6,18 @@ export default {
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
// new BundleAnalyzerPlugin(),
],
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env']],
},
},
},
],
},
};

View file

@ -1,6 +1,6 @@
{
"name": "picsur-frontend",
"version": "0.5.1",
"version": "0.5.2",
"description": "Frontend for Picsur",
"license": "GPL-3.0",
"repository": "https://github.com/caramelfur/Picsur",
@ -14,36 +14,40 @@
"purge": "rm -rf dist && rm -rf node_modules && rm -rf .angular"
},
"devDependencies": {
"@angular-builders/custom-webpack": "^15.0.0",
"@angular-devkit/build-angular": "^15.2.3",
"@angular/animations": "^15.2.2",
"@angular/cdk": "^15.2.2",
"@angular/cli": "^15.2.3",
"@angular/common": "^15.2.2",
"@angular/compiler": "^15.2.2",
"@angular/compiler-cli": "^15.2.2",
"@angular/core": "^15.2.2",
"@angular/forms": "^15.2.2",
"@angular/material": "^15.2.2",
"@angular/platform-browser": "^15.2.2",
"@angular/platform-browser-dynamic": "^15.2.2",
"@angular/router": "^15.2.2",
"@fontsource/material-icons": "^4.5.4",
"@fontsource/material-icons-outlined": "^4.5.4",
"@fontsource/roboto": "^4.5.8",
"@angular-builders/custom-webpack": "^16.0.0",
"@angular-devkit/build-angular": "^16.1.0",
"@angular/animations": "^16.1.1",
"@angular/cdk": "^16.1.1",
"@angular/cli": "^16.1.0",
"@angular/common": "^16.1.1",
"@angular/compiler": "^16.1.1",
"@angular/compiler-cli": "^16.1.1",
"@angular/core": "^16.1.1",
"@angular/forms": "^16.1.1",
"@angular/material": "^16.1.1",
"@angular/platform-browser": "^16.1.1",
"@angular/platform-browser-dynamic": "^16.1.1",
"@angular/router": "^16.1.1",
"@babel/cli": "^7.22.5",
"@babel/core": "^7.22.5",
"@babel/preset-env": "^7.22.5",
"@fontsource/roboto": "^5.0.3",
"@ng-web-apis/common": "^2.1.0",
"@ng-web-apis/resize-observer": "^2.0.0",
"@ngui/common": "^1.0.0",
"@popperjs/core": "^2.11.8",
"@types/ackee-tracker": "^5.0.2",
"@types/node": "^18.15.3",
"@types/node": "^20.3.1",
"@types/resize-observer-browser": "^0.1.7",
"@types/validator": "^13.7.14",
"@types/validator": "^13.7.17",
"ackee-tracker": "^5.1.0",
"axios": "^1.3.4",
"bootstrap": "^5.2.3",
"caniuse-lite": "^1.0.30001466",
"axios": "^1.4.0",
"babel-loader": "^9.1.2",
"bootstrap": "^5.3.0",
"browserslist": "^4.21.8",
"caniuse-lite": "^1.0.30001503",
"fuse.js": "^6.6.2",
"jwt-decode": "^3.1.2",
"material-icons": "^1.13.8",
"moment": "^2.29.4",
"ng-mat-select-infinite-scroll": "^4.0.0",
"ngx-auto-unsubscribe-decorator": "^1.1.0",
@ -51,11 +55,14 @@
"ngx-moment": "^6.0.2",
"picsur-shared": "*",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"tslib": "^2.5.0",
"typescript": "^4.9.5",
"webpack-bundle-analyzer": "^4.8.0",
"rxjs": "^7.8.1",
"tslib": "^2.5.3",
"typescript": "^5.1.3",
"webpack-bundle-analyzer": "^4.9.0",
"zod": "^3.21.4",
"zone.js": "^0.13.0"
"zone.js": "^0.13.1"
},
"dependencies": {
"@leteu/jwt-decoder": "^1.0.4"
}
}

View file

@ -25,14 +25,14 @@ export class AppComponent implements OnInit {
@ViewChild(MatSidenav) sidebar: MatSidenav;
loading: boolean = false;
loading = false;
private loadingTimeout: number | null = null;
wrapContentWithContainer: boolean = true;
wrapContentWithContainer = true;
sidebarPortal: Portal<any> | undefined = undefined;
isDesktop: boolean = false;
hasSidebar: boolean = false;
isDesktop = false;
hasSidebar = false;
public constructor(
private readonly router: Router,
@ -64,7 +64,7 @@ export class AppComponent implements OnInit {
if (event instanceof NavigationEnd) {
this.loadingEnd();
}
if (event instanceof NavigationEnd) this.onNavigationEnd(event);
if (event instanceof NavigationEnd) this.onNavigationEnd();
if (event instanceof NavigationError) this.onNavigationError(event);
});
}
@ -84,7 +84,7 @@ export class AppComponent implements OnInit {
this.router.navigate(['/error/404'], { replaceUrl: true });
}
private async onNavigationEnd(event: NavigationEnd) {
private async onNavigationEnd() {
const data = this.routeData;
this.wrapContentWithContainer = !data.noContainer;

View file

@ -1,12 +1,12 @@
import { Clipboard } from '@angular/cdk/clipboard';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import {
MatFormFieldAppearance,
SubscriptSizing,
} from '@angular/material/form-field';
import { Fail, FT } from 'picsur-shared/dist/types';
import { Logger } from 'src/app/services/logger/logger.service';
import { ErrorService } from 'src/app/util/error-manager/error.service';
import { FT, Fail } from 'picsur-shared/dist/types/failable';
import { Logger } from '../../services/logger/logger.service';
import { ClipboardService } from '../../util/clipboard.service';
import { ErrorService } from '../../util/error-manager/error.service';
@Component({
selector: 'copy-field',
@ -17,11 +17,11 @@ export class CopyFieldComponent {
private readonly logger = new Logger(CopyFieldComponent.name);
// Two parameters: name, value
@Input() label: string = 'Loading...';
@Input() value: string = 'Loading...';
@Input() label = 'Loading...';
@Input() value = 'Loading...';
@Input() showHideButton: boolean = false;
@Input() hidden: boolean = false;
@Input() showHideButton = false;
@Input() hidden = false;
@Input() color: 'primary' | 'accent' | 'warn' = 'primary';
@Input() appearance: MatFormFieldAppearance = 'outline';
@ -31,12 +31,12 @@ export class CopyFieldComponent {
@Output('hide') onHide = new EventEmitter<boolean>();
constructor(
private readonly clipboard: Clipboard,
private readonly clipboard: ClipboardService,
private readonly errorService: ErrorService,
) {}
public copy() {
if (this.clipboard.copy(this.value)) {
public async copy() {
if (await this.clipboard.copy(this.value)) {
this.errorService.info(`Copied ${this.label}!`);
this.onCopy.emit(this.value);
return;

View file

@ -1,11 +1,11 @@
import { ClipboardModule } from '@angular/cdk/clipboard';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module';
import { CopyFieldComponent } from './copy-field.component';
import { ErrorManagerModule } from '../../util/error-manager/error-manager.module';
@NgModule({
declarations: [CopyFieldComponent],
imports: [
@ -15,7 +15,6 @@ import { CopyFieldComponent } from './copy-field.component';
MatInputModule,
MatIconModule,
MatButtonModule,
ClipboardModule,
],
exports: [CopyFieldComponent],
})

View file

@ -5,11 +5,10 @@ import { Component, Input } from '@angular/core';
templateUrl: './fab.component.html',
})
export class FabComponent {
@Input('aria-label') ariaLabel: string = 'Floating Action Button';
@Input() icon: string = 'add';
@Input() color: string = 'primary';
@Input('aria-label') ariaLabel = 'Floating Action Button';
@Input() icon = 'add';
@Input() color = 'primary';
@Input('tooltip') tooltip: string;
// eslint-disable-next-line @typescript-eslint/no-empty-function
@Input() onClick: () => void = () => {};
constructor() {}
}

View file

@ -22,7 +22,7 @@
[matTooltip]="tooltip"
matTooltipPosition="left"
[matTooltipDisabled]="!openManager.isOpen"
(click)="click($event)"
(click)="click()"
aria-label=""
>
<mat-icon

View file

@ -9,19 +9,19 @@ import { SpeedDialAnimation } from './speed-dial.animation';
animations: [SpeedDialAnimation],
})
export class SpeedDialComponent {
@Input('aria-label') ariaLabel: string = 'Floating Action Button';
@Input('aria-label') ariaLabel = 'Floating Action Button';
@Input('icon') icon: string = 'add';
@Input('icon-hover') iconHover: string = 'close';
@Input('color') color: string = 'primary';
@Input('open-on-hover') openOnHover: boolean = false;
@Input('icon') icon = 'add';
@Input('icon-hover') iconHover = 'close';
@Input('color') color = 'primary';
@Input('open-on-hover') openOnHover = false;
@Input('tooltip') tooltip: string;
@Output('main-click') clickEmitter = new Subject<void>();
public openManager = new OpenManager();
private touchUntil: number = 0;
private touchUntil = 0;
@HostListener('document:touchstart', ['$event'])
@HostListener('document:touchend', ['$event'])
@ -35,13 +35,13 @@ export class SpeedDialComponent {
@HostListener('document:click', ['$event'])
@HostListener('document:keydown.escape', ['$event'])
anyClick(e: Event) {
anyClick() {
if (!this.openManager.isOpen || this.openManager.isAnimating) return;
this.openManager.close();
}
click(e: MouseEvent) {
click() {
if (!this.openManager.isOpen) {
this.openManager.open();
} else {

View file

@ -5,7 +5,7 @@ import {
OnInit,
} from '@angular/core';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { InfoService } from 'src/app/services/api/info.service';
import { InfoService } from '../../services/api/info.service';
@Component({
selector: 'app-footer',
@ -19,8 +19,8 @@ export class FooterComponent implements OnInit {
private readonly changeDetector: ChangeDetectorRef,
) {}
isDemo: boolean = false;
version: string = 'Unkown Version';
isDemo = false;
version = 'Unkown Version';
ngOnInit(): void {
this.subscribeInfo();

View file

@ -11,11 +11,11 @@ import { Router } from '@angular/router';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { HasFailed } from 'picsur-shared/dist/types';
import { PermissionService } from 'src/app/services/api/permission.service';
import { UserService } from 'src/app/services/api/user.service';
import { Logger } from 'src/app/services/logger/logger.service';
import { ErrorService } from 'src/app/util/error-manager/error.service';
import { HasFailed } from 'picsur-shared/dist/types/failable';
import { UserService } from '../../services/api/user.service';
import { PermissionService } from '../../services/api/permission.service';
import { Logger } from '../../services/logger/logger.service';
import { ErrorService } from '../../util/error-manager/error.service';
@Component({
selector: 'app-header',
@ -38,17 +38,17 @@ export class HeaderComponent implements OnInit {
this._enableHamburger = value;
this.changeDetector.markForCheck();
}
public _enableHamburger: boolean = true;
public _enableHamburger = true;
@Output('onHamburgerClick') onHamburgerClick = new EventEmitter<void>();
@Input('loading') public loading: boolean = false;
@Input('loading') public loading = false;
private currentUser: EUser | null = null;
public canLogIn: boolean = false;
public canAccessSettings: boolean = false;
public canUpload: boolean = false;
public canRegister: boolean = false;
public canLogIn = false;
public canAccessSettings = false;
public canUpload = false;
public canRegister = false;
public get user() {
return this.currentUser;

View file

@ -7,8 +7,8 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router';
import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module';
import { HeaderComponent } from './header.component';
import { ErrorManagerModule } from '../../util/error-manager/error-manager.module';
@NgModule({
imports: [

View file

@ -1,12 +1,11 @@
import { Directive, ElementRef, Inject } from '@angular/core';
import {
boxExtractor,
ResizeObserverDirective,
ResizeObserverService,
RESIZE_OPTION_BOX,
ResizeObserverService,
boxExtractor,
} from '@ng-web-apis/resize-observer';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { map, Observable } from 'rxjs';
import { Observable, map } from 'rxjs';
@Directive({
selector: '[masonry-item]',

View file

@ -12,9 +12,9 @@ import {
} from '@angular/core';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { combineLatest, Subscription } from 'rxjs';
import { RemoveChildren } from 'src/app/util/remove-children';
import { Throttle } from 'src/app/util/throttle';
import { MasonryItemDirective } from './masonry-item.directive';
import { RemoveChildren } from '../../util/remove-children';
import { Throttle } from '../../util/throttle';
@Component({
selector: 'masonry',
@ -30,7 +30,7 @@ export class MasonryComponent implements AfterViewInit, OnDestroy {
this.changeDetector.markForCheck();
}
public _column_count = 1;
@Input('update-speed') update_speed: number = 200;
@Input('update-speed') update_speed = 200;
@ContentChildren(MasonryItemDirective)
private content: QueryList<MasonryItemDirective>;
@ -59,7 +59,7 @@ export class MasonryComponent implements AfterViewInit, OnDestroy {
this.sizesSubscription = combineLatest(sizes)
.pipe(Throttle(this.update_speed))
.subscribe((output) => {
.subscribe(() => {
this.resortItems(items);
});
@ -76,7 +76,7 @@ export class MasonryComponent implements AfterViewInit, OnDestroy {
RemoveChildren(columnsArray[i]);
}
const columnSizes = columnsArray.map((c) => 0);
const columnSizes = columnsArray.map(() => 0);
for (let i = 0; i < itemsArray.length; i++) {
const item = itemsArray[i];
@ -84,7 +84,7 @@ export class MasonryComponent implements AfterViewInit, OnDestroy {
let smallestColumn = 0;
let smallestColumnSize = columnSizes[0];
for (let j = columnSizes.length - 1; j >= 0; j--) {
let better_j = (j + i) % columnSizes.length;
const better_j = (j + i) % columnSizes.length;
if (columnSizes[better_j] <= smallestColumnSize) {
smallestColumn = better_j;

View file

@ -1,5 +1,5 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Required } from 'src/app/models/decorators/required.decorator';
import { Required } from '../../models/decorators/required.decorator';
@Component({
selector: 'paginator',
@ -13,18 +13,18 @@ export class PaginatorComponent implements OnInit {
this.calculateRanges();
}
page: number = 1;
page = 1;
@Input('page') set pageInput(value: number) {
this.page = value;
this.calculateRanges();
}
@Output('page') pageChange = new EventEmitter<number>();
@Input('show-first-last') showFirstLast: boolean = true;
@Input('show-first-last') showFirstLast = true;
@Input('shown-pages') shownPages: number = 7;
@Input('shown-first-pages') shownFirstPages: number = 1;
@Input('shown-last-pages') shownLastPages: number = 1;
@Input('shown-pages') shownPages = 7;
@Input('shown-first-pages') shownFirstPages = 1;
@Input('shown-last-pages') shownLastPages = 1;
firstPagesRange: [number, number] | null = null;
lastPagesRange: [number, number] | null = null;

View file

@ -2,8 +2,8 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { PipesModule } from 'src/app/pipes/pipes.module';
import { PaginatorComponent } from './paginator.component';
import { PipesModule } from '../../pipes/pipes.module';
@NgModule({
declarations: [PaginatorComponent],

View file

@ -14,7 +14,7 @@
<mat-icon *ngIf="state === 'error'">broken_image</mat-icon>
<mat-spinner
(nguiInview)="onInview($event)"
(nguiOutview)="onOutview($event)"
(nguiInview)="onInview()"
(nguiOutview)="onOutview()"
*ngIf="state === 'init' || state === 'loading'"
></mat-spinner>

View file

@ -5,16 +5,15 @@ import {
ElementRef,
Input,
OnChanges,
SimpleChanges,
ViewChild,
} from '@angular/core';
import { FileType, ImageFileType } from 'picsur-shared/dist/dto/mimes.dto';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types/failable';
import { URLRegex } from 'picsur-shared/dist/util/common-regex';
import { ParseMime2FileType } from 'picsur-shared/dist/util/parse-mime';
import { ApiService } from 'src/app/services/api/api.service';
import { Logger } from 'src/app/services/logger/logger.service';
import { QoiWorkerService } from 'src/app/workers/qoi-worker.service';
import { ApiService } from '../../services/api/api.service';
import { Logger } from '../../services/logger/logger.service';
import { QoiWorkerService } from '../../workers/qoi-worker.service';
enum PicsurImgState {
Init = 'init',
@ -48,12 +47,12 @@ export class PicsurImgComponent implements OnChanges {
private readonly changeDetector: ChangeDetectorRef,
) {}
ngOnChanges(changes: SimpleChanges): void {
ngOnChanges(): void {
if (this.isInView) this.reload();
}
private reload() {
let url = this.imageURL ?? '';
const url = this.imageURL ?? '';
if (!URLRegex.test(url)) {
this.state = PicsurImgState.Loading;
this.changeDetector.markForCheck();
@ -68,7 +67,7 @@ export class PicsurImgComponent implements OnChanges {
this.changeDetector.markForCheck();
}
})
.catch((e) => this.logger.error);
.catch(this.logger.error);
}
private async update(url: string): AsyncFailable<void> {
@ -111,7 +110,7 @@ export class PicsurImgComponent implements OnChanges {
return ParseMime2FileType(mime);
}
onInview(e: any) {
onInview() {
this.isInView = true;
if (this.state === PicsurImgState.Init) {
@ -120,7 +119,7 @@ export class PicsurImgComponent implements OnChanges {
}
}
onOutview(e: any) {
onOutview() {
this.isInView = false;
}
}

View file

@ -5,13 +5,13 @@ import {
DecodedPref,
PrefValueType,
} from 'picsur-shared/dist/dto/preferences.dto';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types/failable';
import { filter } from 'rxjs';
import { Required } from 'src/app/models/decorators/required.decorator';
import { Logger } from 'src/app/services/logger/logger.service';
import { ErrorService } from 'src/app/util/error-manager/error.service';
import { Throttle } from 'src/app/util/throttle';
import { ZodTypeAny } from 'zod';
import { Required } from '../../models/decorators/required.decorator';
import { Logger } from '../../services/logger/logger.service';
import { ErrorService } from '../../util/error-manager/error.service';
import { Throttle } from '../../util/throttle';
@Component({
selector: 'pref-option',
@ -40,8 +40,8 @@ export class PrefOptionComponent implements OnInit {
pref: PrefValueType,
) => AsyncFailable<any>;
@Input() @Required name: string = '';
@Input() helpText: string = '';
@Input() @Required name = '';
@Input() helpText = '';
@Input() validator?: ZodTypeAny = undefined;
constructor(private readonly errorService: ErrorService) {}
@ -96,7 +96,7 @@ export class PrefOptionComponent implements OnInit {
subscribeUpdate() {
return this.formControl.valueChanges
.pipe(
filter((value) => this.formControl.errors === null),
filter(() => this.formControl.errors === null),
Throttle(300),
)
.subscribe(this.updatePreference.bind(this));

View file

@ -7,8 +7,8 @@ import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module';
import { PrefOptionComponent } from './pref-option.component';
import { ErrorManagerModule } from '../../util/error-manager/error-manager.module';
@NgModule({
imports: [

View file

@ -6,7 +6,7 @@ import { MatChipInputEvent } from '@angular/material/chips';
import Fuse from 'fuse.js';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { BehaviorSubject } from 'rxjs';
import { Required } from 'src/app/models/decorators/required.decorator';
import { Required } from '../../models/decorators/required.decorator';
@Component({
selector: 'values-picker',
@ -18,7 +18,7 @@ export class ValuesPickerComponent implements OnInit, OnChanges {
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
// Ui niceties
@Input('name') @Required name: string = '';
@Input('name') @Required name = '';
public get nameCap(): string {
return this.name.charAt(0).toUpperCase() + this.name.slice(1);
}
@ -107,14 +107,14 @@ export class ValuesPickerComponent implements OnInit, OnChanges {
@AutoUnsubscribe()
private subscribeInputValue() {
return this.inputControl.valueChanges.subscribe((value) => {
return this.inputControl.valueChanges.subscribe(() => {
this.updateSelectable();
});
}
@AutoUnsubscribe()
private subscribeMyValue() {
return this.myControl.valueChanges.subscribe((value) => {
return this.myControl.valueChanges.subscribe(() => {
this.updateSelectable();
});
}

View file

@ -4,7 +4,6 @@ import {
CanActivate,
CanActivateChild,
Router,
RouterStateSnapshot,
} from '@angular/router';
import { isPermissionsArray } from 'picsur-shared/dist/validators/permissions.validator';
import { PRouteData } from '../models/dto/picsur-routes.dto';
@ -24,18 +23,15 @@ export class PermissionGuard implements CanActivate, CanActivateChild {
private readonly router: Router,
) {}
async canActivateChild(
childRoute: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
) {
return await this.can(childRoute, state);
async canActivateChild(childRoute: ActivatedRouteSnapshot) {
return await this.can(childRoute);
}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return await this.can(route, state);
async canActivate(route: ActivatedRouteSnapshot) {
return await this.can(route);
}
private async can(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
private async can(route: ActivatedRouteSnapshot) {
const requiredPermissions: string[] = this.nestedPermissions(route);
const allPermissionsArray = await this.staticInfo.getAllPermissions();

View file

@ -1,9 +1,9 @@
import { TrackingState } from 'picsur-shared/dist/dto/tracking-state.enum';
export class ServerInfo {
production: boolean = false;
demo: boolean = false;
version: string = '0.0.0';
production = false;
demo = false;
version = '0.0.0';
host_override?: string;
tracking: {
state: TrackingState;

View file

@ -1,5 +1,5 @@
import { FormControl } from '@angular/forms';
import { Fail, Failable, FT } from 'picsur-shared/dist/types';
import { Fail, Failable, FT } from 'picsur-shared/dist/types/failable';
import { UserPassModel } from '../forms-dto/userpass.dto';
import {
CreatePasswordError,

View file

@ -1,5 +1,5 @@
import { FormControl } from '@angular/forms';
import { Fail, Failable, FT } from 'picsur-shared/dist/types';
import { Fail, Failable, FT } from 'picsur-shared/dist/types/failable';
import { UserPassModel } from '../forms-dto/userpass.dto';
import { Compare } from '../validators/compare.validator';
import {

View file

@ -11,7 +11,7 @@ import {
} from '../validators/user.validator';
export class UpdateUserControl {
private id: string = '';
private id = '';
public username = new FormControl('', UsernameValidators);
public password = new FormControl('', PasswordValidators);
public roles = new FormControl<string[]>([]);

View file

@ -4,9 +4,9 @@ import { Pipe, PipeTransform } from '@angular/core';
name: 'truncate',
})
export class TruncatePipe implements PipeTransform {
transform(text: string, length: number = 32, suffix: string = '...'): string {
transform(text: string, length = 32, suffix = '...'): string {
if (text.length > length) {
let truncated: string = text.substring(0, length).trim() + suffix;
const truncated: string = text.substring(0, length).trim() + suffix;
return truncated;
}

View file

@ -1,10 +1,10 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { PRoutes } from 'src/app/models/dto/picsur-routes.dto';
import { E401Component } from './401.component';
import { E404Component } from './404.component';
import { ImageDeleteFailureComponent } from './delete-failure.component';
import { ImageDeleteSuccessComponent } from './delete-success.component';
import { PRoutes } from '../../models/dto/picsur-routes.dto';
const routes: PRoutes = [
{

View file

@ -6,19 +6,19 @@ import { EImage } from 'picsur-shared/dist/entities/image.entity';
import { HasFailed } from 'picsur-shared/dist/types/failable';
import {
BehaviorSubject,
Observable,
filter,
map,
merge,
Observable,
switchMap,
timer,
} from 'rxjs';
import { ImageService } from 'src/app/services/api/image.service';
import { UserService } from 'src/app/services/api/user.service';
import { Logger } from 'src/app/services/logger/logger.service';
import { BootstrapService, BSScreenSize } from 'src/app/util/bootstrap.service';
import { DialogService } from 'src/app/util/dialog-manager/dialog.service';
import { ErrorService } from 'src/app/util/error-manager/error.service';
import { ImageService } from '../../services/api/image.service';
import { UserService } from '../../services/api/user.service';
import { Logger } from '../../services/logger/logger.service';
import { BSScreenSize, BootstrapService } from '../../util/bootstrap.service';
import { DialogService } from '../../util/dialog-manager/dialog.service';
import { ErrorService } from '../../util/error-manager/error.service';
@Component({
templateUrl: './images.component.html',
@ -39,8 +39,8 @@ export class ImagesComponent implements OnInit {
);
}
page: number = 1;
pages: number = 1;
page = 1;
pages = 1;
constructor(
private readonly route: ActivatedRoute,
@ -110,7 +110,7 @@ export class ImagesComponent implements OnInit {
merge(
...images
.filter((i) => i.expires_at !== null)
.map((i) => timer(i.expires_at!).pipe(map(() => i))),
.map((i) => timer(i.expires_at ?? new Date(0)).pipe(map(() => i))),
),
),
) as Observable<EImage>;

View file

@ -4,12 +4,12 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MomentModule } from 'ngx-moment';
import { MasonryModule } from 'src/app/components/masonry/masonry.module';
import { PaginatorModule } from 'src/app/components/paginator/paginator.module';
import { PicsurImgModule } from 'src/app/components/picsur-img/picsur-img.module';
import { PipesModule } from 'src/app/pipes/pipes.module';
import { DialogManagerModule } from 'src/app/util/dialog-manager/dialog-manager.module';
import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module';
import { MasonryModule } from '../../components/masonry/masonry.module';
import { PaginatorModule } from '../../components/paginator/paginator.module';
import { PicsurImgModule } from '../../components/picsur-img/picsur-img.module';
import { PipesModule } from '../../pipes/pipes.module';
import { DialogManagerModule } from '../../util/dialog-manager/dialog-manager.module';
import { ErrorManagerModule } from '../../util/error-manager/error-manager.module';
import { ImagesComponent } from './images.component';
import { ImagesRoutingModule } from './images.routing.module';

View file

@ -1,9 +1,9 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { PermissionGuard } from 'src/app/guards/permission.guard';
import { PRoutes } from 'src/app/models/dto/picsur-routes.dto';
import { ImagesComponent } from './images.component';
import { PermissionGuard } from '../../guards/permission.guard';
import { PRoutes } from '../../models/dto/picsur-routes.dto';
const routes: PRoutes = [
{

View file

@ -1,11 +1,11 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { ProcessingViewMeta } from 'src/app/models/dto/processing-view-meta.dto';
import { ApiService } from 'src/app/services/api/api.service';
import { ImageService } from 'src/app/services/api/image.service';
import { Logger } from 'src/app/services/logger/logger.service';
import { ErrorService } from 'src/app/util/error-manager/error.service';
import { Fail, FT, HasFailed } from 'picsur-shared/dist/types/failable';
import { ProcessingViewMeta } from '../../models/dto/processing-view-meta.dto';
import { ApiService } from '../../services/api/api.service';
import { ImageService } from '../../services/api/image.service';
import { Logger } from '../../services/logger/logger.service';
import { ErrorService } from '../../util/error-manager/error.service';
@Component({
templateUrl: './processing.component.html',

Some files were not shown because too many files have changed in this diff Show more