update packages and fix build

This commit is contained in:
Caramel
2023-11-28 18:55:41 +01:00
parent 1e9dad9ac2
commit d7241fa7b4
30 changed files with 4555 additions and 4264 deletions

View File

@@ -21,65 +21,65 @@
"purge": "rm -rf dist && rm -rf node_modules" "purge": "rm -rf dist && rm -rf node_modules"
}, },
"dependencies": { "dependencies": {
"@fastify/helmet": "^10.1.1", "@fastify/helmet": "^11.1.1",
"@fastify/multipart": "^7.6.1", "@fastify/multipart": "^8.0.0",
"@fastify/reply-from": "^9.3.0", "@fastify/reply-from": "^9.4.0",
"@fastify/static": "^6.10.2", "@fastify/static": "^6.12.0",
"@nestjs/common": "^10.0.0", "@nestjs/common": "^10.2.10",
"@nestjs/config": "^2.3.4", "@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.0.0", "@nestjs/core": "^10.2.10",
"@nestjs/jwt": "^10.1.0", "@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^9.0.3", "@nestjs/passport": "^10.0.2",
"@nestjs/platform-fastify": "^10.0.0", "@nestjs/platform-fastify": "^10.2.10",
"@nestjs/schedule": "^3.0.0", "@nestjs/schedule": "^4.0.0",
"@nestjs/serve-static": "^4.0.0", "@nestjs/serve-static": "^4.0.0",
"@nestjs/throttler": "^4.0.0", "@nestjs/throttler": "^5.0.1",
"@nestjs/typeorm": "^9.0.1", "@nestjs/typeorm": "^10.0.1",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.1",
"bmp-img": "^1.2.1", "bmp-img": "^1.2.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"extensionless": "^1.4.5", "extensionless": "^1.7.3",
"file-type": "^18.5.0", "file-type": "^18.7.0",
"is-docker": "^3.0.0", "is-docker": "^3.0.0",
"ms": "2.1.3", "ms": "2.1.3",
"node-fetch": "^3.3.1", "node-fetch": "^3.3.2",
"p-timeout": "^6.1.2", "p-timeout": "^6.1.2",
"passport": "^0.6.0", "passport": "^0.7.0",
"passport-headerapikey": "^1.2.2", "passport-headerapikey": "^1.2.2",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"passport-strategy": "^1.0.0", "passport-strategy": "^1.0.0",
"pg": "^8.11.0", "pg": "^8.11.3",
"picsur-shared": "*", "picsur-shared": "*",
"posix.js": "^0.1.1", "posix.js": "^0.1.1",
"qoi-img": "^2.1.1", "qoi-img": "^2.1.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^5.0.1", "rimraf": "^5.0.5",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"sharp": "^0.32.1", "sharp": "^0.32.6",
"stream-parser": "^0.3.1", "stream-parser": "^0.3.1",
"thunks": "^4.9.6", "thunks": "^4.9.6",
"typeorm": "0.3.16", "typeorm": "0.3.17",
"zod": "^3.21.4" "zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.0.1", "@nestjs/cli": "^10.2.1",
"@nestjs/schematics": "^10.0.1", "@nestjs/schematics": "^10.0.3",
"@nestjs/testing": "^10.0.0", "@nestjs/testing": "^10.2.10",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.2",
"@types/cors": "^2.8.13", "@types/cors": "^2.8.17",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.11",
"@types/node": "^20.3.1", "@types/node": "^20.10.0",
"@types/passport-jwt": "^3.0.8", "@types/passport-jwt": "^3.0.13",
"@types/passport-local": "^1.0.35", "@types/passport-local": "^1.0.38",
"@types/passport-strategy": "^0.2.35", "@types/passport-strategy": "^0.2.38",
"@types/sharp": "^0.32.0", "@types/sharp": "^0.32.0",
"@types/supertest": "^2.0.12", "@types/supertest": "^2.0.16",
"prettier": "^2.8.8", "prettier": "^3.1.0",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"ts-loader": "^9.4.3", "ts-loader": "^9.5.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3" "typescript": "^5.3.2"
} }
} }

View File

@@ -32,7 +32,7 @@ export class JwtConfigService implements JwtOptionsFactory {
await this.prefService.getStringPreference('jwt_expires_in'), await this.prefService.getStringPreference('jwt_expires_in'),
); );
let milliseconds = ms(expiresIn as any); let milliseconds = ms(expiresIn as string);
if (isNaN(milliseconds)) { if (isNaN(milliseconds)) {
milliseconds = 1000 * 60 * 60 * 24; // 1 day milliseconds = 1000 * 60 * 60 * 24; // 1 day
} }

View File

@@ -0,0 +1,12 @@
import { Throttle } from '@nestjs/throttler';
export const EasyThrottle = (
limit: number,
ttl?: number,
): MethodDecorator & ClassDecorator =>
Throttle({
default: {
limit,
ttl: ttl ?? 60,
},
});

View File

@@ -8,8 +8,12 @@ import { ZodValidationPipe } from './validate/zod-validator.pipe';
@Module({ @Module({
imports: [ imports: [
ThrottlerModule.forRoot({ ThrottlerModule.forRoot({
ttl: 60, throttlers: [
limit: 60, {
limit: 60,
ttl: 60,
},
],
}), }),
], ],
providers: [ providers: [

View File

@@ -4,7 +4,7 @@ import { FT, Fail } from 'picsur-shared/dist/types/failable';
@Injectable() @Injectable()
export class PicsurThrottlerGuard extends ThrottlerGuard { export class PicsurThrottlerGuard extends ThrottlerGuard {
protected override throwThrottlingException(): void { protected override throwThrottlingException(): Promise<void> {
throw Fail(FT.RateLimit); throw Fail(FT.RateLimit);
} }
} }

View File

@@ -62,7 +62,7 @@ export class ImageConverterService {
if (HasFailed(memLimit) || HasFailed(timeLimit)) { if (HasFailed(memLimit) || HasFailed(timeLimit)) {
return Fail(FT.Internal, 'Failed to get conversion limits'); return Fail(FT.Internal, 'Failed to get conversion limits');
} }
let timeLimitMS = ms(timeLimit as any); let timeLimitMS = ms(timeLimit as string);
if (isNaN(timeLimitMS) || timeLimitMS === 0) timeLimitMS = 15 * 1000; // 15 seconds if (isNaN(timeLimitMS) || timeLimitMS === 0) timeLimitMS = 15 * 1000; // 15 seconds
const sharpWrapper = new SharpWrapper(timeLimitMS, memLimit); const sharpWrapper = new SharpWrapper(timeLimitMS, memLimit);

View File

@@ -49,7 +49,7 @@ export class ImageManagerModule implements OnModuleInit {
return; return;
} }
let after_ms = ms(remove_derivatives_after as any); let after_ms = ms(remove_derivatives_after as string);
if (isNaN(after_ms) || after_ms === 0) { if (isNaN(after_ms) || after_ms === 0) {
this.logger.log('remove_derivatives_after is 0, skipping cron'); this.logger.log('remove_derivatives_after is 0, skipping cron');
return; return;
@@ -59,6 +59,7 @@ export class ImageManagerModule implements OnModuleInit {
const result = await this.imageFileDB.cleanupDerivatives(after_ms / 1000); const result = await this.imageFileDB.cleanupDerivatives(after_ms / 1000);
if (HasFailed(result)) { if (HasFailed(result)) {
result.print(this.logger); result.print(this.logger);
return;
} }
if (result > 0) this.logger.log(`Cleaned up ${result} derivatives`); if (result > 0) this.logger.log(`Cleaned up ${result} derivatives`);

View File

@@ -242,9 +242,8 @@ export class ImageManagerService {
// Util stuff ================================================================== // Util stuff ==================================================================
private async getFileTypeFromBuffer(image: Buffer): AsyncFailable<FileType> { private async getFileTypeFromBuffer(image: Buffer): AsyncFailable<FileType> {
const filetypeResult: FileTypeResult | undefined = await fileTypeFromBuffer( const filetypeResult: FileTypeResult | undefined =
image, await fileTypeFromBuffer(image);
);
let mime: string | undefined; let mime: string | undefined;
if (filetypeResult === undefined) { if (filetypeResult === undefined) {

View File

@@ -1,5 +1,4 @@
import { Body, Controller, Post } from '@nestjs/common'; import { Body, Controller, Post } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { import {
ApiKeyCreateResponse, ApiKeyCreateResponse,
ApiKeyDeleteRequest, ApiKeyDeleteRequest,
@@ -14,6 +13,7 @@ import {
import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable'; import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { ApiKeyDbService } from '../../../collections/apikey-db/apikey-db.service'; import { ApiKeyDbService } from '../../../collections/apikey-db/apikey-db.service';
import { EasyThrottle } from '../../../decorators/easy-throttle.decorator';
import { import {
HasPermission, HasPermission,
RequiredPermissions, RequiredPermissions,
@@ -54,7 +54,7 @@ export class ApiKeysController {
@Post('create') @Post('create')
@Returns(ApiKeyCreateResponse) @Returns(ApiKeyCreateResponse)
@Throttle(10) @EasyThrottle(10)
async createApiKey( async createApiKey(
@ReqUserID() userID: string, @ReqUserID() userID: string,
): Promise<ApiKeyCreateResponse> { ): Promise<ApiKeyCreateResponse> {

View File

@@ -1,5 +1,4 @@
import { Body, Controller, Get, Logger, Param, Post } from '@nestjs/common'; import { Body, Controller, Get, Logger, Param, Post } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { import {
GetPreferenceResponse, GetPreferenceResponse,
MultiplePreferencesResponse, MultiplePreferencesResponse,
@@ -8,6 +7,7 @@ import {
} from 'picsur-shared/dist/dto/api/pref.dto'; } from 'picsur-shared/dist/dto/api/pref.dto';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable'; import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { SysPreferenceDbService } from '../../../collections/preference-db/sys-preference-db.service'; import { SysPreferenceDbService } from '../../../collections/preference-db/sys-preference-db.service';
import { EasyThrottle } from '../../../decorators/easy-throttle.decorator';
import { RequiredPermissions } from '../../../decorators/permissions.decorator'; import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator'; import { Returns } from '../../../decorators/returns.decorator';
import { Permission } from '../../../models/constants/permissions.const'; import { Permission } from '../../../models/constants/permissions.const';
@@ -21,7 +21,7 @@ export class SysPrefController {
@Get() @Get()
@Returns(MultiplePreferencesResponse) @Returns(MultiplePreferencesResponse)
@Throttle(20) @EasyThrottle(20)
async getAllSysPrefs(): Promise<MultiplePreferencesResponse> { async getAllSysPrefs(): Promise<MultiplePreferencesResponse> {
const prefs = ThrowIfFailed(await this.prefService.getAllPreferences()); const prefs = ThrowIfFailed(await this.prefService.getAllPreferences());
@@ -41,7 +41,7 @@ export class SysPrefController {
@Post(':key') @Post(':key')
@Returns(UpdatePreferenceResponse) @Returns(UpdatePreferenceResponse)
@Throttle(30) @EasyThrottle(30)
async setSysPref( async setSysPref(
@Param('key') key: string, @Param('key') key: string,
@Body() body: UpdatePreferenceRequest, @Body() body: UpdatePreferenceRequest,

View File

@@ -1,5 +1,4 @@
import { Body, Controller, Get, Logger, Param, Post } from '@nestjs/common'; import { Body, Controller, Get, Logger, Param, Post } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { import {
GetPreferenceResponse, GetPreferenceResponse,
MultiplePreferencesResponse, MultiplePreferencesResponse,
@@ -8,6 +7,7 @@ import {
} from 'picsur-shared/dist/dto/api/pref.dto'; } from 'picsur-shared/dist/dto/api/pref.dto';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable'; import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { UsrPreferenceDbService } from '../../../collections/preference-db/usr-preference-db.service'; import { UsrPreferenceDbService } from '../../../collections/preference-db/usr-preference-db.service';
import { EasyThrottle } from '../../../decorators/easy-throttle.decorator';
import { RequiredPermissions } from '../../../decorators/permissions.decorator'; import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { ReqUserID } from '../../../decorators/request-user.decorator'; import { ReqUserID } from '../../../decorators/request-user.decorator';
import { Returns } from '../../../decorators/returns.decorator'; import { Returns } from '../../../decorators/returns.decorator';
@@ -22,7 +22,7 @@ export class UsrPrefController {
@Get() @Get()
@Returns(MultiplePreferencesResponse) @Returns(MultiplePreferencesResponse)
@Throttle(20) @EasyThrottle(20)
async getAllUsrPrefs( async getAllUsrPrefs(
@ReqUserID() userid: string, @ReqUserID() userid: string,
): Promise<MultiplePreferencesResponse> { ): Promise<MultiplePreferencesResponse> {
@@ -51,7 +51,7 @@ export class UsrPrefController {
@Post(':key') @Post(':key')
@Returns(UpdatePreferenceResponse) @Returns(UpdatePreferenceResponse)
@Throttle(30) @EasyThrottle(30)
async setUsrPref( async setUsrPref(
@Param('key') key: string, @Param('key') key: string,
@ReqUserID() userid: string, @ReqUserID() userid: string,

View File

@@ -1,5 +1,4 @@
import { Body, Controller, Get, Logger, Post } from '@nestjs/common'; import { Body, Controller, Get, Logger, Post } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { import {
RoleCreateRequest, RoleCreateRequest,
RoleCreateResponse, RoleCreateResponse,
@@ -12,9 +11,10 @@ import {
RoleUpdateResponse, RoleUpdateResponse,
SpecialRolesResponse, SpecialRolesResponse,
} from 'picsur-shared/dist/dto/api/roles.dto'; } from 'picsur-shared/dist/dto/api/roles.dto';
import { Fail, FT, ThrowIfFailed } from 'picsur-shared/dist/types/failable'; import { FT, Fail, ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { RoleDbService } from '../../../collections/role-db/role-db.service'; import { RoleDbService } from '../../../collections/role-db/role-db.service';
import { UserDbService } from '../../../collections/user-db/user-db.service'; import { UserDbService } from '../../../collections/user-db/user-db.service';
import { EasyThrottle } from '../../../decorators/easy-throttle.decorator';
import { RequiredPermissions } from '../../../decorators/permissions.decorator'; import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator'; import { Returns } from '../../../decorators/returns.decorator';
import { Permission } from '../../../models/constants/permissions.const'; import { Permission } from '../../../models/constants/permissions.const';
@@ -57,7 +57,7 @@ export class RolesController {
@Post('update') @Post('update')
@Returns(RoleUpdateResponse) @Returns(RoleUpdateResponse)
@Throttle(20) @EasyThrottle(20)
async updateRole( async updateRole(
@Body() body: RoleUpdateRequest, @Body() body: RoleUpdateRequest,
): Promise<RoleUpdateResponse> { ): Promise<RoleUpdateResponse> {
@@ -75,7 +75,7 @@ export class RolesController {
@Post('create') @Post('create')
@Returns(RoleCreateResponse) @Returns(RoleCreateResponse)
@Throttle(10) @EasyThrottle(10)
async createRole( async createRole(
@Body() role: RoleCreateRequest, @Body() role: RoleCreateRequest,
): Promise<RoleCreateResponse> { ): Promise<RoleCreateResponse> {

View File

@@ -1,8 +1,8 @@
import { Controller, Logger, Post, Req, Res } from '@nestjs/common'; import { Controller, Logger, Post, Req, Res } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from 'fastify';
import { Fail, FT, ThrowIfFailed } from 'picsur-shared/dist/types/failable'; import { FT, Fail, ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { UsageConfigService } from '../../../config/late/usage.config.service'; import { UsageConfigService } from '../../../config/late/usage.config.service';
import { EasyThrottle } from '../../../decorators/easy-throttle.decorator';
import { NoPermissions } from '../../../decorators/permissions.decorator'; import { NoPermissions } from '../../../decorators/permissions.decorator';
import { ReturnsAnything } from '../../../decorators/returns.decorator'; import { ReturnsAnything } from '../../../decorators/returns.decorator';
@@ -15,7 +15,7 @@ export class UsageController {
@Post(['report', 'report/*']) @Post(['report', 'report/*'])
@ReturnsAnything() @ReturnsAnything()
@Throttle(120) @EasyThrottle(120)
async deleteRole( async deleteRole(
@Req() req: FastifyRequest, @Req() req: FastifyRequest,
@Res({ @Res({

View File

@@ -1,5 +1,4 @@
import { Body, Controller, Get, Logger, Post } from '@nestjs/common'; import { Body, Controller, Get, Logger, Post } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { import {
GetSpecialUsersResponse, GetSpecialUsersResponse,
UserCreateRequest, UserCreateRequest,
@@ -15,6 +14,7 @@ import {
} from 'picsur-shared/dist/dto/api/user-manage.dto'; } from 'picsur-shared/dist/dto/api/user-manage.dto';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable'; import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { UserDbService } from '../../../collections/user-db/user-db.service'; import { UserDbService } from '../../../collections/user-db/user-db.service';
import { EasyThrottle } from '../../../decorators/easy-throttle.decorator';
import { RequiredPermissions } from '../../../decorators/permissions.decorator'; import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator'; import { Returns } from '../../../decorators/returns.decorator';
import { Permission } from '../../../models/constants/permissions.const'; import { Permission } from '../../../models/constants/permissions.const';
@@ -47,7 +47,7 @@ export class UserAdminController {
@Post('create') @Post('create')
@Returns(UserCreateResponse) @Returns(UserCreateResponse)
@Throttle(10) @EasyThrottle(10)
async register( async register(
@Body() create: UserCreateRequest, @Body() create: UserCreateRequest,
): Promise<UserCreateResponse> { ): Promise<UserCreateResponse> {
@@ -80,7 +80,7 @@ export class UserAdminController {
@Post('update') @Post('update')
@Returns(UserUpdateResponse) @Returns(UserUpdateResponse)
@Throttle(20) @EasyThrottle(20)
async setPermissions( async setPermissions(
@Body() body: UserUpdateRequest, @Body() body: UserUpdateRequest,
): Promise<UserUpdateResponse> { ): Promise<UserUpdateResponse> {

View File

@@ -1,5 +1,4 @@
import { Body, Controller, Get, Logger, Post } from '@nestjs/common'; import { Body, Controller, Get, Logger, Post } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { import {
UserCheckNameRequest, UserCheckNameRequest,
UserCheckNameResponse, UserCheckNameResponse,
@@ -12,6 +11,7 @@ import {
import type { EUser } from 'picsur-shared/dist/entities/user.entity'; import type { EUser } from 'picsur-shared/dist/entities/user.entity';
import { ThrowIfFailed } from 'picsur-shared/dist/types/failable'; import { ThrowIfFailed } from 'picsur-shared/dist/types/failable';
import { UserDbService } from '../../../collections/user-db/user-db.service'; import { UserDbService } from '../../../collections/user-db/user-db.service';
import { EasyThrottle } from '../../../decorators/easy-throttle.decorator';
import { import {
NoPermissions, NoPermissions,
RequiredPermissions, RequiredPermissions,
@@ -35,7 +35,7 @@ export class UserController {
@Post('login') @Post('login')
@Returns(UserLoginResponse) @Returns(UserLoginResponse)
@UseLocalAuth(Permission.UserLogin) @UseLocalAuth(Permission.UserLogin)
@Throttle(30, 300) @EasyThrottle(30, 300)
async login(@ReqUser() user: EUser): Promise<UserLoginResponse> { async login(@ReqUser() user: EUser): Promise<UserLoginResponse> {
const jwt_token = ThrowIfFailed(await this.authService.createToken(user)); const jwt_token = ThrowIfFailed(await this.authService.createToken(user));
@@ -45,7 +45,7 @@ export class UserController {
@Post('register') @Post('register')
@Returns(UserRegisterResponse) @Returns(UserRegisterResponse)
@RequiredPermissions(Permission.UserRegister) @RequiredPermissions(Permission.UserRegister)
@Throttle(5, 300) @EasyThrottle(5, 300)
async register( async register(
@Body() register: UserRegisterRequest, @Body() register: UserRegisterRequest,
): Promise<UserRegisterResponse> { ): Promise<UserRegisterResponse> {
@@ -59,7 +59,7 @@ export class UserController {
@Post('checkname') @Post('checkname')
@Returns(UserCheckNameResponse) @Returns(UserCheckNameResponse)
@RequiredPermissions(Permission.UserRegister) @RequiredPermissions(Permission.UserRegister)
@Throttle(20) @EasyThrottle(20)
async checkName( async checkName(
@Body() checkName: UserCheckNameRequest, @Body() checkName: UserCheckNameRequest,
): Promise<UserCheckNameResponse> { ): Promise<UserCheckNameResponse> {
@@ -71,7 +71,7 @@ export class UserController {
@Get('me') @Get('me')
@Returns(UserMeResponse) @Returns(UserMeResponse)
@RequiredPermissions(Permission.UserKeepLogin) @RequiredPermissions(Permission.UserKeepLogin)
@Throttle(10) @EasyThrottle(10)
async me(@ReqUserID() userid: string): Promise<UserMeResponse> { async me(@ReqUserID() userid: string): Promise<UserMeResponse> {
const backenduser = ThrowIfFailed(await this.usersService.findOne(userid)); const backenduser = ThrowIfFailed(await this.usersService.findOne(userid));
@@ -86,7 +86,7 @@ export class UserController {
@Get('me/permissions') @Get('me/permissions')
@Returns(UserMePermissionsResponse) @Returns(UserMePermissionsResponse)
@NoPermissions() @NoPermissions()
@Throttle(20) @EasyThrottle(20)
async refresh( async refresh(
@ReqUserID() userid: string, @ReqUserID() userid: string,
): Promise<UserMePermissionsResponse> { ): Promise<UserMePermissionsResponse> {

View File

@@ -7,7 +7,6 @@ import {
Post, Post,
Res, Res,
} from '@nestjs/common'; } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import type { FastifyReply } from 'fastify'; import type { FastifyReply } from 'fastify';
import { import {
ImageDeleteRequest, ImageDeleteRequest,
@@ -22,11 +21,12 @@ import {
} from 'picsur-shared/dist/dto/api/image-manage.dto'; } from 'picsur-shared/dist/dto/api/image-manage.dto';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum'; import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { import {
Fail,
FT, FT,
Fail,
HasFailed, HasFailed,
ThrowIfFailed, ThrowIfFailed,
} from 'picsur-shared/dist/types/failable'; } from 'picsur-shared/dist/types/failable';
import { EasyThrottle } from '../../decorators/easy-throttle.decorator';
import { PostFiles } from '../../decorators/multipart/multipart.decorator'; import { PostFiles } from '../../decorators/multipart/multipart.decorator';
import type { FileIterator } from '../../decorators/multipart/postfiles.pipe'; import type { FileIterator } from '../../decorators/multipart/postfiles.pipe';
import { import {
@@ -37,6 +37,7 @@ import { ReqUserID } from '../../decorators/request-user.decorator';
import { Returns } from '../../decorators/returns.decorator'; import { Returns } from '../../decorators/returns.decorator';
import { ImageManagerService } from '../../managers/image/image.service'; import { ImageManagerService } from '../../managers/image/image.service';
import { GetNextAsync } from '../../util/iterator'; import { GetNextAsync } from '../../util/iterator';
@Controller('api/image') @Controller('api/image')
@RequiredPermissions(Permission.ImageUpload) @RequiredPermissions(Permission.ImageUpload)
export class ImageManageController { export class ImageManageController {
@@ -46,7 +47,7 @@ export class ImageManageController {
@Post('upload') @Post('upload')
@Returns(ImageUploadResponse) @Returns(ImageUploadResponse)
@Throttle(20) @EasyThrottle(20)
async uploadImage( async uploadImage(
@PostFiles(1) multipart: FileIterator, @PostFiles(1) multipart: FileIterator,
@ReqUserID() userid: string, @ReqUserID() userid: string,

View File

@@ -21,9 +21,6 @@ export const HelmetOptions: FastifyHelmetOptions = {
// Destroy reference to global object on new page // Destroy reference to global object on new page
crossOriginOpenerPolicy: true, crossOriginOpenerPolicy: true,
crossOriginResourcePolicy: true, crossOriginResourcePolicy: true,
// Dont fully understand the purpose of this
// But pretty sure we dont need it
expectCt: false,
// Do not send referrer header // Do not send referrer header
referrerPolicy: true, referrerPolicy: true,
// Ensure browser doesnt connect with HTTP // Ensure browser doesnt connect with HTTP

View File

@@ -124,7 +124,7 @@ async function bmpSharpOut(sharpImage: Sharp): Promise<SharpResult> {
const encoded = BMPencode(raw.data, { const encoded = BMPencode(raw.data, {
width: raw.info.width, width: raw.info.width,
height: raw.info.height, height: raw.info.height,
channels: raw.info.channels, channels: raw.info.channels as 3 | 4,
}); });
return { return {
@@ -141,7 +141,7 @@ async function qoiSharpOut(sharpImage: Sharp): Promise<SharpResult> {
const encoded = QOIencode(raw.data, { const encoded = QOIencode(raw.data, {
width: raw.info.width, width: raw.info.width,
height: raw.info.height, height: raw.info.height,
channels: raw.info.channels, channels: raw.info.channels as 3 | 4,
}); });
return { return {

View File

@@ -43,7 +43,8 @@
"ngx-auto-unsubscribe-decorator", "ngx-auto-unsubscribe-decorator",
"moment", "moment",
"platform", "platform",
"form-data" "form-data",
"ms"
], ],
"optimization": true, "optimization": true,
"webWorkerTsConfig": "tsconfig.worker.json", "webWorkerTsConfig": "tsconfig.worker.json",

View File

@@ -14,40 +14,40 @@
"purge": "rm -rf dist && rm -rf node_modules && rm -rf .angular" "purge": "rm -rf dist && rm -rf node_modules && rm -rf .angular"
}, },
"devDependencies": { "devDependencies": {
"@angular-builders/custom-webpack": "^16.0.0", "@angular-builders/custom-webpack": "^17.0.0",
"@angular-devkit/build-angular": "^16.1.0", "@angular-devkit/build-angular": "^17.0.3",
"@angular/animations": "^16.1.1", "@angular/animations": "^17.0.4",
"@angular/cdk": "^16.1.1", "@angular/cdk": "^17.0.1",
"@angular/cli": "^16.1.0", "@angular/cli": "^17.0.3",
"@angular/common": "^16.1.1", "@angular/common": "^17.0.4",
"@angular/compiler": "^16.1.1", "@angular/compiler": "^17.0.4",
"@angular/compiler-cli": "^16.1.1", "@angular/compiler-cli": "^17.0.4",
"@angular/core": "^16.1.1", "@angular/core": "^17.0.4",
"@angular/forms": "^16.1.1", "@angular/forms": "^17.0.4",
"@angular/material": "^16.1.1", "@angular/material": "^17.0.1",
"@angular/platform-browser": "^16.1.1", "@angular/platform-browser": "^17.0.4",
"@angular/platform-browser-dynamic": "^16.1.1", "@angular/platform-browser-dynamic": "^17.0.4",
"@angular/router": "^16.1.1", "@angular/router": "^17.0.4",
"@babel/cli": "^7.22.5", "@babel/cli": "^7.23.4",
"@babel/core": "^7.22.5", "@babel/core": "^7.23.3",
"@babel/preset-env": "^7.22.5", "@babel/preset-env": "^7.23.3",
"@fontsource/roboto": "^5.0.3", "@fontsource/roboto": "^5.0.8",
"@ng-web-apis/common": "^2.1.0", "@ng-web-apis/common": "^3.0.6",
"@ng-web-apis/resize-observer": "^2.0.0", "@ng-web-apis/resize-observer": "^3.0.6",
"@ngui/common": "^1.0.0", "@ngui/common": "^1.0.0",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@types/ackee-tracker": "^5.0.2", "@types/ackee-tracker": "^5.0.4",
"@types/node": "^20.3.1", "@types/node": "^20.10.0",
"@types/resize-observer-browser": "^0.1.7", "@types/resize-observer-browser": "^0.1.11",
"@types/validator": "^13.7.17", "@types/validator": "^13.11.7",
"ackee-tracker": "^5.1.0", "ackee-tracker": "^5.1.0",
"axios": "^1.4.0", "axios": "^1.6.2",
"babel-loader": "^9.1.2", "babel-loader": "^9.1.3",
"bootstrap": "^5.3.0", "bootstrap": "^5.3.2",
"browserslist": "^4.21.8", "browserslist": "^4.22.1",
"caniuse-lite": "^1.0.30001503", "caniuse-lite": "^1.0.30001565",
"fuse.js": "^6.6.2", "fuse.js": "^7.0.0",
"material-icons": "^1.13.8", "material-icons": "^1.13.12",
"moment": "^2.29.4", "moment": "^2.29.4",
"ng-mat-select-infinite-scroll": "^4.0.0", "ng-mat-select-infinite-scroll": "^4.0.0",
"ngx-auto-unsubscribe-decorator": "^1.1.0", "ngx-auto-unsubscribe-decorator": "^1.1.0",
@@ -56,11 +56,11 @@
"picsur-shared": "*", "picsur-shared": "*",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"tslib": "^2.5.3", "tslib": "^2.6.2",
"typescript": "^5.1.3", "typescript": "^5.3.2",
"webpack-bundle-analyzer": "^4.9.0", "webpack-bundle-analyzer": "^4.10.1",
"zod": "^3.21.4", "zod": "^3.22.4",
"zone.js": "^0.13.1" "zone.js": "^0.14.2"
}, },
"dependencies": { "dependencies": {
"@leteu/jwt-decoder": "^1.0.4" "@leteu/jwt-decoder": "^1.0.4"

View File

@@ -52,8 +52,10 @@ mat-toolbar {
/* Make sure the toolbar will stay on top of the content as it scrolls past. */ /* Make sure the toolbar will stay on top of the content as it scrolls past. */
z-index: 2; z-index: 2;
box-shadow: 0px 2px 5px -3px rgba(0, 0, 0, 0.2), box-shadow:
0px 5px 8px 0px rgba(0, 0, 0, 0.14), 0px 1px 14px 0px rgba(0, 0, 0, 0.12); 0px 2px 5px -3px rgba(0, 0, 0, 0.2),
0px 5px 8px 0px rgba(0, 0, 0, 0.14),
0px 1px 14px 0px rgba(0, 0, 0, 0.12);
} }
.loading-bar { .loading-bar {

View File

@@ -80,12 +80,12 @@ export class PrefOptionComponent implements OnInit {
this.pref.type === 'string' this.pref.type === 'string'
? `Updated ${this.name}` ? `Updated ${this.name}`
: this.pref.type === 'number' : this.pref.type === 'number'
? `Updated ${this.name}` ? `Updated ${this.name}`
: this.pref.type === 'boolean' : this.pref.type === 'boolean'
? value ? value
? `Enabled ${this.name}` ? `Enabled ${this.name}`
: `Disabled ${this.name}` : `Disabled ${this.name}`
: ''; : '';
this.errorService.success(message); this.errorService.success(message);
} else { } else {
this.errorService.showFailure(result, this.logger); this.errorService.showFailure(result, this.logger);

View File

@@ -29,18 +29,18 @@ export class InfoService {
return this.infoSubject.value; return this.infoSubject.value;
} }
private infoSubject: BehaviorSubject<ServerInfo>;
constructor( constructor(
@Inject(LOCATION) private readonly location: Location, @Inject(LOCATION) private readonly location: Location,
private readonly api: ApiService, private readonly api: ApiService,
private readonly infoStorage: InfoStorageService, private readonly infoStorage: InfoStorageService,
) { ) {
this.updateInfo().catch((e) => this.logger.warn(e)); this.updateInfo().catch((e) => this.logger.warn(e));
this.infoSubject = new BehaviorSubject<ServerInfo>(
this.infoStorage?.get() ?? new ServerInfo(),
);
} }
private infoSubject = new BehaviorSubject<ServerInfo>(
this.infoStorage?.get() ?? new ServerInfo(),
);
public async getLoadedSnapshot(): Promise<ServerInfo> { public async getLoadedSnapshot(): Promise<ServerInfo> {
if (this.isLoaded()) { if (this.isLoaded()) {
return this.snapshot; return this.snapshot;

View File

@@ -60,16 +60,16 @@ export class BootstrapService {
const size = xxxl const size = xxxl
? BSScreenSize.xxxl ? BSScreenSize.xxxl
: xxl : xxl
? BSScreenSize.xxl ? BSScreenSize.xxl
: xl : xl
? BSScreenSize.xl ? BSScreenSize.xl
: lg : lg
? BSScreenSize.lg ? BSScreenSize.lg
: md : md
? BSScreenSize.md ? BSScreenSize.md
: sm : sm
? BSScreenSize.sm ? BSScreenSize.sm
: BSScreenSize.xs; : BSScreenSize.xs;
this.screenSizeSubject.next(size); this.screenSizeSubject.next(size);
}); });

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />

View File

@@ -23,22 +23,22 @@
"terser": ">=5.14.2", "terser": ">=5.14.2",
"typeorm": ">=0.3.9", "typeorm": ">=0.3.9",
"jsonwebtoken": ">=9.0.0", "jsonwebtoken": ">=9.0.0",
"typescript": "~4.9.5", "typescript": "~5.2.0",
"webpack": ">=5.76.0", "webpack": ">=5.76.0",
"fastify-static": "npm:@fastify/static@*", "fastify-static": "npm:@fastify/static@*",
"fastify-formbody": "npm:@fastify/formbody@*", "fastify-formbody": "npm:@fastify/formbody@*",
"minimist": "npm:minimist-lite@*" "minimist": "npm:minimist-lite@*"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^5.59.11", "@typescript-eslint/parser": "^6.13.1",
"eslint": "^8.42.0", "eslint": "^8.54.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-require-extensions": "^0.1.3", "eslint-plugin-require-extensions": "^0.1.3",
"ms": "3.0.0-canary.1", "ms": "2.1.3",
"prettier": "^2.8.8", "prettier": "^3.1.0",
"prettier-plugin-sh": "^0.12.8" "prettier-plugin-sh": "^0.13.1"
}, },
"packageManager": "yarn@3.2.2" "packageManager": "yarn@3.2.2"
} }

View File

@@ -14,12 +14,13 @@
}, },
"dependencies": { "dependencies": {
"ms": "2.1.3", "ms": "2.1.3",
"zod": "^3.21.4" "zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.3.1", "@types/ms": "^0.7.34",
"@types/node": "^20.10.0",
"tsc-watch": "^6.0.4", "tsc-watch": "^6.0.4",
"typescript": "^5.1.3" "typescript": "^5.3.2"
}, },
"scripts": { "scripts": {
"clean": "rm -rf ./dist", "clean": "rm -rf ./dist",

View File

@@ -2,24 +2,24 @@ import { z } from 'zod';
import { EImageSchema } from '../../entities/image.entity'; import { EImageSchema } from '../../entities/image.entity';
import { EUserSchema } from '../../entities/user.entity'; import { EUserSchema } from '../../entities/user.entity';
import { createZodDto } from '../../util/create-zod-dto'; import { createZodDto } from '../../util/create-zod-dto';
import { ParseBool, ParseInt } from '../../util/parse-simple'; import { ParseBoolZ, ParseIntZ } from '../../util/parse-simple';
import { ImageEntryVariant } from '../image-entry-variant.enum'; import { ImageEntryVariant } from '../image-entry-variant.enum';
export const ImageRequestParamsSchema = z export const ImageRequestParamsSchema = z
.object({ .object({
height: z.preprocess(ParseInt, z.number().int().min(1).max(32767)), height: z.preprocess(ParseIntZ, z.number().int().min(1).max(32767)),
width: z.preprocess(ParseInt, z.number().int().min(1).max(32767)), width: z.preprocess(ParseIntZ, z.number().int().min(1).max(32767)),
rotate: z.preprocess( rotate: z.preprocess(
ParseInt, ParseIntZ,
z.number().int().multipleOf(90).min(0).max(360), z.number().int().multipleOf(90).min(0).max(360),
), ),
flipx: z.preprocess(ParseBool, z.boolean()), flipx: z.preprocess(ParseBoolZ, z.boolean()),
flipy: z.preprocess(ParseBool, z.boolean()), flipy: z.preprocess(ParseBoolZ, z.boolean()),
greyscale: z.preprocess(ParseBool, z.boolean()), greyscale: z.preprocess(ParseBoolZ, z.boolean()),
noalpha: z.preprocess(ParseBool, z.boolean()), noalpha: z.preprocess(ParseBoolZ, z.boolean()),
negative: z.preprocess(ParseBool, z.boolean()), negative: z.preprocess(ParseBoolZ, z.boolean()),
shrinkonly: z.preprocess(ParseBool, z.boolean()), shrinkonly: z.preprocess(ParseBoolZ, z.boolean()),
quality: z.preprocess(ParseInt, z.number().int().min(1).max(100)), quality: z.preprocess(ParseIntZ, z.number().int().min(1).max(100)),
}) })
.partial(); .partial();

View File

@@ -10,6 +10,10 @@ export const ParseBool = <T extends boolean | null = null>(
return fallback === undefined ? (null as T) : fallback; return fallback === undefined ? (null as T) : fallback;
}; };
export const ParseBoolZ = (value: unknown): boolean | null => {
return ParseBool(value, null);
};
export const ParseInt = <T extends number | null = null>( export const ParseInt = <T extends number | null = null>(
value: unknown, value: unknown,
fallback?: T, fallback?: T,
@@ -23,8 +27,12 @@ export const ParseInt = <T extends number | null = null>(
return fallback === undefined return fallback === undefined
? (null as T) ? (null as T)
: fallback === null : fallback === null
? fallback ? fallback
: Math.round(fallback); : Math.round(fallback);
};
export const ParseIntZ = (value: unknown): number | null => {
return ParseInt(value, null);
}; };
export const ParseString = <T extends string | null = null>( export const ParseString = <T extends string | null = null>(
@@ -36,3 +44,7 @@ export const ParseString = <T extends string | null = null>(
if (typeof value === 'number') return value.toString(); if (typeof value === 'number') return value.toString();
return fallback === undefined ? (null as T) : fallback; return fallback === undefined ? (null as T) : fallback;
}; };
export const ParseStringZ = (value: unknown): string | null => {
return ParseString(value, null);
};

8463
yarn.lock

File diff suppressed because it is too large Load Diff