mirror of
https://github.com/immich-app/immich.git
synced 2025-06-04 22:24:26 -04:00
refactor: more database types (#17490)
This commit is contained in:
parent
04b03f2924
commit
8943ec23ba
@ -163,6 +163,28 @@ export type Partner = {
|
|||||||
inTimeline: boolean;
|
inTimeline: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Place = {
|
||||||
|
admin1Code: string | null;
|
||||||
|
admin1Name: string | null;
|
||||||
|
admin2Code: string | null;
|
||||||
|
admin2Name: string | null;
|
||||||
|
alternateNames: string | null;
|
||||||
|
countryCode: string;
|
||||||
|
id: number;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
modificationDate: Date;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Session = {
|
||||||
|
id: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
deviceOS: string;
|
||||||
|
deviceType: string;
|
||||||
|
};
|
||||||
|
|
||||||
const userColumns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const;
|
const userColumns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const;
|
||||||
|
|
||||||
export const columns = {
|
export const columns = {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
|
import { IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
|
||||||
|
import { Place } from 'src/database';
|
||||||
import { PropertyLifecycle } from 'src/decorators';
|
import { PropertyLifecycle } from 'src/decorators';
|
||||||
import { AlbumResponseDto } from 'src/dtos/album.dto';
|
import { AlbumResponseDto } from 'src/dtos/album.dto';
|
||||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
||||||
import { AssetOrder, AssetType } from 'src/enum';
|
import { AssetOrder, AssetType } from 'src/enum';
|
||||||
import { SearchPlacesItem } from 'src/types';
|
|
||||||
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
|
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
|
||||||
|
|
||||||
class BaseSearchDto {
|
class BaseSearchDto {
|
||||||
@ -226,7 +226,7 @@ export class PlacesResponseDto {
|
|||||||
admin2name?: string;
|
admin2name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapPlaces(place: SearchPlacesItem): PlacesResponseDto {
|
export function mapPlaces(place: Place): PlacesResponseDto {
|
||||||
return {
|
return {
|
||||||
name: place.name,
|
name: place.name,
|
||||||
latitude: place.latitude,
|
latitude: place.latitude,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { SessionItem } from 'src/types';
|
import { Session } from 'src/database';
|
||||||
|
|
||||||
export class SessionResponseDto {
|
export class SessionResponseDto {
|
||||||
id!: string;
|
id!: string;
|
||||||
@ -9,7 +9,7 @@ export class SessionResponseDto {
|
|||||||
deviceOS!: string;
|
deviceOS!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapSession = (entity: SessionItem, currentId?: string): SessionResponseDto => ({
|
export const mapSession = (entity: Session, currentId?: string): SessionResponseDto => ({
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
createdAt: entity.createdAt.toISOString(),
|
createdAt: entity.createdAt.toISOString(),
|
||||||
updatedAt: entity.updatedAt.toISOString(),
|
updatedAt: entity.updatedAt.toISOString(),
|
||||||
|
@ -38,42 +38,11 @@ where
|
|||||||
|
|
||||||
-- SessionRepository.getByUserId
|
-- SessionRepository.getByUserId
|
||||||
select
|
select
|
||||||
"sessions".*,
|
"sessions".*
|
||||||
to_json("user") as "user"
|
|
||||||
from
|
from
|
||||||
"sessions"
|
"sessions"
|
||||||
inner join lateral (
|
inner join "users" on "users"."id" = "sessions"."userId"
|
||||||
select
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"email",
|
|
||||||
"profileImagePath",
|
|
||||||
"profileChangedAt",
|
|
||||||
"createdAt",
|
|
||||||
"updatedAt",
|
|
||||||
"deletedAt",
|
|
||||||
"isAdmin",
|
|
||||||
"status",
|
|
||||||
"oauthId",
|
|
||||||
"profileImagePath",
|
|
||||||
"shouldChangePassword",
|
|
||||||
"storageLabel",
|
|
||||||
"quotaSizeInBytes",
|
|
||||||
"quotaUsageInBytes",
|
|
||||||
(
|
|
||||||
select
|
|
||||||
array_agg("user_metadata") as "metadata"
|
|
||||||
from
|
|
||||||
"user_metadata"
|
|
||||||
where
|
|
||||||
"users"."id" = "user_metadata"."userId"
|
|
||||||
) as "metadata"
|
|
||||||
from
|
|
||||||
"users"
|
|
||||||
where
|
|
||||||
"users"."id" = "sessions"."userId"
|
|
||||||
and "users"."deletedAt" is null
|
and "users"."deletedAt" is null
|
||||||
) as "user" on true
|
|
||||||
where
|
where
|
||||||
"sessions"."userId" = $1
|
"sessions"."userId" = $1
|
||||||
order by
|
order by
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ExpressionBuilder, Insertable, Kysely, Updateable } from 'kysely';
|
import { Insertable, Kysely, Updateable } from 'kysely';
|
||||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { columns } from 'src/database';
|
import { columns } from 'src/database';
|
||||||
@ -9,22 +9,6 @@ import { asUuid } from 'src/utils/database';
|
|||||||
|
|
||||||
export type SessionSearchOptions = { updatedBefore: Date };
|
export type SessionSearchOptions = { updatedBefore: Date };
|
||||||
|
|
||||||
const withUser = (eb: ExpressionBuilder<DB, 'sessions'>) => {
|
|
||||||
return eb
|
|
||||||
.selectFrom('users')
|
|
||||||
.select(columns.userAdmin)
|
|
||||||
.select((eb) =>
|
|
||||||
eb
|
|
||||||
.selectFrom('user_metadata')
|
|
||||||
.whereRef('users.id', '=', 'user_metadata.userId')
|
|
||||||
.select((eb) => eb.fn('array_agg', [eb.table('user_metadata')]).as('metadata'))
|
|
||||||
.as('metadata'),
|
|
||||||
)
|
|
||||||
.whereRef('users.id', '=', 'sessions.userId')
|
|
||||||
.where('users.deletedAt', 'is', null)
|
|
||||||
.as('user');
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SessionRepository {
|
export class SessionRepository {
|
||||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
@ -60,9 +44,8 @@ export class SessionRepository {
|
|||||||
getByUserId(userId: string) {
|
getByUserId(userId: string) {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('sessions')
|
.selectFrom('sessions')
|
||||||
.innerJoinLateral(withUser, (join) => join.onTrue())
|
.innerJoin('users', (join) => join.onRef('users.id', '=', 'sessions.userId').on('users.deletedAt', 'is', null))
|
||||||
.selectAll('sessions')
|
.selectAll('sessions')
|
||||||
.select((eb) => eb.fn.toJson('user').as('user'))
|
|
||||||
.where('sessions.userId', '=', userId)
|
.where('sessions.userId', '=', userId)
|
||||||
.orderBy('sessions.updatedAt', 'desc')
|
.orderBy('sessions.updatedAt', 'desc')
|
||||||
.orderBy('sessions.createdAt', 'desc')
|
.orderBy('sessions.createdAt', 'desc')
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
import { AuthDto, SignUpDto } from 'src/dtos/auth.dto';
|
import { AuthDto, SignUpDto } from 'src/dtos/auth.dto';
|
||||||
import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
|
import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { AuthType, Permission } from 'src/enum';
|
import { AuthType, Permission } from 'src/enum';
|
||||||
import { AuthService } from 'src/services/auth.service';
|
import { AuthService } from 'src/services/auth.service';
|
||||||
import { sessionStub } from 'test/fixtures/session.stub';
|
|
||||||
import { sharedLinkStub } from 'test/fixtures/shared-link.stub';
|
import { sharedLinkStub } from 'test/fixtures/shared-link.stub';
|
||||||
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
||||||
import { userStub } from 'test/fixtures/user.stub';
|
import { userStub } from 'test/fixtures/user.stub';
|
||||||
@ -97,17 +97,19 @@ describe('AuthService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should successfully log the user in', async () => {
|
it('should successfully log the user in', async () => {
|
||||||
mocks.user.getByEmail.mockResolvedValue(userStub.user1);
|
const user = { ...factory.user(), password: 'immich_password' } as UserEntity;
|
||||||
mocks.session.create.mockResolvedValue(sessionStub.valid);
|
const session = factory.session();
|
||||||
|
mocks.user.getByEmail.mockResolvedValue(user);
|
||||||
|
mocks.session.create.mockResolvedValue(session);
|
||||||
|
|
||||||
await expect(sut.login(fixtures.login, loginDetails)).resolves.toEqual({
|
await expect(sut.login(fixtures.login, loginDetails)).resolves.toEqual({
|
||||||
accessToken: 'cmFuZG9tLWJ5dGVz',
|
accessToken: 'cmFuZG9tLWJ5dGVz',
|
||||||
userId: 'user-id',
|
userId: user.id,
|
||||||
userEmail: 'immich@test.com',
|
userEmail: user.email,
|
||||||
name: 'immich_name',
|
name: user.name,
|
||||||
profileImagePath: '',
|
profileImagePath: user.profileImagePath,
|
||||||
isAdmin: false,
|
isAdmin: user.isAdmin,
|
||||||
shouldChangePassword: false,
|
shouldChangePassword: user.shouldChangePassword,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1);
|
expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1);
|
||||||
@ -256,8 +258,14 @@ describe('AuthService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should validate using authorization header', async () => {
|
it('should validate using authorization header', async () => {
|
||||||
mocks.user.get.mockResolvedValue(userStub.user1);
|
const session = factory.session();
|
||||||
mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any);
|
const sessionWithToken = {
|
||||||
|
id: session.id,
|
||||||
|
updatedAt: session.updatedAt,
|
||||||
|
user: factory.authUser(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mocks.session.getByToken.mockResolvedValue(sessionWithToken);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.authenticate({
|
sut.authenticate({
|
||||||
@ -266,8 +274,8 @@ describe('AuthService', () => {
|
|||||||
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' },
|
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' },
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({
|
).resolves.toEqual({
|
||||||
user: userStub.user1,
|
user: sessionWithToken.user,
|
||||||
session: sessionStub.valid,
|
session: { id: session.id },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -371,7 +379,14 @@ describe('AuthService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return an auth dto', async () => {
|
it('should return an auth dto', async () => {
|
||||||
mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any);
|
const session = factory.session();
|
||||||
|
const sessionWithToken = {
|
||||||
|
id: session.id,
|
||||||
|
updatedAt: session.updatedAt,
|
||||||
|
user: factory.authUser(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mocks.session.getByToken.mockResolvedValue(sessionWithToken);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.authenticate({
|
sut.authenticate({
|
||||||
@ -380,13 +395,20 @@ describe('AuthService', () => {
|
|||||||
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' },
|
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' },
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({
|
).resolves.toEqual({
|
||||||
user: userStub.user1,
|
user: sessionWithToken.user,
|
||||||
session: sessionStub.valid,
|
session: { id: session.id },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if admin route and not an admin', async () => {
|
it('should throw if admin route and not an admin', async () => {
|
||||||
mocks.session.getByToken.mockResolvedValue(sessionStub.valid as any);
|
const session = factory.session();
|
||||||
|
const sessionWithToken = {
|
||||||
|
id: session.id,
|
||||||
|
updatedAt: session.updatedAt,
|
||||||
|
user: factory.authUser(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mocks.session.getByToken.mockResolvedValue(sessionWithToken);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.authenticate({
|
sut.authenticate({
|
||||||
@ -398,8 +420,15 @@ describe('AuthService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update when access time exceeds an hour', async () => {
|
it('should update when access time exceeds an hour', async () => {
|
||||||
mocks.session.getByToken.mockResolvedValue(sessionStub.inactive as any);
|
const session = factory.session({ updatedAt: DateTime.now().minus({ hours: 2 }).toJSDate() });
|
||||||
mocks.session.update.mockResolvedValue(sessionStub.valid);
|
const sessionWithToken = {
|
||||||
|
id: session.id,
|
||||||
|
updatedAt: session.updatedAt,
|
||||||
|
user: factory.authUser(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mocks.session.getByToken.mockResolvedValue(sessionWithToken);
|
||||||
|
mocks.session.update.mockResolvedValue(session);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.authenticate({
|
sut.authenticate({
|
||||||
@ -408,7 +437,8 @@ describe('AuthService', () => {
|
|||||||
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' },
|
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' },
|
||||||
}),
|
}),
|
||||||
).resolves.toBeDefined();
|
).resolves.toBeDefined();
|
||||||
expect(mocks.session.update.mock.calls[0][1]).toMatchObject({ id: 'not_active', updatedAt: expect.any(Date) });
|
|
||||||
|
expect(mocks.session.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -506,7 +536,7 @@ describe('AuthService', () => {
|
|||||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
||||||
mocks.user.getByEmail.mockResolvedValue(userStub.user1);
|
mocks.user.getByEmail.mockResolvedValue(userStub.user1);
|
||||||
mocks.user.update.mockResolvedValue(userStub.user1);
|
mocks.user.update.mockResolvedValue(userStub.user1);
|
||||||
mocks.session.create.mockResolvedValue(sessionStub.valid);
|
mocks.session.create.mockResolvedValue(factory.session());
|
||||||
|
|
||||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
||||||
oauthResponse,
|
oauthResponse,
|
||||||
@ -535,7 +565,7 @@ describe('AuthService', () => {
|
|||||||
mocks.user.getByEmail.mockResolvedValue(void 0);
|
mocks.user.getByEmail.mockResolvedValue(void 0);
|
||||||
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
||||||
mocks.user.create.mockResolvedValue(userStub.user1);
|
mocks.user.create.mockResolvedValue(userStub.user1);
|
||||||
mocks.session.create.mockResolvedValue(sessionStub.valid);
|
mocks.session.create.mockResolvedValue(factory.session());
|
||||||
|
|
||||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
||||||
oauthResponse,
|
oauthResponse,
|
||||||
@ -550,7 +580,7 @@ describe('AuthService', () => {
|
|||||||
mocks.user.getByEmail.mockResolvedValue(void 0);
|
mocks.user.getByEmail.mockResolvedValue(void 0);
|
||||||
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
||||||
mocks.user.create.mockResolvedValue(userStub.user1);
|
mocks.user.create.mockResolvedValue(userStub.user1);
|
||||||
mocks.session.create.mockResolvedValue(sessionStub.valid);
|
mocks.session.create.mockResolvedValue(factory.session());
|
||||||
mocks.oauth.getProfile.mockResolvedValue({ sub, email: undefined });
|
mocks.oauth.getProfile.mockResolvedValue({ sub, email: undefined });
|
||||||
|
|
||||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).rejects.toBeInstanceOf(
|
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).rejects.toBeInstanceOf(
|
||||||
@ -572,7 +602,7 @@ describe('AuthService', () => {
|
|||||||
it(`should use the mobile redirect override for a url of ${url}`, async () => {
|
it(`should use the mobile redirect override for a url of ${url}`, async () => {
|
||||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithMobileOverride);
|
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithMobileOverride);
|
||||||
mocks.user.getByOAuthId.mockResolvedValue(userStub.user1);
|
mocks.user.getByOAuthId.mockResolvedValue(userStub.user1);
|
||||||
mocks.session.create.mockResolvedValue(sessionStub.valid);
|
mocks.session.create.mockResolvedValue(factory.session());
|
||||||
|
|
||||||
await sut.callback({ url }, loginDetails);
|
await sut.callback({ url }, loginDetails);
|
||||||
|
|
||||||
|
@ -338,7 +338,9 @@ export class AuthService extends BaseService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
user: session.user,
|
user: session.user,
|
||||||
session,
|
session: {
|
||||||
|
id: session.id,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { JobStatus } from 'src/enum';
|
import { JobStatus } from 'src/enum';
|
||||||
import { SessionService } from 'src/services/session.service';
|
import { SessionService } from 'src/services/session.service';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { sessionStub } from 'test/fixtures/session.stub';
|
import { factory } from 'test/small.factory';
|
||||||
import { newTestService, ServiceMocks } from 'test/utils';
|
import { newTestService, ServiceMocks } from 'test/utils';
|
||||||
|
|
||||||
describe('SessionService', () => {
|
describe('SessionService', () => {
|
||||||
@ -45,40 +45,35 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
describe('getAll', () => {
|
describe('getAll', () => {
|
||||||
it('should get the devices', async () => {
|
it('should get the devices', async () => {
|
||||||
mocks.session.getByUserId.mockResolvedValue([sessionStub.valid as any, sessionStub.inactive]);
|
const currentSession = factory.session();
|
||||||
await expect(sut.getAll(authStub.user1)).resolves.toEqual([
|
const otherSession = factory.session();
|
||||||
{
|
const auth = factory.auth({ session: currentSession });
|
||||||
createdAt: '2021-01-01T00:00:00.000Z',
|
|
||||||
current: true,
|
mocks.session.getByUserId.mockResolvedValue([currentSession, otherSession]);
|
||||||
deviceOS: '',
|
|
||||||
deviceType: '',
|
await expect(sut.getAll(auth)).resolves.toEqual([
|
||||||
id: 'token-id',
|
expect.objectContaining({ current: true, id: currentSession.id }),
|
||||||
updatedAt: expect.any(String),
|
expect.objectContaining({ current: false, id: otherSession.id }),
|
||||||
},
|
|
||||||
{
|
|
||||||
createdAt: '2021-01-01T00:00:00.000Z',
|
|
||||||
current: false,
|
|
||||||
deviceOS: 'Android',
|
|
||||||
deviceType: 'Mobile',
|
|
||||||
id: 'not_active',
|
|
||||||
updatedAt: expect.any(String),
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(mocks.session.getByUserId).toHaveBeenCalledWith(authStub.user1.user.id);
|
expect(mocks.session.getByUserId).toHaveBeenCalledWith(auth.user.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('logoutDevices', () => {
|
describe('logoutDevices', () => {
|
||||||
it('should logout all devices', async () => {
|
it('should logout all devices', async () => {
|
||||||
mocks.session.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid] as any[]);
|
const currentSession = factory.session();
|
||||||
|
const otherSession = factory.session();
|
||||||
|
const auth = factory.auth({ session: currentSession });
|
||||||
|
|
||||||
|
mocks.session.getByUserId.mockResolvedValue([currentSession, otherSession]);
|
||||||
mocks.session.delete.mockResolvedValue();
|
mocks.session.delete.mockResolvedValue();
|
||||||
|
|
||||||
await sut.deleteAll(authStub.user1);
|
await sut.deleteAll(auth);
|
||||||
|
|
||||||
expect(mocks.session.getByUserId).toHaveBeenCalledWith(authStub.user1.user.id);
|
expect(mocks.session.getByUserId).toHaveBeenCalledWith(auth.user.id);
|
||||||
expect(mocks.session.delete).toHaveBeenCalledWith('not_active');
|
expect(mocks.session.delete).toHaveBeenCalledWith(otherSession.id);
|
||||||
expect(mocks.session.delete).not.toHaveBeenCalledWith('token-id');
|
expect(mocks.session.delete).not.toHaveBeenCalledWith(currentSession.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -13,20 +13,11 @@ import {
|
|||||||
TranscodeTarget,
|
TranscodeTarget,
|
||||||
VideoCodec,
|
VideoCodec,
|
||||||
} from 'src/enum';
|
} from 'src/enum';
|
||||||
import { SearchRepository } from 'src/repositories/search.repository';
|
|
||||||
import { SessionRepository } from 'src/repositories/session.repository';
|
|
||||||
|
|
||||||
export type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
|
export type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
|
||||||
|
|
||||||
export type RepositoryInterface<T extends object> = Pick<T, keyof T>;
|
export type RepositoryInterface<T extends object> = Pick<T, keyof T>;
|
||||||
|
|
||||||
type ISearchRepository = RepositoryInterface<SearchRepository>;
|
|
||||||
type ISessionRepository = RepositoryInterface<SessionRepository>;
|
|
||||||
|
|
||||||
export type SearchPlacesItem = Awaited<ReturnType<ISearchRepository['searchPlaces']>>[0];
|
|
||||||
|
|
||||||
export type SessionItem = Awaited<ReturnType<ISessionRepository['getByUserId']>>[0];
|
|
||||||
|
|
||||||
export interface CropOptions {
|
export interface CropOptions {
|
||||||
top: number;
|
top: number;
|
||||||
left: number;
|
left: number;
|
||||||
|
6
server/test/fixtures/auth.stub.ts
vendored
6
server/test/fixtures/auth.stub.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
|
import { Session } from 'src/database';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||||
import { SessionItem } from 'src/types';
|
|
||||||
|
|
||||||
const authUser = {
|
const authUser = {
|
||||||
admin: {
|
admin: {
|
||||||
@ -27,7 +27,7 @@ export const authStub = {
|
|||||||
user: authUser.user1,
|
user: authUser.user1,
|
||||||
session: {
|
session: {
|
||||||
id: 'token-id',
|
id: 'token-id',
|
||||||
} as SessionItem,
|
} as Session,
|
||||||
}),
|
}),
|
||||||
user2: Object.freeze<AuthDto>({
|
user2: Object.freeze<AuthDto>({
|
||||||
user: {
|
user: {
|
||||||
@ -40,7 +40,7 @@ export const authStub = {
|
|||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
id: 'token-id',
|
id: 'token-id',
|
||||||
} as SessionItem,
|
} as Session,
|
||||||
}),
|
}),
|
||||||
adminSharedLink: Object.freeze<AuthDto>({
|
adminSharedLink: Object.freeze<AuthDto>({
|
||||||
user: authUser.admin,
|
user: authUser.admin,
|
||||||
|
27
server/test/fixtures/session.stub.ts
vendored
27
server/test/fixtures/session.stub.ts
vendored
@ -1,27 +0,0 @@
|
|||||||
import { SessionItem } from 'src/types';
|
|
||||||
import { userStub } from 'test/fixtures/user.stub';
|
|
||||||
|
|
||||||
export const sessionStub = {
|
|
||||||
valid: Object.freeze<SessionItem>({
|
|
||||||
id: 'token-id',
|
|
||||||
token: 'auth_token',
|
|
||||||
userId: userStub.user1.id,
|
|
||||||
user: userStub.user1,
|
|
||||||
createdAt: new Date('2021-01-01'),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
deviceType: '',
|
|
||||||
deviceOS: '',
|
|
||||||
updateId: 'uuid-v7',
|
|
||||||
}),
|
|
||||||
inactive: Object.freeze<SessionItem>({
|
|
||||||
id: 'not_active',
|
|
||||||
token: 'auth_token',
|
|
||||||
userId: userStub.user1.id,
|
|
||||||
user: userStub.user1,
|
|
||||||
createdAt: new Date('2021-01-01'),
|
|
||||||
updatedAt: new Date('2021-01-01'),
|
|
||||||
deviceType: 'Mobile',
|
|
||||||
deviceOS: 'Android',
|
|
||||||
updateId: 'uuid-v7',
|
|
||||||
}),
|
|
||||||
};
|
|
@ -8,6 +8,7 @@ import {
|
|||||||
Library,
|
Library,
|
||||||
Memory,
|
Memory,
|
||||||
Partner,
|
Partner,
|
||||||
|
Session,
|
||||||
SidecarWriteAsset,
|
SidecarWriteAsset,
|
||||||
User,
|
User,
|
||||||
UserAdmin,
|
UserAdmin,
|
||||||
@ -31,7 +32,11 @@ export const newEmbedding = () => {
|
|||||||
return '[' + embedding + ']';
|
return '[' + embedding + ']';
|
||||||
};
|
};
|
||||||
|
|
||||||
const authFactory = ({ apiKey, ...user }: Partial<AuthUser> & { apiKey?: Partial<AuthApiKey> } = {}) => {
|
const authFactory = ({
|
||||||
|
apiKey,
|
||||||
|
session,
|
||||||
|
...user
|
||||||
|
}: Partial<AuthUser> & { apiKey?: Partial<AuthApiKey>; session?: { id: string } } = {}) => {
|
||||||
const auth: AuthDto = {
|
const auth: AuthDto = {
|
||||||
user: authUserFactory(user),
|
user: authUserFactory(user),
|
||||||
};
|
};
|
||||||
@ -40,6 +45,10 @@ const authFactory = ({ apiKey, ...user }: Partial<AuthUser> & { apiKey?: Partial
|
|||||||
auth.apiKey = authApiKeyFactory(apiKey);
|
auth.apiKey = authApiKeyFactory(apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
auth.session = { id: session.id };
|
||||||
|
}
|
||||||
|
|
||||||
return auth;
|
return auth;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -76,7 +85,7 @@ const partnerFactory = (partner: Partial<Partner> = {}) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const sessionFactory = () => ({
|
const sessionFactory = (session: Partial<Session> = {}) => ({
|
||||||
id: newUuid(),
|
id: newUuid(),
|
||||||
createdAt: newDate(),
|
createdAt: newDate(),
|
||||||
updatedAt: newDate(),
|
updatedAt: newDate(),
|
||||||
@ -85,6 +94,7 @@ const sessionFactory = () => ({
|
|||||||
deviceType: 'mobile',
|
deviceType: 'mobile',
|
||||||
token: 'abc123',
|
token: 'abc123',
|
||||||
userId: newUuid(),
|
userId: newUuid(),
|
||||||
|
...session,
|
||||||
});
|
});
|
||||||
|
|
||||||
const stackFactory = () => ({
|
const stackFactory = () => ({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user