mirror of
https://github.com/immich-app/immich.git
synced 2025-02-18 01:24:26 -05:00
refactor: session repository (#15957)
This commit is contained in:
parent
d7d4d22fe0
commit
758449e9f0
11 changed files with 38 additions and 57 deletions
|
@ -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(),
|
||||
|
|
|
@ -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<SessionEntity[]>;
|
||||
create(dto: Insertable<Sessions>): Promise<SessionEntity>;
|
||||
update(id: string, dto: Updateable<Sessions>): Promise<SessionEntity>;
|
||||
delete(id: string): Promise<void>;
|
||||
getByToken(token: string): Promise<E | undefined>;
|
||||
getByUserId(userId: string): Promise<E[]>;
|
||||
}
|
|
@ -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 },
|
||||
|
|
|
@ -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<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [{ updatedBefore: DummyValue.DATE }] })
|
||||
search(options: SessionSearchOptions): Promise<SessionEntity[]> {
|
||||
search(options: SessionSearchOptions) {
|
||||
return this.db
|
||||
.selectFrom('sessions')
|
||||
.selectAll()
|
||||
.where('sessions.updatedAt', '<=', options.updatedBefore)
|
||||
.execute() as Promise<SessionEntity[]>;
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.STRING] })
|
||||
getByToken(token: string): Promise<SessionEntity | undefined> {
|
||||
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<SessionEntity | undefined>;
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getByUserId(userId: string): Promise<SessionEntity[]> {
|
||||
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<SessionEntity[]>;
|
||||
.execute();
|
||||
}
|
||||
|
||||
async create(dto: Insertable<Sessions>): Promise<SessionEntity> {
|
||||
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<Sessions>) {
|
||||
return this.db.insertInto('sessions').values(dto).returningAll().executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
update(id: string, dto: Updateable<Sessions>): Promise<SessionEntity> {
|
||||
update(id: string, dto: Updateable<Sessions>) {
|
||||
return this.db
|
||||
.updateTable('sessions')
|
||||
.set(dto)
|
||||
.where('sessions.id', '=', asUuid(id))
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow() as Promise<SessionEntity>;
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async delete(id: string): Promise<void> {
|
||||
async delete(id: string) {
|
||||
await this.db.deleteFrom('sessions').where('id', '=', asUuid(id)).execute();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<MetricGroupRepository>;
|
|||
export type INotificationRepository = RepositoryInterface<NotificationRepository>;
|
||||
export type IOAuthRepository = RepositoryInterface<OAuthRepository>;
|
||||
export type IProcessRepository = RepositoryInterface<ProcessRepository>;
|
||||
export type ISessionRepository = RepositoryInterface<SessionRepository>;
|
||||
export type IServerInfoRepository = RepositoryInterface<ServerInfoRepository>;
|
||||
export type ISystemMetadataRepository = RepositoryInterface<SystemMetadataRepository>;
|
||||
export type ITelemetryRepository = RepositoryInterface<TelemetryRepository>;
|
||||
|
@ -81,6 +83,8 @@ export type MemoryItem =
|
|||
| Awaited<ReturnType<IMemoryRepository['create']>>
|
||||
| Awaited<ReturnType<IMemoryRepository['search']>>[0];
|
||||
|
||||
export type SessionItem = Awaited<ReturnType<ISessionRepository['getByUserId']>>[0];
|
||||
|
||||
export interface CropOptions {
|
||||
top: number;
|
||||
left: number;
|
||||
|
|
|
@ -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<ISessionRepository> => {
|
||||
|
|
|
@ -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 = <T extends BaseService>(
|
|||
processMock as IProcessRepository as ProcessRepository,
|
||||
searchMock,
|
||||
serverInfoMock as IServerInfoRepository as ServerInfoRepository,
|
||||
sessionMock,
|
||||
sessionMock as ISessionRepository as SessionRepository,
|
||||
sharedLinkMock,
|
||||
stackMock,
|
||||
storageMock,
|
||||
|
|
Loading…
Add table
Reference in a new issue