mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-12 23:05:39 +01:00
Fix some cors issues
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import cors from 'cors';
|
||||
import { IncomingMessage, ServerResponse } from 'http';
|
||||
import { EarlyConfigModule } from './config/early/early-config.module';
|
||||
import { ServeStaticConfigService } from './config/early/serve-static.config.service';
|
||||
import { TypeOrmConfigService } from './config/early/type-orm.config.service';
|
||||
@@ -9,6 +11,30 @@ import { AuthManagerModule } from './managers/auth/auth.module';
|
||||
import { DemoManagerModule } from './managers/demo/demo.module';
|
||||
import { PicsurRoutesModule } from './routes/routes.module';
|
||||
|
||||
const mainCorsConfig = cors({
|
||||
origin: '<origin>',
|
||||
});
|
||||
|
||||
const imageCorsConfig = cors({
|
||||
origin: '*',
|
||||
methods: ['GET', 'HEAD', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],
|
||||
exposedHeaders: ['Content-Type', 'Authorization', 'Accept'],
|
||||
credentials: false,
|
||||
// A month
|
||||
maxAge: 30 * 24 * 60 * 60,
|
||||
});
|
||||
|
||||
const imageCorpOverride = (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
next: Function,
|
||||
) => {
|
||||
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forRootAsync({
|
||||
@@ -25,4 +51,9 @@ import { PicsurRoutesModule } from './routes/routes.module';
|
||||
PicsurRoutesModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
export class AppModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(mainCorsConfig).exclude('/i').forRoutes('/');
|
||||
consumer.apply(imageCorsConfig, imageCorpOverride).forRoutes('/i');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.dto';
|
||||
import { AsyncFailable, Fail } from 'picsur-shared/dist/types';
|
||||
import { LessThan, Repository } from 'typeorm';
|
||||
import { ImageFileType } from '../../models/constants/image-file-types.const';
|
||||
import { In, LessThan, Repository } from 'typeorm';
|
||||
import { EImageDerivativeBackend } from '../../models/entities/image-derivative.entity';
|
||||
import { EImageFileBackend } from '../../models/entities/image-file.entity';
|
||||
|
||||
@@ -74,6 +74,28 @@ export class ImageFileDBService {
|
||||
}
|
||||
}
|
||||
|
||||
public async getFileMimes(
|
||||
imageId: string,
|
||||
types: ImageFileType[],
|
||||
): AsyncFailable<{ [key: string]: string | undefined }> {
|
||||
try {
|
||||
const found = await this.imageFileRepo.find({
|
||||
where: { image_id: imageId, type: In(types) },
|
||||
select: ['type', 'mime'],
|
||||
});
|
||||
|
||||
if (!found) return Fail('Image not found');
|
||||
return Object.fromEntries(
|
||||
types.map((type) => [
|
||||
type,
|
||||
found.find((f) => f.type === type)?.mime,
|
||||
]),
|
||||
);
|
||||
} catch (e) {
|
||||
return Fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
public async addDerivative(
|
||||
imageId: string,
|
||||
key: string,
|
||||
|
||||
@@ -31,7 +31,6 @@ async function bootstrap() {
|
||||
},
|
||||
);
|
||||
|
||||
// Configure nest app
|
||||
app.useGlobalFilters(new MainExceptionFilter());
|
||||
app.useGlobalInterceptors(new SuccessInterceptor(app.get(Reflector)));
|
||||
app.useGlobalPipes(new ZodValidationPipe());
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import Crypto from 'crypto';
|
||||
import { fileTypeFromBuffer, FileTypeResult } from 'file-type';
|
||||
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.dto';
|
||||
import { FullMime } from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import { UsrPreference } from 'picsur-shared/dist/dto/usr-preferences.dto';
|
||||
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { ParseMime } from 'picsur-shared/dist/util/parse-mime';
|
||||
import { IsQOI } from 'qoi-img';
|
||||
import { ImageDBService } from '../../collections/image-db/image-db.service';
|
||||
import { ImageFileDBService } from '../../collections/image-db/image-file-db.service';
|
||||
import { UsrPreferenceService } from '../../collections/preference-db/usr-preference-db.service';
|
||||
import { ImageFileType } from '../../models/constants/image-file-types.const';
|
||||
import { EImageDerivativeBackend } from '../../models/entities/image-derivative.entity';
|
||||
import { EImageFileBackend } from '../../models/entities/image-file.entity';
|
||||
import { EImageBackend } from '../../models/entities/image.entity';
|
||||
@@ -157,6 +157,26 @@ export class ImageManagerService {
|
||||
return ParseMime(mime);
|
||||
}
|
||||
|
||||
public async getAllFileMimes(imageId: string): AsyncFailable<{
|
||||
[ImageFileType.MASTER]: string;
|
||||
[ImageFileType.ORIGINAL]: string | undefined;
|
||||
}> {
|
||||
const result = await this.imageFilesService.getFileMimes(imageId, [
|
||||
ImageFileType.MASTER,
|
||||
ImageFileType.ORIGINAL,
|
||||
]);
|
||||
if (HasFailed(result)) return result;
|
||||
|
||||
if (result[ImageFileType.MASTER] === undefined) {
|
||||
return Fail('No master file found');
|
||||
}
|
||||
|
||||
return {
|
||||
[ImageFileType.MASTER]: result[ImageFileType.MASTER]!,
|
||||
[ImageFileType.ORIGINAL]: result[ImageFileType.ORIGINAL],
|
||||
};
|
||||
}
|
||||
|
||||
// Util stuff ==================================================================
|
||||
|
||||
private async getFullMimeFromBuffer(image: Buffer): AsyncFailable<FullMime> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ImageFileType } from 'picsur-shared/dist/dto/image-file-types.dto';
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
import { ImageFileType } from '../constants/image-file-types.const';
|
||||
|
||||
@Entity()
|
||||
@Unique(['image_id', 'type'])
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
Res
|
||||
} from '@nestjs/common';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
|
||||
import {
|
||||
ImageMetaResponse,
|
||||
ImageUploadResponse
|
||||
} from 'picsur-shared/dist/dto/api/image.dto';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator';
|
||||
import { ImageIdParam } from '../../decorators/image-id/image-id.decorator';
|
||||
@@ -88,16 +91,22 @@ export class ImageController {
|
||||
throw new NotFoundException('Could not find image');
|
||||
}
|
||||
|
||||
return image;
|
||||
const fileMimes = await this.imagesService.getAllFileMimes(id);
|
||||
if (HasFailed(fileMimes)) {
|
||||
this.logger.warn(fileMimes.getReason());
|
||||
throw new InternalServerErrorException('Could not get image mime');
|
||||
}
|
||||
|
||||
return { image, fileMimes };
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Returns(ImageMetaResponse)
|
||||
@Returns(ImageUploadResponse)
|
||||
@RequiredPermissions(Permission.ImageUpload)
|
||||
async uploadImage(
|
||||
@MultiPart() multipart: ImageUploadDto,
|
||||
@ReqUserID() userid: string,
|
||||
): Promise<ImageMetaResponse> {
|
||||
): Promise<ImageUploadResponse> {
|
||||
const image = await this.imagesService.upload(
|
||||
multipart.image.buffer,
|
||||
userid,
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||
import cors from 'cors';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DecoratorsModule } from '../../decorators/decorators.module';
|
||||
import { ImageManagerModule } from '../../managers/image/image.module';
|
||||
import { ImageController } from './image.controller';
|
||||
|
||||
const corsConfig = cors({
|
||||
// 48 hours
|
||||
maxAge: 1728000,
|
||||
});
|
||||
|
||||
@Module({
|
||||
imports: [ImageManagerModule, DecoratorsModule],
|
||||
controllers: [ImageController],
|
||||
})
|
||||
export class ImageModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(corsConfig).forRoutes('/i');
|
||||
}
|
||||
}
|
||||
export class ImageModule {}
|
||||
|
||||
@@ -16,6 +16,7 @@ const SettingsRoutes: PRoutes = [
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'general',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-12 py-3">
|
||||
<picsur-img class="uploadedimage" [src]="imageLinks.source"></picsur-img>
|
||||
<picsur-img class="uploadedimage" [src]="previewLink"></picsur-img>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
|
||||
@@ -18,6 +18,7 @@ export class ViewComponent implements OnInit {
|
||||
private utilService: UtilService
|
||||
) {}
|
||||
|
||||
public previewLink = '';
|
||||
public imageLinks = new ImageLinks();
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -32,7 +33,16 @@ export class ViewComponent implements OnInit {
|
||||
return this.utilService.quitError(metadata.getReason());
|
||||
}
|
||||
|
||||
this.imageLinks = this.imageService.CreateImageLinksFromID(id, 'qoi');
|
||||
const hasOriginal = metadata.fileMimes.original !== undefined;
|
||||
this.previewLink = this.imageService.GetImageURL(
|
||||
id,
|
||||
metadata.fileMimes.master
|
||||
);
|
||||
|
||||
this.imageLinks = this.imageService.CreateImageLinksFromID(
|
||||
id,
|
||||
hasOriginal ? null : metadata.fileMimes.master
|
||||
);
|
||||
}
|
||||
|
||||
download() {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
|
||||
import {
|
||||
ImageMetaResponse,
|
||||
ImageUploadResponse
|
||||
} from 'picsur-shared/dist/dto/api/image.dto';
|
||||
import { ImageLinks } from 'picsur-shared/dist/dto/image-links.dto';
|
||||
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
||||
import { Mime2Ext } from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import { AsyncFailable } from 'picsur-shared/dist/types';
|
||||
import { Open } from 'picsur-shared/dist/types/failable';
|
||||
import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto';
|
||||
@@ -14,7 +17,7 @@ export class ImageService {
|
||||
|
||||
public async UploadImage(image: File): AsyncFailable<string> {
|
||||
const result = await this.api.postForm(
|
||||
ImageMetaResponse,
|
||||
ImageUploadResponse,
|
||||
'/i',
|
||||
new ImageUploadRequest(image)
|
||||
);
|
||||
@@ -22,14 +25,15 @@ export class ImageService {
|
||||
return Open(result, 'id');
|
||||
}
|
||||
|
||||
public async GetImageMeta(image: string): AsyncFailable<EImage> {
|
||||
public async GetImageMeta(image: string): AsyncFailable<ImageMetaResponse> {
|
||||
return await this.api.get(ImageMetaResponse, `/i/meta/${image}`);
|
||||
}
|
||||
|
||||
public GetImageURL(image: string): string {
|
||||
public GetImageURL(image: string, mime: string | null): string {
|
||||
const baseURL = window.location.protocol + '//' + window.location.host;
|
||||
const extension = mime !== null ? Mime2Ext(mime) ?? 'error' : null;
|
||||
|
||||
return `${baseURL}/i/${image}`;
|
||||
return `${baseURL}/i/${image}${extension !== null ? '.' + extension : ''}`;
|
||||
}
|
||||
|
||||
public CreateImageLinks(imageURL: string): ImageLinks {
|
||||
@@ -42,7 +46,7 @@ export class ImageService {
|
||||
};
|
||||
}
|
||||
|
||||
public CreateImageLinksFromID(imageID: string, format: string): ImageLinks {
|
||||
return this.CreateImageLinks(this.GetImageURL(imageID + '.' + format));
|
||||
public CreateImageLinksFromID(imageID: string, mime: string | null): ImageLinks {
|
||||
return this.CreateImageLinks(this.GetImageURL(imageID, mime));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
import { EImageSchema } from '../../entities/image.entity';
|
||||
import { createZodDto } from '../../util/create-zod-dto';
|
||||
import { ImageFileType } from '../image-file-types.dto';
|
||||
|
||||
export const ImageMetaResponseSchema = EImageSchema;
|
||||
export const ImageMetaResponseSchema = z.object({
|
||||
image: EImageSchema,
|
||||
fileMimes: z.object({
|
||||
[ImageFileType.MASTER]: z.string(),
|
||||
[ImageFileType.ORIGINAL]: z.union([z.string(), z.undefined()]),
|
||||
}),
|
||||
});
|
||||
export class ImageMetaResponse extends createZodDto(ImageMetaResponseSchema) {}
|
||||
|
||||
export const ImageUploadResponseSchema = EImageSchema;
|
||||
export class ImageUploadResponse extends createZodDto(
|
||||
ImageUploadResponseSchema,
|
||||
) {}
|
||||
|
||||
Reference in New Issue
Block a user