diff --git a/server/src/dtos/session.dto.ts b/server/src/dtos/session.dto.ts index d96d7819adcab..dab1bf62b5b39 100644 --- a/server/src/dtos/session.dto.ts +++ b/server/src/dtos/session.dto.ts @@ -1,4 +1,4 @@ -import { SessionEntity } from 'src/entities/session.entity'; +import { SessionItem } from 'src/types'; export class SessionResponseDto { id!: string; @@ -9,7 +9,7 @@ export class SessionResponseDto { deviceOS!: string; } -export const mapSession = (entity: SessionEntity, currentId?: string): SessionResponseDto => ({ +export const mapSession = (entity: SessionItem, currentId?: string): SessionResponseDto => ({ id: entity.id, createdAt: entity.createdAt.toISOString(), updatedAt: entity.updatedAt.toISOString(), diff --git a/server/src/interfaces/session.interface.ts b/server/src/interfaces/session.interface.ts deleted file mode 100644 index 8d695fbfc29c0..0000000000000 --- a/server/src/interfaces/session.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Insertable, Updateable } from 'kysely'; -import { Sessions } from 'src/db'; -import { SessionEntity } from 'src/entities/session.entity'; - -export const ISessionRepository = 'ISessionRepository'; - -type E = SessionEntity; -export type SessionSearchOptions = { updatedBefore: Date }; - -export interface ISessionRepository { - search(options: SessionSearchOptions): Promise; - create(dto: Insertable): Promise; - update(id: string, dto: Updateable): Promise; - delete(id: string): Promise; - getByToken(token: string): Promise; - getByUserId(userId: string): Promise; -} diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index 0c3cf2cd778bb..cb870bd3395c6 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -10,7 +10,6 @@ import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; -import { ISessionRepository } from 'src/interfaces/session.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { IStackRepository } from 'src/interfaces/stack.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; @@ -72,6 +71,7 @@ export const repositories = [ NotificationRepository, OAuthRepository, ProcessRepository, + SessionRepository, ServerInfoRepository, SystemMetadataRepository, TelemetryRepository, @@ -93,7 +93,6 @@ export const providers = [ { provide: IPartnerRepository, useClass: PartnerRepository }, { provide: IPersonRepository, useClass: PersonRepository }, { provide: ISearchRepository, useClass: SearchRepository }, - { provide: ISessionRepository, useClass: SessionRepository }, { provide: ISharedLinkRepository, useClass: SharedLinkRepository }, { provide: IStackRepository, useClass: StackRepository }, { provide: IStorageRepository, useClass: StorageRepository }, diff --git a/server/src/repositories/session.repository.ts b/server/src/repositories/session.repository.ts index 3e6c8977212a7..3e490bdc8495f 100644 --- a/server/src/repositories/session.repository.ts +++ b/server/src/repositories/session.repository.ts @@ -3,36 +3,37 @@ import { Insertable, Kysely, Updateable } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; import { DB, Sessions } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { SessionEntity, withUser } from 'src/entities/session.entity'; -import { ISessionRepository, SessionSearchOptions } from 'src/interfaces/session.interface'; +import { withUser } from 'src/entities/session.entity'; import { asUuid } from 'src/utils/database'; +export type SessionSearchOptions = { updatedBefore: Date }; + @Injectable() -export class SessionRepository implements ISessionRepository { +export class SessionRepository { constructor(@InjectKysely() private db: Kysely) {} @GenerateSql({ params: [{ updatedBefore: DummyValue.DATE }] }) - search(options: SessionSearchOptions): Promise { + search(options: SessionSearchOptions) { return this.db .selectFrom('sessions') .selectAll() .where('sessions.updatedAt', '<=', options.updatedBefore) - .execute() as Promise; + .execute(); } @GenerateSql({ params: [DummyValue.STRING] }) - getByToken(token: string): Promise { + getByToken(token: string) { return this.db .selectFrom('sessions') .innerJoinLateral(withUser, (join) => join.onTrue()) .selectAll('sessions') .select((eb) => eb.fn.toJson('user').as('user')) .where('sessions.token', '=', token) - .executeTakeFirst() as Promise; + .executeTakeFirst(); } @GenerateSql({ params: [DummyValue.UUID] }) - getByUserId(userId: string): Promise { + getByUserId(userId: string) { return this.db .selectFrom('sessions') .innerJoinLateral(withUser, (join) => join.onTrue()) @@ -41,30 +42,24 @@ export class SessionRepository implements ISessionRepository { .where('sessions.userId', '=', userId) .orderBy('sessions.updatedAt', 'desc') .orderBy('sessions.createdAt', 'desc') - .execute() as unknown as Promise; + .execute(); } - async create(dto: Insertable): Promise { - const { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } = await this.db - .insertInto('sessions') - .values(dto) - .returningAll() - .executeTakeFirstOrThrow(); - - return { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } as SessionEntity; + create(dto: Insertable) { + return this.db.insertInto('sessions').values(dto).returningAll().executeTakeFirstOrThrow(); } - update(id: string, dto: Updateable): Promise { + update(id: string, dto: Updateable) { return this.db .updateTable('sessions') .set(dto) .where('sessions.id', '=', asUuid(id)) .returningAll() - .executeTakeFirstOrThrow() as Promise; + .executeTakeFirstOrThrow(); } @GenerateSql({ params: [DummyValue.UUID] }) - async delete(id: string): Promise { + async delete(id: string) { await this.db.deleteFrom('sessions').where('id', '=', asUuid(id)).execute(); } } diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts index dc9f2162f4d11..e3b418d350cb0 100644 --- a/server/src/services/auth.service.spec.ts +++ b/server/src/services/auth.service.spec.ts @@ -5,11 +5,10 @@ import { UserEntity } from 'src/entities/user.entity'; import { AuthType, Permission } from 'src/enum'; import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { IEventRepository } from 'src/interfaces/event.interface'; -import { ISessionRepository } from 'src/interfaces/session.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; import { AuthService } from 'src/services/auth.service'; -import { IApiKeyRepository, IOAuthRepository, ISystemMetadataRepository } from 'src/types'; +import { IApiKeyRepository, IOAuthRepository, ISessionRepository, ISystemMetadataRepository } from 'src/types'; import { keyStub } from 'test/fixtures/api-key.stub'; import { authStub } from 'test/fixtures/auth.stub'; import { sessionStub } from 'test/fixtures/session.stub'; @@ -257,7 +256,7 @@ describe('AuthService', () => { it('should validate using authorization header', async () => { userMock.get.mockResolvedValue(userStub.user1); - sessionMock.getByToken.mockResolvedValue(sessionStub.valid); + sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any); await expect( sut.authenticate({ headers: { authorization: 'Bearer auth_token' }, @@ -362,7 +361,7 @@ describe('AuthService', () => { }); it('should return an auth dto', async () => { - sessionMock.getByToken.mockResolvedValue(sessionStub.valid); + sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any); await expect( sut.authenticate({ headers: { cookie: 'immich_access_token=auth_token' }, @@ -376,7 +375,7 @@ describe('AuthService', () => { }); it('should throw if admin route and not an admin', async () => { - sessionMock.getByToken.mockResolvedValue(sessionStub.valid); + sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any); await expect( sut.authenticate({ headers: { cookie: 'immich_access_token=auth_token' }, @@ -387,7 +386,7 @@ describe('AuthService', () => { }); it('should update when access time exceeds an hour', async () => { - sessionMock.getByToken.mockResolvedValue(sessionStub.inactive); + sessionMock.getByToken.mockResolvedValue(sessionStub.inactive as any); sessionMock.update.mockResolvedValue(sessionStub.valid); await expect( sut.authenticate({ diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index f46eb93111481..f4c6c6249e2ee 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -17,6 +17,7 @@ import { mapLoginResponse, } from 'src/dtos/auth.dto'; import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto'; +import { SessionEntity } from 'src/entities/session.entity'; import { UserEntity } from 'src/entities/user.entity'; import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum'; import { OAuthProfile } from 'src/repositories/oauth.repository'; @@ -338,7 +339,7 @@ export class AuthService extends BaseService { await this.sessionRepository.update(session.id, { id: session.id, updatedAt: new Date() }); } - return { user: session.user, session }; + return { user: session.user as unknown as UserEntity, session: session as unknown as SessionEntity }; } throw new UnauthorizedException('Invalid user token'); diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index 15e374a411584..8a690b752ef8d 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -18,7 +18,6 @@ import { IMoveRepository } from 'src/interfaces/move.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPersonRepository } from 'src/interfaces/person.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; -import { ISessionRepository } from 'src/interfaces/session.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { IStackRepository } from 'src/interfaces/stack.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface'; @@ -40,6 +39,7 @@ import { NotificationRepository } from 'src/repositories/notification.repository import { OAuthRepository } from 'src/repositories/oauth.repository'; import { ProcessRepository } from 'src/repositories/process.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository'; +import { SessionRepository } from 'src/repositories/session.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TrashRepository } from 'src/repositories/trash.repository'; @@ -80,7 +80,7 @@ export class BaseService { protected processRepository: ProcessRepository, @Inject(ISearchRepository) protected searchRepository: ISearchRepository, protected serverInfoRepository: ServerInfoRepository, - @Inject(ISessionRepository) protected sessionRepository: ISessionRepository, + protected sessionRepository: SessionRepository, @Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository, @Inject(IStackRepository) protected stackRepository: IStackRepository, @Inject(IStorageRepository) protected storageRepository: IStorageRepository, diff --git a/server/src/services/session.service.spec.ts b/server/src/services/session.service.spec.ts index 49d122771210d..8d989db5df520 100644 --- a/server/src/services/session.service.spec.ts +++ b/server/src/services/session.service.spec.ts @@ -1,7 +1,6 @@ -import { UserEntity } from 'src/entities/user.entity'; import { JobStatus } from 'src/interfaces/job.interface'; -import { ISessionRepository } from 'src/interfaces/session.interface'; import { SessionService } from 'src/services/session.service'; +import { ISessionRepository } from 'src/types'; import { authStub } from 'test/fixtures/auth.stub'; import { sessionStub } from 'test/fixtures/session.stub'; import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock'; @@ -38,7 +37,6 @@ describe('SessionService', () => { deviceType: '', id: '123', token: '420', - user: {} as UserEntity, userId: '42', }, ]); @@ -50,7 +48,7 @@ describe('SessionService', () => { describe('getAll', () => { it('should get the devices', async () => { - sessionMock.getByUserId.mockResolvedValue([sessionStub.valid, sessionStub.inactive]); + sessionMock.getByUserId.mockResolvedValue([sessionStub.valid as any, sessionStub.inactive]); await expect(sut.getAll(authStub.user1)).resolves.toEqual([ { createdAt: '2021-01-01T00:00:00.000Z', @@ -76,7 +74,7 @@ describe('SessionService', () => { describe('logoutDevices', () => { it('should logout all devices', async () => { - sessionMock.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid]); + sessionMock.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid] as any[]); await sut.deleteAll(authStub.user1); diff --git a/server/src/types.ts b/server/src/types.ts index 471a928a989a3..8e8e329b8b3c7 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -16,6 +16,7 @@ import { NotificationRepository } from 'src/repositories/notification.repository import { OAuthRepository } from 'src/repositories/oauth.repository'; import { ProcessRepository } from 'src/repositories/process.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository'; +import { SessionRepository } from 'src/repositories/session.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; import { MetricGroupRepository, TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TrashRepository } from 'src/repositories/trash.repository'; @@ -61,6 +62,7 @@ export type IMetricGroupRepository = RepositoryInterface; export type INotificationRepository = RepositoryInterface; export type IOAuthRepository = RepositoryInterface; export type IProcessRepository = RepositoryInterface; +export type ISessionRepository = RepositoryInterface; export type IServerInfoRepository = RepositoryInterface; export type ISystemMetadataRepository = RepositoryInterface; export type ITelemetryRepository = RepositoryInterface; @@ -81,6 +83,8 @@ export type MemoryItem = | Awaited> | Awaited>[0]; +export type SessionItem = Awaited>[0]; + export interface CropOptions { top: number; left: number; diff --git a/server/test/repositories/session.repository.mock.ts b/server/test/repositories/session.repository.mock.ts index e24d4c87dd6bb..41fa1640a22f8 100644 --- a/server/test/repositories/session.repository.mock.ts +++ b/server/test/repositories/session.repository.mock.ts @@ -1,4 +1,4 @@ -import { ISessionRepository } from 'src/interfaces/session.interface'; +import { ISessionRepository } from 'src/types'; import { Mocked, vitest } from 'vitest'; export const newSessionRepositoryMock = (): Mocked => { diff --git a/server/test/utils.ts b/server/test/utils.ts index 0a3c614159dfb..34af8877b1b6d 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -17,6 +17,7 @@ import { NotificationRepository } from 'src/repositories/notification.repository import { OAuthRepository } from 'src/repositories/oauth.repository'; import { ProcessRepository } from 'src/repositories/process.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository'; +import { SessionRepository } from 'src/repositories/session.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TrashRepository } from 'src/repositories/trash.repository'; @@ -39,6 +40,7 @@ import { IOAuthRepository, IProcessRepository, IServerInfoRepository, + ISessionRepository, ISystemMetadataRepository, ITrashRepository, IVersionHistoryRepository, @@ -170,7 +172,7 @@ export const newTestService = ( processMock as IProcessRepository as ProcessRepository, searchMock, serverInfoMock as IServerInfoRepository as ServerInfoRepository, - sessionMock, + sessionMock as ISessionRepository as SessionRepository, sharedLinkMock, stackMock, storageMock,