mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
WIP
This commit is contained in:
parent
bf1f8da884
commit
1f5393d02c
1069
server/package-lock.json
generated
1069
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -35,10 +35,13 @@
|
|||||||
"email:dev": "email dev -p 3050 --dir src/emails"
|
"email:dev": "email dev -p 3050 --dir src/emails"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@apollo/server": "^4.11.3",
|
||||||
|
"@nestjs/apollo": "^13.0.2",
|
||||||
"@nestjs/bullmq": "^11.0.1",
|
"@nestjs/bullmq": "^11.0.1",
|
||||||
"@nestjs/common": "^11.0.4",
|
"@nestjs/common": "^11.0.4",
|
||||||
"@nestjs/core": "^11.0.4",
|
"@nestjs/core": "^11.0.4",
|
||||||
"@nestjs/event-emitter": "^3.0.0",
|
"@nestjs/event-emitter": "^3.0.0",
|
||||||
|
"@nestjs/graphql": "^13.0.2",
|
||||||
"@nestjs/platform-express": "^11.0.4",
|
"@nestjs/platform-express": "^11.0.4",
|
||||||
"@nestjs/platform-socket.io": "^11.0.4",
|
"@nestjs/platform-socket.io": "^11.0.4",
|
||||||
"@nestjs/schedule": "^5.0.0",
|
"@nestjs/schedule": "^5.0.0",
|
||||||
@ -63,6 +66,7 @@
|
|||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"geo-tz": "^8.0.0",
|
"geo-tz": "^8.0.0",
|
||||||
|
"graphql": "^16.10.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"i18n-iso-countries": "^7.6.0",
|
"i18n-iso-countries": "^7.6.0",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
|
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
||||||
import { BullModule } from '@nestjs/bullmq';
|
import { BullModule } from '@nestjs/bullmq';
|
||||||
import { Inject, Module, OnModuleDestroy, OnModuleInit, ValidationPipe } from '@nestjs/common';
|
import { Inject, Module, OnModuleDestroy, OnModuleInit, ValidationPipe } from '@nestjs/common';
|
||||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE, ModuleRef } from '@nestjs/core';
|
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE, ModuleRef } from '@nestjs/core';
|
||||||
|
import { GraphQLModule } from '@nestjs/graphql';
|
||||||
import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule';
|
import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { PostgresJSDialect } from 'kysely-postgres-js';
|
import { PostgresJSDialect } from 'kysely-postgres-js';
|
||||||
import { ClsModule } from 'nestjs-cls';
|
import { ClsModule } from 'nestjs-cls';
|
||||||
import { KyselyModule } from 'nestjs-kysely';
|
import { KyselyModule } from 'nestjs-kysely';
|
||||||
import { OpenTelemetryModule } from 'nestjs-otel';
|
import { OpenTelemetryModule } from 'nestjs-otel';
|
||||||
|
import { join } from 'node:path';
|
||||||
import postgres from 'postgres';
|
import postgres from 'postgres';
|
||||||
import { commands } from 'src/commands';
|
import { commands } from 'src/commands';
|
||||||
import { IWorker } from 'src/constants';
|
import { IWorker } from 'src/constants';
|
||||||
@ -24,6 +27,7 @@ import { providers, repositories } from 'src/repositories';
|
|||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository';
|
import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository';
|
||||||
|
import { resolvers } from 'src/resolvers';
|
||||||
import { services } from 'src/services';
|
import { services } from 'src/services';
|
||||||
import { CliService } from 'src/services/cli.service';
|
import { CliService } from 'src/services/cli.service';
|
||||||
import { DatabaseService } from 'src/services/database.service';
|
import { DatabaseService } from 'src/services/database.service';
|
||||||
@ -104,9 +108,28 @@ class BaseModule implements OnModuleInit, OnModuleDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [...imports, ScheduleModule.forRoot()],
|
imports: [
|
||||||
|
...imports,
|
||||||
|
ScheduleModule.forRoot(),
|
||||||
|
GraphQLModule.forRoot<ApolloDriverConfig>({
|
||||||
|
driver: ApolloDriver,
|
||||||
|
playground: true,
|
||||||
|
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
|
||||||
|
sortSchema: true,
|
||||||
|
debug: true,
|
||||||
|
buildSchemaOptions: {
|
||||||
|
numberScalarMode: 'integer',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
controllers: [...controllers],
|
controllers: [...controllers],
|
||||||
providers: [...common, ...middleware, { provide: IWorker, useValue: ImmichWorker.API }],
|
providers: [
|
||||||
|
//
|
||||||
|
...common,
|
||||||
|
...middleware,
|
||||||
|
...resolvers,
|
||||||
|
{ provide: IWorker, useValue: ImmichWorker.API },
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class ApiModule extends BaseModule {}
|
export class ApiModule extends BaseModule {}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ export const citiesFile = 'cities500.txt';
|
|||||||
export const MOBILE_REDIRECT = 'app.immich:///oauth-callback';
|
export const MOBILE_REDIRECT = 'app.immich:///oauth-callback';
|
||||||
export const LOGIN_URL = '/auth/login?autoLaunch=0';
|
export const LOGIN_URL = '/auth/login?autoLaunch=0';
|
||||||
|
|
||||||
export const excludePaths = ['/.well-known/immich', '/custom.css', '/favicon.ico'];
|
export const excludePaths = ['/.well-known/immich', '/custom.css', '/favicon.ico', '/graphql'];
|
||||||
|
|
||||||
export const FACE_THUMBNAIL_SIZE = 250;
|
export const FACE_THUMBNAIL_SIZE = 250;
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
|
Req,
|
||||||
Res,
|
Res,
|
||||||
UploadedFile,
|
UploadedFile,
|
||||||
UseInterceptors,
|
UseInterceptors,
|
||||||
@ -38,8 +39,21 @@ export class UserController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated()
|
@Authenticated()
|
||||||
searchUsers(@Auth() auth: AuthDto): Promise<UserResponseDto[]> {
|
async searchUsers(@Req() req: Request): Promise<UserResponseDto[]> {
|
||||||
return this.service.search(auth);
|
const response = await fetch(`http://localhost:2283/graphql`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
operationName: null,
|
||||||
|
query: '{ users { id name email } }',
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
...req.headers,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await response.json();
|
||||||
|
return data.users;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('me')
|
@Get('me')
|
||||||
|
@ -5,19 +5,18 @@ import { AssetMediaResponseDto, AssetMediaStatus } from 'src/dtos/asset-media-re
|
|||||||
import { ImmichHeader } from 'src/enum';
|
import { ImmichHeader } from 'src/enum';
|
||||||
import { AuthenticatedRequest } from 'src/middleware/auth.guard';
|
import { AuthenticatedRequest } from 'src/middleware/auth.guard';
|
||||||
import { AssetMediaService } from 'src/services/asset-media.service';
|
import { AssetMediaService } from 'src/services/asset-media.service';
|
||||||
import { fromMaybeArray } from 'src/utils/request';
|
import { fromMaybeArray, getReqRes } from 'src/utils/request';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AssetUploadInterceptor implements NestInterceptor {
|
export class AssetUploadInterceptor implements NestInterceptor {
|
||||||
constructor(private service: AssetMediaService) {}
|
constructor(private service: AssetMediaService) {}
|
||||||
|
|
||||||
async intercept(context: ExecutionContext, next: CallHandler<any>) {
|
async intercept(context: ExecutionContext, next: CallHandler<any>) {
|
||||||
const req = context.switchToHttp().getRequest<AuthenticatedRequest>();
|
const { type, req, res } = getReqRes<AuthenticatedRequest, Response<AssetMediaResponseDto>>(context);
|
||||||
const res = context.switchToHttp().getResponse<Response<AssetMediaResponseDto>>();
|
|
||||||
|
|
||||||
const checksum = fromMaybeArray(req.headers[ImmichHeader.CHECKSUM]);
|
const checksum = fromMaybeArray(req.headers[ImmichHeader.CHECKSUM]);
|
||||||
const response = await this.service.getUploadAssetIdByChecksum(req.user, checksum);
|
const response = await this.service.getUploadAssetIdByChecksum(req.user, checksum);
|
||||||
if (response) {
|
if (response && type === 'http') {
|
||||||
res.status(200);
|
res.status(200);
|
||||||
return of({ status: AssetMediaStatus.DUPLICATE, id: response.id });
|
return of({ status: AssetMediaStatus.DUPLICATE, id: response.id });
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { AuthDto } from 'src/dtos/auth.dto';
|
|||||||
import { ImmichQuery, MetadataKey, Permission } from 'src/enum';
|
import { ImmichQuery, MetadataKey, Permission } from 'src/enum';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { AuthService, LoginDetails } from 'src/services/auth.service';
|
import { AuthService, LoginDetails } from 'src/services/auth.service';
|
||||||
|
import { getReqRes } from 'src/utils/request';
|
||||||
import { UAParser } from 'ua-parser-js';
|
import { UAParser } from 'ua-parser-js';
|
||||||
|
|
||||||
type AdminRoute = { admin?: true };
|
type AdminRoute = { admin?: true };
|
||||||
@ -35,7 +36,8 @@ export const Authenticated = (options?: AuthenticatedOptions): MethodDecorator =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Auth = createParamDecorator((data, context: ExecutionContext): AuthDto => {
|
export const Auth = createParamDecorator((data, context: ExecutionContext): AuthDto => {
|
||||||
return context.switchToHttp().getRequest<AuthenticatedRequest>().user;
|
const { req } = getReqRes<AuthenticatedRequest>(context);
|
||||||
|
return req.user;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const FileResponse = () =>
|
export const FileResponse = () =>
|
||||||
@ -86,12 +88,12 @@ export class AuthGuard implements CanActivate {
|
|||||||
sharedLink: sharedLinkRoute,
|
sharedLink: sharedLinkRoute,
|
||||||
permission,
|
permission,
|
||||||
} = { sharedLink: false, admin: false, ...options };
|
} = { sharedLink: false, admin: false, ...options };
|
||||||
const request = context.switchToHttp().getRequest<AuthRequest>();
|
const { req } = getReqRes<AuthenticatedRequest>(context);
|
||||||
|
|
||||||
request.user = await this.authService.authenticate({
|
req.user = await this.authService.authenticate({
|
||||||
headers: request.headers,
|
headers: req.headers,
|
||||||
queryParams: request.query as Record<string, string>,
|
queryParams: req.query as Record<string, string>,
|
||||||
metadata: { adminRoute, sharedLinkRoute, permission, uri: request.path },
|
metadata: { adminRoute, sharedLinkRoute, permission, uri: req.path },
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
|
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
|
||||||
import { Response } from 'express';
|
import { GqlContextType } from '@nestjs/graphql';
|
||||||
|
import { GraphQLError } from 'graphql';
|
||||||
import { ClsService } from 'nestjs-cls';
|
import { ClsService } from 'nestjs-cls';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { logGlobalError } from 'src/utils/logger';
|
import { logGlobalError } from 'src/utils/logger';
|
||||||
|
import { getReqRes } from 'src/utils/request';
|
||||||
|
|
||||||
|
type StructuredError = {
|
||||||
|
status: number;
|
||||||
|
body: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
@Catch()
|
@Catch()
|
||||||
export class GlobalExceptionFilter implements ExceptionFilter<Error> {
|
export class GlobalExceptionFilter implements ExceptionFilter<Error> {
|
||||||
@ -14,15 +24,20 @@ export class GlobalExceptionFilter implements ExceptionFilter<Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
catch(error: Error, host: ArgumentsHost) {
|
catch(error: Error, host: ArgumentsHost) {
|
||||||
const ctx = host.switchToHttp();
|
const { res } = getReqRes(host);
|
||||||
const response = ctx.getResponse<Response>();
|
|
||||||
const { status, body } = this.fromError(error);
|
const { status, body } = this.fromError(error);
|
||||||
if (!response.headersSent) {
|
const message = { ...body, statusCode: status, correlationId: this.cls.getId() };
|
||||||
response.status(status).json({ ...body, statusCode: status, correlationId: this.cls.getId() });
|
|
||||||
|
if (host.getType<GqlContextType>() === 'graphql') {
|
||||||
|
throw new GraphQLError(body?.message || 'Error', { extensions: message });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(status).json(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fromError(error: Error) {
|
private fromError(error: Error): StructuredError {
|
||||||
logGlobalError(this.logger, error);
|
logGlobalError(this.logger, error);
|
||||||
|
|
||||||
if (error instanceof HttpException) {
|
if (error instanceof HttpException) {
|
||||||
@ -34,7 +49,7 @@ export class GlobalExceptionFilter implements ExceptionFilter<Error> {
|
|||||||
body = { message: body };
|
body = { message: body };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { status, body };
|
return { status, body } as StructuredError;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
|
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
|
||||||
import { Request, Response } from 'express';
|
|
||||||
import { Observable, finalize } from 'rxjs';
|
import { Observable, finalize } from 'rxjs';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { getReqRes } from 'src/utils/request';
|
||||||
|
|
||||||
const maxArrayLength = 100;
|
const maxArrayLength = 100;
|
||||||
const replacer = (key: string, value: unknown) => {
|
const replacer = (key: string, value: unknown) => {
|
||||||
@ -23,10 +23,7 @@ export class LoggingInterceptor implements NestInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
|
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
|
||||||
const handler = context.switchToHttp();
|
const { req, res } = getReqRes(context);
|
||||||
const req = handler.getRequest<Request>();
|
|
||||||
const res = handler.getResponse<Response>();
|
|
||||||
|
|
||||||
const { method, ip, url } = req;
|
const { method, ip, url } = req;
|
||||||
|
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
@ -35,9 +32,7 @@ export class LoggingInterceptor implements NestInterceptor {
|
|||||||
finalize(() => {
|
finalize(() => {
|
||||||
const finish = performance.now();
|
const finish = performance.now();
|
||||||
const duration = (finish - start).toFixed(2);
|
const duration = (finish - start).toFixed(2);
|
||||||
const { statusCode } = res;
|
this.logger.debug(`${method} ${url} ${res?.statusCode || ''} ${duration}ms ${ip}`);
|
||||||
|
|
||||||
this.logger.debug(`${method} ${url} ${statusCode} ${duration}ms ${ip}`);
|
|
||||||
if (req.body && Object.keys(req.body).length > 0) {
|
if (req.body && Object.keys(req.body).length > 0) {
|
||||||
this.logger.verbose(JSON.stringify(req.body, replacer));
|
this.logger.verbose(JSON.stringify(req.body, replacer));
|
||||||
}
|
}
|
||||||
|
27
server/src/models/user.model.ts
Normal file
27
server/src/models/user.model.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||||
|
import { UserAvatarColor } from 'src/enum';
|
||||||
|
|
||||||
|
registerEnumType(UserAvatarColor, {
|
||||||
|
name: 'UserAvatarColor',
|
||||||
|
});
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class User {
|
||||||
|
@Field()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
email!: string;
|
||||||
|
|
||||||
|
@Field(() => UserAvatarColor)
|
||||||
|
avatarColor!: UserAvatarColor;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
profileImagePath!: string;
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
profileChangedAt!: Date;
|
||||||
|
}
|
3
server/src/resolvers/index.ts
Normal file
3
server/src/resolvers/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { UsersResolver } from 'src/resolvers/user.resolver';
|
||||||
|
|
||||||
|
export const resolvers = [UsersResolver];
|
22
server/src/resolvers/user.resolver.ts
Normal file
22
server/src/resolvers/user.resolver.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Args, Int, Query, Resolver } from '@nestjs/graphql';
|
||||||
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
|
import { User } from 'src/models/user.model';
|
||||||
|
import { UserService } from 'src/services/user.service';
|
||||||
|
|
||||||
|
@Resolver(() => User)
|
||||||
|
export class UsersResolver {
|
||||||
|
constructor(private service: UserService) {}
|
||||||
|
|
||||||
|
@Authenticated()
|
||||||
|
@Query(() => User)
|
||||||
|
async user(@Args('id', { type: () => Int }) id: string) {
|
||||||
|
return this.service.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Authenticated()
|
||||||
|
@Query(() => [User])
|
||||||
|
async users(@Auth() auth: AuthDto) {
|
||||||
|
return this.service.search(auth);
|
||||||
|
}
|
||||||
|
}
|
35
server/src/schema.gql
Normal file
35
server/src/schema.gql
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# ------------------------------------------------------
|
||||||
|
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
|
||||||
|
# ------------------------------------------------------
|
||||||
|
|
||||||
|
"""
|
||||||
|
A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format.
|
||||||
|
"""
|
||||||
|
scalar DateTime
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
user(id: Int!): User!
|
||||||
|
users: [User!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type User {
|
||||||
|
avatarColor: UserAvatarColor!
|
||||||
|
email: String!
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
profileChangedAt: DateTime
|
||||||
|
profileImagePath: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UserAvatarColor {
|
||||||
|
AMBER
|
||||||
|
BLUE
|
||||||
|
GRAY
|
||||||
|
GREEN
|
||||||
|
ORANGE
|
||||||
|
PINK
|
||||||
|
PRIMARY
|
||||||
|
PURPLE
|
||||||
|
RED
|
||||||
|
YELLOW
|
||||||
|
}
|
@ -1,5 +1,22 @@
|
|||||||
|
import { ArgumentsHost } from '@nestjs/common';
|
||||||
|
import { GqlArgumentsHost, GqlContextType } from '@nestjs/graphql';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
|
||||||
export const fromChecksum = (checksum: string): Buffer => {
|
export const fromChecksum = (checksum: string): Buffer => {
|
||||||
return Buffer.from(checksum, checksum.length === 28 ? 'base64' : 'hex');
|
return Buffer.from(checksum, checksum.length === 28 ? 'base64' : 'hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fromMaybeArray = <T>(param: T | T[]) => (Array.isArray(param) ? param[0] : param);
|
export const fromMaybeArray = <T>(param: T | T[]) => (Array.isArray(param) ? param[0] : param);
|
||||||
|
|
||||||
|
export const getReqRes = <Req extends Request = Request, Res extends Response = Response>(
|
||||||
|
context: ArgumentsHost,
|
||||||
|
): { type: GqlContextType; req: Req; res: Res } => {
|
||||||
|
const type = context.getType<GqlContextType>();
|
||||||
|
if (type === 'graphql') {
|
||||||
|
const ctx = GqlArgumentsHost.create(context).getContext();
|
||||||
|
return { type, req: ctx.req, res: ctx.req.res };
|
||||||
|
}
|
||||||
|
|
||||||
|
const http = context.switchToHttp();
|
||||||
|
return { type, req: http.getRequest(), res: http.getResponse() };
|
||||||
|
};
|
||||||
|
@ -26,6 +26,7 @@ export default defineConfig({
|
|||||||
// connect to a remote backend during web-only development
|
// connect to a remote backend during web-only development
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': upstream,
|
'/api': upstream,
|
||||||
|
'/graphql': upstream,
|
||||||
'/.well-known/immich': upstream,
|
'/.well-known/immich': upstream,
|
||||||
'/custom.css': upstream,
|
'/custom.css': upstream,
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user