mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
refactor(server)*: tsconfigs (#2689)
* refactor(server): tsconfigs * chore: dummy commit * fix: start.sh * chore: restore original entry scripts
This commit is contained in:
34
server/src/infra/communication.gateway.ts
Normal file
34
server/src/infra/communication.gateway.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { OnGatewayConnection, OnGatewayDisconnect, WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
|
||||
import { Server, Socket } from 'socket.io';
|
||||
import { AuthService } from '@app/domain';
|
||||
|
||||
@WebSocketGateway({ cors: true })
|
||||
export class CommunicationGateway implements OnGatewayConnection, OnGatewayDisconnect {
|
||||
private logger = new Logger(CommunicationGateway.name);
|
||||
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
@WebSocketServer() server!: Server;
|
||||
|
||||
handleDisconnect(client: Socket) {
|
||||
client.leave(client.nsp.name);
|
||||
this.logger.log(`Client ${client.id} disconnected from Websocket`);
|
||||
}
|
||||
|
||||
async handleConnection(client: Socket) {
|
||||
try {
|
||||
this.logger.log(`New websocket connection: ${client.id}`);
|
||||
const user = await this.authService.validate(client.request.headers, {});
|
||||
if (user) {
|
||||
client.join(user.id);
|
||||
} else {
|
||||
client.emit('error', 'unauthorized');
|
||||
client.disconnect();
|
||||
}
|
||||
} catch (e) {
|
||||
client.emit('error', 'unauthorized');
|
||||
client.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
26
server/src/infra/database.config.ts
Normal file
26
server/src/infra/database.config.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
|
||||
|
||||
const url = process.env.DB_URL;
|
||||
const urlOrParts = url
|
||||
? { url }
|
||||
: {
|
||||
host: process.env.DB_HOSTNAME || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
username: process.env.DB_USERNAME || 'postgres',
|
||||
password: process.env.DB_PASSWORD || 'postgres',
|
||||
database: process.env.DB_DATABASE_NAME || 'immich',
|
||||
};
|
||||
|
||||
export const databaseConfig: PostgresConnectionOptions = {
|
||||
type: 'postgres',
|
||||
entities: [__dirname + '/entities/*.entity.{js,ts}'],
|
||||
synchronize: false,
|
||||
migrations: [__dirname + '/migrations/*.{js,ts}'],
|
||||
migrationsRun: true,
|
||||
connectTimeoutMS: 10000, // 10 seconds
|
||||
...urlOrParts,
|
||||
};
|
||||
|
||||
// this export is used by TypeORM commands in package.json#scripts
|
||||
export const dataSource = new DataSource(databaseConfig);
|
||||
52
server/src/infra/entities/album.entity.ts
Normal file
52
server/src/infra/entities/album.entity.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { SharedLinkEntity } from './shared-link.entity';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Entity('albums')
|
||||
export class AlbumEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
|
||||
owner!: UserEntity;
|
||||
|
||||
@Column()
|
||||
ownerId!: string;
|
||||
|
||||
@Column({ default: 'Untitled Album' })
|
||||
albumName!: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
|
||||
@ManyToOne(() => AssetEntity, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' })
|
||||
albumThumbnailAsset!: AssetEntity | null;
|
||||
|
||||
@Column({ comment: 'Asset ID to be used as thumbnail', nullable: true })
|
||||
albumThumbnailAssetId!: string | null;
|
||||
|
||||
@ManyToMany(() => UserEntity)
|
||||
@JoinTable()
|
||||
sharedUsers!: UserEntity[];
|
||||
|
||||
@ManyToMany(() => AssetEntity, (asset) => asset.albums)
|
||||
@JoinTable()
|
||||
assets!: AssetEntity[];
|
||||
|
||||
@OneToMany(() => SharedLinkEntity, (link) => link.album)
|
||||
sharedLinks!: SharedLinkEntity[];
|
||||
}
|
||||
26
server/src/infra/entities/api-key.entity.ts
Normal file
26
server/src/infra/entities/api-key.entity.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Entity('api_keys')
|
||||
export class APIKeyEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column()
|
||||
name!: string;
|
||||
|
||||
@Column({ select: false })
|
||||
key?: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, { onUpdate: 'CASCADE', onDelete: 'CASCADE' })
|
||||
user?: UserEntity;
|
||||
|
||||
@Column()
|
||||
userId!: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
}
|
||||
25
server/src/infra/entities/asset-face.entity.ts
Normal file
25
server/src/infra/entities/asset-face.entity.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
import { PersonEntity } from './person.entity';
|
||||
|
||||
@Entity('asset_faces')
|
||||
export class AssetFaceEntity {
|
||||
@PrimaryColumn()
|
||||
assetId!: string;
|
||||
|
||||
@PrimaryColumn()
|
||||
personId!: string;
|
||||
|
||||
@Column({
|
||||
type: 'float4',
|
||||
array: true,
|
||||
nullable: true,
|
||||
})
|
||||
embedding!: number[] | null;
|
||||
|
||||
@ManyToOne(() => AssetEntity, (asset) => asset.faces, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
asset!: AssetEntity;
|
||||
|
||||
@ManyToOne(() => PersonEntity, (person) => person.faces, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
person!: PersonEntity;
|
||||
}
|
||||
127
server/src/infra/entities/asset.entity.ts
Normal file
127
server/src/infra/entities/asset.entity.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { AlbumEntity } from './album.entity';
|
||||
import { AssetFaceEntity } from './asset-face.entity';
|
||||
import { ExifEntity } from './exif.entity';
|
||||
import { SharedLinkEntity } from './shared-link.entity';
|
||||
import { SmartInfoEntity } from './smart-info.entity';
|
||||
import { TagEntity } from './tag.entity';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Entity('assets')
|
||||
@Unique('UQ_userid_checksum', ['owner', 'checksum'])
|
||||
export class AssetEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column()
|
||||
deviceAssetId!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
|
||||
owner!: UserEntity;
|
||||
|
||||
@Column()
|
||||
ownerId!: string;
|
||||
|
||||
@Column()
|
||||
deviceId!: string;
|
||||
|
||||
@Column()
|
||||
type!: AssetType;
|
||||
|
||||
@Column()
|
||||
originalPath!: string;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
resizePath!: string | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true, default: '' })
|
||||
webpPath!: string | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true, default: '' })
|
||||
encodedVideoPath!: string | null;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
fileCreatedAt!: Date;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
fileModifiedAt!: Date;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isFavorite!: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isArchived!: boolean;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
mimeType!: string | null;
|
||||
|
||||
@Column({ type: 'bytea' })
|
||||
@Index()
|
||||
checksum!: Buffer; // sha1 checksum
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
duration!: string | null;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
isVisible!: boolean;
|
||||
|
||||
@OneToOne(() => AssetEntity, { nullable: true, onUpdate: 'CASCADE', onDelete: 'SET NULL' })
|
||||
@JoinColumn()
|
||||
livePhotoVideo!: AssetEntity | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
livePhotoVideoId!: string | null;
|
||||
|
||||
@Column({ type: 'varchar' })
|
||||
originalFileName!: string;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
sidecarPath!: string | null;
|
||||
|
||||
@OneToOne(() => ExifEntity, (exifEntity) => exifEntity.asset)
|
||||
exifInfo?: ExifEntity;
|
||||
|
||||
@OneToOne(() => SmartInfoEntity, (smartInfoEntity) => smartInfoEntity.asset)
|
||||
smartInfo?: SmartInfoEntity;
|
||||
|
||||
@ManyToMany(() => TagEntity, (tag) => tag.assets, { cascade: true })
|
||||
@JoinTable({ name: 'tag_asset' })
|
||||
tags!: TagEntity[];
|
||||
|
||||
@ManyToMany(() => SharedLinkEntity, (link) => link.assets, { cascade: true })
|
||||
@JoinTable({ name: 'shared_link__asset' })
|
||||
sharedLinks!: SharedLinkEntity[];
|
||||
|
||||
@ManyToMany(() => AlbumEntity, (album) => album.assets, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
albums?: AlbumEntity[];
|
||||
|
||||
@OneToMany(() => AssetFaceEntity, (assetFace) => assetFace.asset)
|
||||
faces!: AssetFaceEntity[];
|
||||
}
|
||||
|
||||
export enum AssetType {
|
||||
IMAGE = 'IMAGE',
|
||||
VIDEO = 'VIDEO',
|
||||
AUDIO = 'AUDIO',
|
||||
OTHER = 'OTHER',
|
||||
}
|
||||
99
server/src/infra/entities/exif.entity.ts
Normal file
99
server/src/infra/entities/exif.entity.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { Index, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
|
||||
import { Column } from 'typeorm/decorator/columns/Column';
|
||||
import { Entity } from 'typeorm/decorator/entity/Entity';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
|
||||
@Entity('exif')
|
||||
export class ExifEntity {
|
||||
@OneToOne(() => AssetEntity, { onDelete: 'CASCADE', nullable: true })
|
||||
@JoinColumn()
|
||||
asset?: AssetEntity;
|
||||
|
||||
@PrimaryColumn()
|
||||
assetId!: string;
|
||||
|
||||
/* General info */
|
||||
@Column({ type: 'text', default: '' })
|
||||
description!: string; // or caption
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
exifImageWidth!: number | null;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
exifImageHeight!: number | null;
|
||||
|
||||
@Column({ type: 'bigint', nullable: true })
|
||||
fileSizeInByte!: number | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
orientation!: string | null;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
dateTimeOriginal!: Date | null;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
modifyDate!: Date | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
timeZone!: string | null;
|
||||
|
||||
@Column({ type: 'float', nullable: true })
|
||||
latitude!: number | null;
|
||||
|
||||
@Column({ type: 'float', nullable: true })
|
||||
longitude!: number | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
city!: string | null;
|
||||
|
||||
@Index('IDX_live_photo_cid')
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
livePhotoCID!: string | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
state!: string | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
country!: string | null;
|
||||
|
||||
/* Image info */
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
make!: string | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
model!: string | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
lensModel!: string | null;
|
||||
|
||||
@Column({ type: 'float8', nullable: true })
|
||||
fNumber!: number | null;
|
||||
|
||||
@Column({ type: 'float8', nullable: true })
|
||||
focalLength!: number | null;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
iso!: number | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
exposureTime!: string | null;
|
||||
|
||||
/* Video info */
|
||||
@Column({ type: 'float8', nullable: true })
|
||||
fps?: number | null;
|
||||
|
||||
@Index('exif_text_searchable', { synchronize: false })
|
||||
@Column({
|
||||
type: 'tsvector',
|
||||
generatedType: 'STORED',
|
||||
asExpression: `TO_TSVECTOR('english',
|
||||
COALESCE(make, '') || ' ' ||
|
||||
COALESCE(model, '') || ' ' ||
|
||||
COALESCE(orientation, '') || ' ' ||
|
||||
COALESCE("lensModel", '') || ' ' ||
|
||||
COALESCE("city", '') || ' ' ||
|
||||
COALESCE("state", '') || ' ' ||
|
||||
COALESCE("country", ''))`,
|
||||
})
|
||||
exifTextSearchableColumn!: string;
|
||||
}
|
||||
41
server/src/infra/entities/index.ts
Normal file
41
server/src/infra/entities/index.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { AlbumEntity } from './album.entity';
|
||||
import { APIKeyEntity } from './api-key.entity';
|
||||
import { AssetFaceEntity } from './asset-face.entity';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
import { PartnerEntity } from './partner.entity';
|
||||
import { PersonEntity } from './person.entity';
|
||||
import { SharedLinkEntity } from './shared-link.entity';
|
||||
import { SmartInfoEntity } from './smart-info.entity';
|
||||
import { SystemConfigEntity } from './system-config.entity';
|
||||
import { TagEntity } from './tag.entity';
|
||||
import { UserTokenEntity } from './user-token.entity';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
export * from './album.entity';
|
||||
export * from './api-key.entity';
|
||||
export * from './asset-face.entity';
|
||||
export * from './asset.entity';
|
||||
export * from './exif.entity';
|
||||
export * from './partner.entity';
|
||||
export * from './person.entity';
|
||||
export * from './shared-link.entity';
|
||||
export * from './smart-info.entity';
|
||||
export * from './system-config.entity';
|
||||
export * from './tag.entity';
|
||||
export * from './user-token.entity';
|
||||
export * from './user.entity';
|
||||
|
||||
export const databaseEntities = [
|
||||
AlbumEntity,
|
||||
APIKeyEntity,
|
||||
AssetEntity,
|
||||
AssetFaceEntity,
|
||||
PartnerEntity,
|
||||
PersonEntity,
|
||||
SharedLinkEntity,
|
||||
SmartInfoEntity,
|
||||
SystemConfigEntity,
|
||||
TagEntity,
|
||||
UserEntity,
|
||||
UserTokenEntity,
|
||||
];
|
||||
26
server/src/infra/entities/partner.entity.ts
Normal file
26
server/src/infra/entities/partner.entity.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { CreateDateColumn, Entity, ManyToOne, PrimaryColumn, JoinColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Entity('partners')
|
||||
export class PartnerEntity {
|
||||
@PrimaryColumn('uuid')
|
||||
sharedById!: string;
|
||||
|
||||
@PrimaryColumn('uuid')
|
||||
sharedWithId!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE', eager: true })
|
||||
@JoinColumn({ name: 'sharedById' })
|
||||
sharedBy!: UserEntity;
|
||||
|
||||
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE', eager: true })
|
||||
@JoinColumn({ name: 'sharedWithId' })
|
||||
sharedWith!: UserEntity;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
}
|
||||
38
server/src/infra/entities/person.entity.ts
Normal file
38
server/src/infra/entities/person.entity.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { AssetFaceEntity } from './asset-face.entity';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Entity('person')
|
||||
export class PersonEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
|
||||
@Column()
|
||||
ownerId!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
|
||||
owner!: UserEntity;
|
||||
|
||||
@Column({ default: '' })
|
||||
name!: string;
|
||||
|
||||
@Column({ default: '' })
|
||||
thumbnailPath!: string;
|
||||
|
||||
@OneToMany(() => AssetFaceEntity, (assetFace) => assetFace.person)
|
||||
faces!: AssetFaceEntity[];
|
||||
}
|
||||
68
server/src/infra/entities/shared-link.entity.ts
Normal file
68
server/src/infra/entities/shared-link.entity.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { AlbumEntity } from './album.entity';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Entity('shared_links')
|
||||
@Unique('UQ_sharedlink_key', ['key'])
|
||||
export class SharedLinkEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column()
|
||||
userId!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity)
|
||||
user!: UserEntity;
|
||||
|
||||
@Index('IDX_sharedlink_key')
|
||||
@Column({ type: 'bytea' })
|
||||
key!: Buffer; // use to access the inidividual asset
|
||||
|
||||
@Column()
|
||||
type!: SharedLinkType;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
expiresAt!: Date | null;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
allowUpload!: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
allowDownload!: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
showExif!: boolean;
|
||||
|
||||
@ManyToMany(() => AssetEntity, (asset) => asset.sharedLinks)
|
||||
assets!: AssetEntity[];
|
||||
|
||||
@Index('IDX_sharedlink_albumId')
|
||||
@ManyToOne(() => AlbumEntity, (album) => album.sharedLinks, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
|
||||
album?: AlbumEntity;
|
||||
}
|
||||
|
||||
export enum SharedLinkType {
|
||||
ALBUM = 'ALBUM',
|
||||
|
||||
/**
|
||||
* Individual asset
|
||||
* or group of assets that are not in an album
|
||||
*/
|
||||
INDIVIDUAL = 'INDIVIDUAL',
|
||||
}
|
||||
25
server/src/infra/entities/smart-info.entity.ts
Normal file
25
server/src/infra/entities/smart-info.entity.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
|
||||
@Entity('smart_info')
|
||||
export class SmartInfoEntity {
|
||||
@OneToOne(() => AssetEntity, { onDelete: 'CASCADE', nullable: true })
|
||||
@JoinColumn({ name: 'assetId', referencedColumnName: 'id' })
|
||||
asset?: AssetEntity;
|
||||
|
||||
@PrimaryColumn()
|
||||
assetId!: string;
|
||||
|
||||
@Column({ type: 'text', array: true, nullable: true })
|
||||
tags!: string[] | null;
|
||||
|
||||
@Column({ type: 'text', array: true, nullable: true })
|
||||
objects!: string[] | null;
|
||||
|
||||
@Column({
|
||||
type: 'float4',
|
||||
array: true,
|
||||
nullable: true,
|
||||
})
|
||||
clipEmbedding!: number[] | null;
|
||||
}
|
||||
92
server/src/infra/entities/system-config.entity.ts
Normal file
92
server/src/infra/entities/system-config.entity.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
import { QueueName } from '@app/domain/job/job.constants';
|
||||
|
||||
@Entity('system_config')
|
||||
export class SystemConfigEntity<T = SystemConfigValue> {
|
||||
@PrimaryColumn()
|
||||
key!: SystemConfigKey;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true, transformer: { to: JSON.stringify, from: JSON.parse } })
|
||||
value!: T;
|
||||
}
|
||||
|
||||
export type SystemConfigValue = string | number | boolean;
|
||||
|
||||
// dot notation matches path in `SystemConfig`
|
||||
export enum SystemConfigKey {
|
||||
FFMPEG_CRF = 'ffmpeg.crf',
|
||||
FFMPEG_THREADS = 'ffmpeg.threads',
|
||||
FFMPEG_PRESET = 'ffmpeg.preset',
|
||||
FFMPEG_TARGET_VIDEO_CODEC = 'ffmpeg.targetVideoCodec',
|
||||
FFMPEG_TARGET_AUDIO_CODEC = 'ffmpeg.targetAudioCodec',
|
||||
FFMPEG_TARGET_RESOLUTION = 'ffmpeg.targetResolution',
|
||||
FFMPEG_MAX_BITRATE = 'ffmpeg.maxBitrate',
|
||||
FFMPEG_TWO_PASS = 'ffmpeg.twoPass',
|
||||
FFMPEG_TRANSCODE = 'ffmpeg.transcode',
|
||||
|
||||
JOB_THUMBNAIL_GENERATION_CONCURRENCY = 'job.thumbnailGeneration.concurrency',
|
||||
JOB_METADATA_EXTRACTION_CONCURRENCY = 'job.metadataExtraction.concurrency',
|
||||
JOB_VIDEO_CONVERSION_CONCURRENCY = 'job.videoConversion.concurrency',
|
||||
JOB_OBJECT_TAGGING_CONCURRENCY = 'job.objectTagging.concurrency',
|
||||
JOB_RECOGNIZE_FACES_CONCURRENCY = 'job.recognizeFaces.concurrency',
|
||||
JOB_CLIP_ENCODING_CONCURRENCY = 'job.clipEncoding.concurrency',
|
||||
JOB_BACKGROUND_TASK_CONCURRENCY = 'job.backgroundTask.concurrency',
|
||||
JOB_STORAGE_TEMPLATE_MIGRATION_CONCURRENCY = 'job.storageTemplateMigration.concurrency',
|
||||
JOB_SEARCH_CONCURRENCY = 'job.search.concurrency',
|
||||
JOB_SIDECAR_CONCURRENCY = 'job.sidecar.concurrency',
|
||||
|
||||
OAUTH_ENABLED = 'oauth.enabled',
|
||||
OAUTH_ISSUER_URL = 'oauth.issuerUrl',
|
||||
OAUTH_CLIENT_ID = 'oauth.clientId',
|
||||
OAUTH_CLIENT_SECRET = 'oauth.clientSecret',
|
||||
OAUTH_SCOPE = 'oauth.scope',
|
||||
OAUTH_AUTO_LAUNCH = 'oauth.autoLaunch',
|
||||
OAUTH_BUTTON_TEXT = 'oauth.buttonText',
|
||||
OAUTH_AUTO_REGISTER = 'oauth.autoRegister',
|
||||
OAUTH_MOBILE_OVERRIDE_ENABLED = 'oauth.mobileOverrideEnabled',
|
||||
OAUTH_MOBILE_REDIRECT_URI = 'oauth.mobileRedirectUri',
|
||||
|
||||
PASSWORD_LOGIN_ENABLED = 'passwordLogin.enabled',
|
||||
|
||||
STORAGE_TEMPLATE = 'storageTemplate.template',
|
||||
}
|
||||
|
||||
export enum TranscodePreset {
|
||||
ALL = 'all',
|
||||
OPTIMAL = 'optimal',
|
||||
REQUIRED = 'required',
|
||||
DISABLED = 'disabled',
|
||||
}
|
||||
|
||||
export interface SystemConfig {
|
||||
ffmpeg: {
|
||||
crf: number;
|
||||
threads: number;
|
||||
preset: string;
|
||||
targetVideoCodec: string;
|
||||
targetAudioCodec: string;
|
||||
targetResolution: string;
|
||||
maxBitrate: string;
|
||||
twoPass: boolean;
|
||||
transcode: TranscodePreset;
|
||||
};
|
||||
job: Record<QueueName, { concurrency: number }>;
|
||||
oauth: {
|
||||
enabled: boolean;
|
||||
issuerUrl: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
scope: string;
|
||||
buttonText: string;
|
||||
autoRegister: boolean;
|
||||
autoLaunch: boolean;
|
||||
mobileOverrideEnabled: boolean;
|
||||
mobileRedirectUri: string;
|
||||
};
|
||||
passwordLogin: {
|
||||
enabled: boolean;
|
||||
};
|
||||
storageTemplate: {
|
||||
template: string;
|
||||
};
|
||||
}
|
||||
45
server/src/infra/entities/tag.entity.ts
Normal file
45
server/src/infra/entities/tag.entity.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Column, Entity, ManyToMany, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Entity('tags')
|
||||
@Unique('UQ_tag_name_userId', ['name', 'userId'])
|
||||
export class TagEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column()
|
||||
type!: TagType;
|
||||
|
||||
@Column()
|
||||
name!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, (user) => user.tags)
|
||||
user!: UserEntity;
|
||||
|
||||
@Column()
|
||||
userId!: string;
|
||||
|
||||
@Column({ type: 'uuid', comment: 'The new renamed tagId', nullable: true })
|
||||
renameTagId!: string | null;
|
||||
|
||||
@ManyToMany(() => AssetEntity, (asset) => asset.tags)
|
||||
assets!: AssetEntity[];
|
||||
}
|
||||
|
||||
export enum TagType {
|
||||
/**
|
||||
* Tag that is detected by the ML model for object detection will use this type
|
||||
*/
|
||||
OBJECT = 'OBJECT',
|
||||
|
||||
/**
|
||||
* Face that is detected by the ML model for facial detection (TBD/NOT YET IMPLEMENTED) will use this type
|
||||
*/
|
||||
FACE = 'FACE',
|
||||
|
||||
/**
|
||||
* Tag that is created by the user will use this type
|
||||
*/
|
||||
CUSTOM = 'CUSTOM',
|
||||
}
|
||||
29
server/src/infra/entities/user-token.entity.ts
Normal file
29
server/src/infra/entities/user-token.entity.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Entity('user_token')
|
||||
export class UserTokenEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ select: false })
|
||||
token!: string;
|
||||
|
||||
@Column()
|
||||
userId!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, { onUpdate: 'CASCADE', onDelete: 'CASCADE' })
|
||||
user!: UserEntity;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
|
||||
@Column({ default: '' })
|
||||
deviceType!: string;
|
||||
|
||||
@Column({ default: '' })
|
||||
deviceOS!: string;
|
||||
}
|
||||
59
server/src/infra/entities/user.entity.ts
Normal file
59
server/src/infra/entities/user.entity.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { AssetEntity } from './asset.entity';
|
||||
import { TagEntity } from './tag.entity';
|
||||
|
||||
@Entity('users')
|
||||
export class UserEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ default: '' })
|
||||
firstName!: string;
|
||||
|
||||
@Column({ default: '' })
|
||||
lastName!: string;
|
||||
|
||||
@Column({ default: false })
|
||||
isAdmin!: boolean;
|
||||
|
||||
@Column({ unique: true })
|
||||
email!: string;
|
||||
|
||||
@Column({ type: 'varchar', unique: true, default: null })
|
||||
storageLabel!: string | null;
|
||||
|
||||
@Column({ default: '', select: false })
|
||||
password?: string;
|
||||
|
||||
@Column({ default: '' })
|
||||
oauthId!: string;
|
||||
|
||||
@Column({ default: '' })
|
||||
profileImagePath!: string;
|
||||
|
||||
@Column({ default: true })
|
||||
shouldChangePassword!: boolean;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@DeleteDateColumn({ type: 'timestamptz' })
|
||||
deletedAt!: Date | null;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
|
||||
@OneToMany(() => TagEntity, (tag) => tag.user)
|
||||
tags!: TagEntity[];
|
||||
|
||||
@OneToMany(() => AssetEntity, (asset) => asset.owner)
|
||||
assets!: AssetEntity[];
|
||||
}
|
||||
4
server/src/infra/index.ts
Normal file
4
server/src/infra/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './database.config';
|
||||
export * from './infra.config';
|
||||
export * from './infra.module';
|
||||
export * from './redis-io.adapter';
|
||||
91
server/src/infra/infra.config.ts
Normal file
91
server/src/infra/infra.config.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { QueueName } from '@app/domain';
|
||||
import { RegisterQueueOptions } from '@nestjs/bullmq';
|
||||
import { QueueOptions } from 'bullmq';
|
||||
import { RedisOptions } from 'ioredis';
|
||||
import { InitOptions } from 'local-reverse-geocoder';
|
||||
import { ConfigurationOptions } from 'typesense/lib/Typesense/Configuration';
|
||||
|
||||
function parseRedisConfig(): RedisOptions {
|
||||
const redisUrl = process.env.REDIS_URL;
|
||||
if (redisUrl && redisUrl.startsWith('ioredis://')) {
|
||||
try {
|
||||
const decodedString = Buffer.from(redisUrl.slice(10), 'base64').toString();
|
||||
return JSON.parse(decodedString);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to decode redis options: ${error}`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
host: process.env.REDIS_HOSTNAME || 'immich_redis',
|
||||
port: parseInt(process.env.REDIS_PORT || '6379'),
|
||||
db: parseInt(process.env.REDIS_DBINDEX || '0'),
|
||||
username: process.env.REDIS_USERNAME || undefined,
|
||||
password: process.env.REDIS_PASSWORD || undefined,
|
||||
path: process.env.REDIS_SOCKET || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const redisConfig: RedisOptions = parseRedisConfig();
|
||||
|
||||
export const bullConfig: QueueOptions = {
|
||||
prefix: 'immich_bull',
|
||||
connection: redisConfig,
|
||||
defaultJobOptions: {
|
||||
attempts: 3,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const bullQueues: RegisterQueueOptions[] = Object.values(QueueName).map((name) => ({ name }));
|
||||
|
||||
function parseTypeSenseConfig(): ConfigurationOptions {
|
||||
const typesenseURL = process.env.TYPESENSE_URL;
|
||||
const common = {
|
||||
apiKey: process.env.TYPESENSE_API_KEY as string,
|
||||
numRetries: 15,
|
||||
retryIntervalSeconds: 4,
|
||||
connectionTimeoutSeconds: 10,
|
||||
};
|
||||
if (typesenseURL && typesenseURL.startsWith('ha://')) {
|
||||
try {
|
||||
const decodedString = Buffer.from(typesenseURL.slice(5), 'base64').toString();
|
||||
return {
|
||||
nodes: JSON.parse(decodedString),
|
||||
...common,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to decode typesense options: ${error}`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
nodes: [
|
||||
{
|
||||
host: process.env.TYPESENSE_HOST || 'typesense',
|
||||
port: Number(process.env.TYPESENSE_PORT) || 8108,
|
||||
protocol: process.env.TYPESENSE_PROTOCOL || 'http',
|
||||
},
|
||||
],
|
||||
...common,
|
||||
};
|
||||
}
|
||||
|
||||
export const typesenseConfig: ConfigurationOptions = parseTypeSenseConfig();
|
||||
|
||||
function parseLocalGeocodingConfig(): InitOptions {
|
||||
const precision = Number(process.env.REVERSE_GEOCODING_PRECISION);
|
||||
|
||||
return {
|
||||
citiesFileOverride: precision ? ['cities15000', 'cities5000', 'cities1000', 'cities500'][precision] : undefined,
|
||||
load: {
|
||||
admin1: true,
|
||||
admin2: true,
|
||||
admin3And4: false,
|
||||
alternateNames: false,
|
||||
},
|
||||
countries: [],
|
||||
dumpDirectory: process.env.REVERSE_GEOCODING_DUMP_DIRECTORY || process.cwd() + '/.reverse-geocoding-dump/',
|
||||
};
|
||||
}
|
||||
|
||||
export const localGeocodingConfig: InitOptions = parseLocalGeocodingConfig();
|
||||
93
server/src/infra/infra.module.ts
Normal file
93
server/src/infra/infra.module.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
IAccessRepository,
|
||||
IAlbumRepository,
|
||||
IAssetRepository,
|
||||
ICommunicationRepository,
|
||||
ICryptoRepository,
|
||||
IFaceRepository,
|
||||
IGeocodingRepository,
|
||||
IJobRepository,
|
||||
IKeyRepository,
|
||||
IMachineLearningRepository,
|
||||
IMediaRepository,
|
||||
immichAppConfig,
|
||||
IPartnerRepository,
|
||||
IPersonRepository,
|
||||
ISearchRepository,
|
||||
ISharedLinkRepository,
|
||||
ISmartInfoRepository,
|
||||
IStorageRepository,
|
||||
ISystemConfigRepository,
|
||||
ITagRepository,
|
||||
IUserRepository,
|
||||
IUserTokenRepository,
|
||||
} from '@app/domain';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { Global, Module, Provider } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CommunicationGateway } from './communication.gateway';
|
||||
import { databaseConfig } from './database.config';
|
||||
import { databaseEntities } from './entities';
|
||||
import { bullConfig, bullQueues } from './infra.config';
|
||||
import {
|
||||
AccessRepository,
|
||||
AlbumRepository,
|
||||
APIKeyRepository,
|
||||
AssetRepository,
|
||||
CommunicationRepository,
|
||||
CryptoRepository,
|
||||
FaceRepository,
|
||||
FilesystemProvider,
|
||||
GeocodingRepository,
|
||||
JobRepository,
|
||||
MachineLearningRepository,
|
||||
MediaRepository,
|
||||
PartnerRepository,
|
||||
PersonRepository,
|
||||
SharedLinkRepository,
|
||||
SmartInfoRepository,
|
||||
SystemConfigRepository,
|
||||
TagRepository,
|
||||
TypesenseRepository,
|
||||
UserRepository,
|
||||
UserTokenRepository,
|
||||
} from './repositories';
|
||||
|
||||
const providers: Provider[] = [
|
||||
{ provide: IAccessRepository, useClass: AccessRepository },
|
||||
{ provide: IAlbumRepository, useClass: AlbumRepository },
|
||||
{ provide: IAssetRepository, useClass: AssetRepository },
|
||||
{ provide: ICommunicationRepository, useClass: CommunicationRepository },
|
||||
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
||||
{ provide: IFaceRepository, useClass: FaceRepository },
|
||||
{ provide: IGeocodingRepository, useClass: GeocodingRepository },
|
||||
{ provide: IJobRepository, useClass: JobRepository },
|
||||
{ provide: IKeyRepository, useClass: APIKeyRepository },
|
||||
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
|
||||
{ provide: IMediaRepository, useClass: MediaRepository },
|
||||
{ provide: IPartnerRepository, useClass: PartnerRepository },
|
||||
{ provide: IPersonRepository, useClass: PersonRepository },
|
||||
{ provide: ISearchRepository, useClass: TypesenseRepository },
|
||||
{ provide: ISharedLinkRepository, useClass: SharedLinkRepository },
|
||||
{ provide: ISmartInfoRepository, useClass: SmartInfoRepository },
|
||||
{ provide: IStorageRepository, useClass: FilesystemProvider },
|
||||
{ provide: ISystemConfigRepository, useClass: SystemConfigRepository },
|
||||
{ provide: ITagRepository, useClass: TagRepository },
|
||||
{ provide: IUserRepository, useClass: UserRepository },
|
||||
{ provide: IUserTokenRepository, useClass: UserTokenRepository },
|
||||
];
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot(immichAppConfig),
|
||||
TypeOrmModule.forRoot(databaseConfig),
|
||||
TypeOrmModule.forFeature(databaseEntities),
|
||||
BullModule.forRoot(bullConfig),
|
||||
BullModule.registerQueue(...bullQueues),
|
||||
],
|
||||
providers: [...providers, CommunicationGateway],
|
||||
exports: [...providers, BullModule],
|
||||
})
|
||||
export class InfraModule {}
|
||||
22
server/src/infra/migrations/1645130759468-CreateUserTable.ts
Normal file
22
server/src/infra/migrations/1645130759468-CreateUserTable.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateUserTable1645130759468 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
create table if not exists users
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null
|
||||
constraint "PK_a3ffb1c0c8416b9fc6f907b7433"
|
||||
primary key,
|
||||
email varchar not null,
|
||||
password varchar not null,
|
||||
salt varchar not null,
|
||||
"createdAt" timestamp default now() not null
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`drop table users`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateDeviceInfoTable1645130777674 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
create table if not exists device_info
|
||||
(
|
||||
id serial
|
||||
constraint "PK_b1c15a80b0a4e5f4eebadbdd92c"
|
||||
primary key,
|
||||
"userId" varchar not null,
|
||||
"deviceId" varchar not null,
|
||||
"deviceType" varchar not null,
|
||||
"notificationToken" varchar,
|
||||
"createdAt" timestamp default now() not null,
|
||||
"isAutoBackup" boolean default false not null,
|
||||
constraint "UQ_ebad78f36b10d15fbea8560e107"
|
||||
unique ("userId", "deviceId")
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`drop table device_info`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateAssetsTable1645130805273 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
create table if not exists assets
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null
|
||||
constraint "PK_da96729a8b113377cfb6a62439c"
|
||||
primary key,
|
||||
"deviceAssetId" varchar not null,
|
||||
"userId" varchar not null,
|
||||
"deviceId" varchar not null,
|
||||
type varchar not null,
|
||||
"originalPath" varchar not null,
|
||||
"resizePath" varchar,
|
||||
"createdAt" varchar not null,
|
||||
"modifiedAt" varchar not null,
|
||||
"isFavorite" boolean default false not null,
|
||||
"mimeType" varchar,
|
||||
duration varchar,
|
||||
constraint "UQ_b599ab0bd9574958acb0b30a90e"
|
||||
unique ("deviceAssetId", "userId", "deviceId")
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`drop table assets`);
|
||||
}
|
||||
}
|
||||
42
server/src/infra/migrations/1645130817965-CreateExifTable.ts
Normal file
42
server/src/infra/migrations/1645130817965-CreateExifTable.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateExifTable1645130817965 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
create table if not exists exif
|
||||
(
|
||||
id serial
|
||||
constraint "PK_28663352d85078ad0046dafafaa"
|
||||
primary key,
|
||||
"assetId" uuid not null
|
||||
constraint "REL_c0117fdbc50b917ef9067740c4"
|
||||
unique
|
||||
constraint "FK_c0117fdbc50b917ef9067740c44"
|
||||
references assets
|
||||
on delete cascade,
|
||||
make varchar,
|
||||
model varchar,
|
||||
"imageName" varchar,
|
||||
"exifImageWidth" integer,
|
||||
"exifImageHeight" integer,
|
||||
"fileSizeInByte" integer,
|
||||
orientation varchar,
|
||||
"dateTimeOriginal" timestamp with time zone,
|
||||
"modifyDate" timestamp with time zone,
|
||||
"lensModel" varchar,
|
||||
"fNumber" double precision,
|
||||
"focalLength" double precision,
|
||||
iso integer,
|
||||
"exposureTime" double precision,
|
||||
latitude double precision,
|
||||
longitude double precision
|
||||
);
|
||||
|
||||
create unique index if not exists "IDX_c0117fdbc50b917ef9067740c4" on exif ("assetId");
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`drop table exif`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateSmartInfoTable1645130870184 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
create table if not exists smart_info
|
||||
(
|
||||
id serial
|
||||
constraint "PK_0beace66440e9713f5c40470e46"
|
||||
primary key,
|
||||
"assetId" uuid not null
|
||||
constraint "UQ_5e3753aadd956110bf3ec0244ac"
|
||||
unique
|
||||
constraint "FK_5e3753aadd956110bf3ec0244ac"
|
||||
references assets
|
||||
on delete cascade,
|
||||
tags text[]
|
||||
);
|
||||
|
||||
create unique index if not exists "IDX_5e3753aadd956110bf3ec0244a"
|
||||
on smart_info ("assetId");
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
drop table smart_info;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddExifTextSearchColumn1646249209023 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE exif
|
||||
ADD COLUMN IF NOT EXISTS exif_text_searchable_column tsvector
|
||||
GENERATED ALWAYS AS (
|
||||
TO_TSVECTOR('english',
|
||||
COALESCE(make, '') || ' ' ||
|
||||
COALESCE(model, '') || ' ' ||
|
||||
COALESCE(orientation, '') || ' ' ||
|
||||
COALESCE("lensModel", '')
|
||||
)
|
||||
) STORED;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE exif
|
||||
DROP COLUMN IF EXISTS exif_text_searchable_column;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateExifTextSearchIndex1646249734844 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX exif_text_searchable_idx
|
||||
ON exif
|
||||
USING GIN (exif_text_searchable_column);
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
DROP INDEX IF EXISTS exif_text_searchable_idx ON exif;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddRegionCityToExIf1646709533213 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE exif
|
||||
ADD COLUMN if not exists city varchar;
|
||||
|
||||
ALTER TABLE exif
|
||||
ADD COLUMN if not exists state varchar;
|
||||
|
||||
ALTER TABLE exif
|
||||
ADD COLUMN if not exists country varchar;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE exif
|
||||
DROP COLUMN city;
|
||||
|
||||
ALTER TABLE exif
|
||||
DROP COLUMN state;
|
||||
|
||||
ALTER TABLE exif
|
||||
DROP COLUMN country;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddLocationToExifTextSearch1646710459852 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE exif
|
||||
DROP COLUMN IF EXISTS exif_text_searchable_column;
|
||||
|
||||
ALTER TABLE exif
|
||||
ADD COLUMN IF NOT EXISTS exif_text_searchable_column tsvector
|
||||
GENERATED ALWAYS AS (
|
||||
TO_TSVECTOR('english',
|
||||
COALESCE(make, '') || ' ' ||
|
||||
COALESCE(model, '') || ' ' ||
|
||||
COALESCE(orientation, '') || ' ' ||
|
||||
COALESCE("lensModel", '') || ' ' ||
|
||||
COALESCE("city", '') || ' ' ||
|
||||
COALESCE("state", '') || ' ' ||
|
||||
COALESCE("country", '')
|
||||
)
|
||||
) STORED;
|
||||
|
||||
CREATE INDEX exif_text_searchable_idx
|
||||
ON exif
|
||||
USING GIN (exif_text_searchable_column);
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE exif
|
||||
DROP COLUMN IF EXISTS exif_text_searchable_column;
|
||||
|
||||
DROP INDEX IF EXISTS exif_text_searchable_idx ON exif;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddObjectColumnToSmartInfo1648317474768 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE smart_info
|
||||
ADD COLUMN if not exists objects text[];
|
||||
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE smart_info
|
||||
DROP COLUMN objects;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateSharedAlbumAndRelatedTables1649643216111 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Create shared_albums
|
||||
await queryRunner.query(`
|
||||
create table if not exists shared_albums
|
||||
(
|
||||
id uuid default uuid_generate_v4() not null
|
||||
constraint "PK_7f71c7b5bc7c87b8f94c9a93a00"
|
||||
primary key,
|
||||
"ownerId" varchar not null,
|
||||
"albumName" varchar default 'Untitled Album'::character varying not null,
|
||||
"createdAt" timestamp with time zone default now() not null,
|
||||
"albumThumbnailAssetId" varchar
|
||||
);
|
||||
|
||||
comment on column shared_albums."albumThumbnailAssetId" is 'Asset ID to be used as thumbnail';
|
||||
`);
|
||||
|
||||
// Create user_shared_album
|
||||
await queryRunner.query(`
|
||||
create table if not exists user_shared_album
|
||||
(
|
||||
id serial
|
||||
constraint "PK_b6562316a98845a7b3e9a25cdd0"
|
||||
primary key,
|
||||
"albumId" uuid not null
|
||||
constraint "FK_7b3bf0f5f8da59af30519c25f18"
|
||||
references shared_albums
|
||||
on delete cascade,
|
||||
"sharedUserId" uuid not null
|
||||
constraint "FK_543c31211653e63e080ba882eb5"
|
||||
references users,
|
||||
constraint "PK_unique_user_in_album"
|
||||
unique ("albumId", "sharedUserId")
|
||||
);
|
||||
`);
|
||||
|
||||
// Create asset_shared_album
|
||||
await queryRunner.query(
|
||||
`
|
||||
create table if not exists asset_shared_album
|
||||
(
|
||||
id serial
|
||||
constraint "PK_a34e076afbc601d81938e2c2277"
|
||||
primary key,
|
||||
"albumId" uuid not null
|
||||
constraint "FK_a8b79a84996cef6ba6a3662825d"
|
||||
references shared_albums
|
||||
on delete cascade,
|
||||
"assetId" uuid not null
|
||||
constraint "FK_64f2e7d68d1d1d8417acc844a4a"
|
||||
references assets
|
||||
on delete cascade,
|
||||
constraint "UQ_a1e2734a1ce361e7a26f6b28288"
|
||||
unique ("albumId", "assetId")
|
||||
);
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
drop table asset_shared_album;
|
||||
drop table user_shared_album;
|
||||
drop table shared_albums;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class UpdateUserTableWithAdminAndName1652633525943 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
alter table users
|
||||
add column if not exists "firstName" varchar default '';
|
||||
|
||||
alter table users
|
||||
add column if not exists "lastName" varchar default '';
|
||||
|
||||
alter table users
|
||||
add column if not exists "profileImagePath" varchar default '';
|
||||
|
||||
alter table users
|
||||
add column if not exists "isAdmin" bool default false;
|
||||
|
||||
alter table users
|
||||
add column if not exists "isFirstLoggedIn" bool default true;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
alter table users
|
||||
drop column "firstName";
|
||||
|
||||
alter table users
|
||||
drop column "lastName";
|
||||
|
||||
alter table users
|
||||
drop column "isAdmin";
|
||||
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class UpdateAssetTableWithWebpPath1653214255670 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
alter table assets
|
||||
add column if not exists "webpPath" varchar default '';
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
alter table assets
|
||||
drop column if exists "webpPath";
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class UpdateAssetTableWithEncodeVideoPath1654299904583 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
alter table assets
|
||||
add column if not exists "encodedVideoPath" varchar default '';
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
alter table assets
|
||||
drop column if exists "encodedVideoPath";
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RenameSharedAlbums1655401127251 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE shared_albums RENAME TO albums;
|
||||
|
||||
ALTER TABLE asset_shared_album RENAME TO asset_album;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE asset_album RENAME TO asset_shared_album;
|
||||
|
||||
ALTER TABLE albums RENAME TO shared_albums;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RenameIsFirstLoggedInColumn1656338626260 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE users
|
||||
RENAME COLUMN "isFirstLoggedIn" to "shouldChangePassword";
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE users
|
||||
RENAME COLUMN "shouldChangePassword" to "isFirstLoggedIn";
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RenameAssetAlbumIdSequence1656888591977 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`alter sequence asset_shared_album_id_seq rename to asset_album_id_seq;`);
|
||||
await queryRunner.query(
|
||||
`alter table asset_album alter column id set default nextval('asset_album_id_seq'::regclass);`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`alter sequence asset_album_id_seq rename to asset_shared_album_id_seq;`);
|
||||
await queryRunner.query(
|
||||
`alter table asset_album alter column id set default nextval('asset_shared_album_id_seq'::regclass);`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class DropExifTextSearchableColumns1656888918620 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "exif_text_searchable_column"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE exif
|
||||
DROP COLUMN IF EXISTS exif_text_searchable_column;
|
||||
|
||||
ALTER TABLE exif
|
||||
ADD COLUMN IF NOT EXISTS exif_text_searchable_column tsvector
|
||||
GENERATED ALWAYS AS (
|
||||
TO_TSVECTOR('english',
|
||||
COALESCE(make, '') || ' ' ||
|
||||
COALESCE(model, '') || ' ' ||
|
||||
COALESCE(orientation, '') || ' ' ||
|
||||
COALESCE("lensModel", '') || ' ' ||
|
||||
COALESCE("city", '') || ' ' ||
|
||||
COALESCE("state", '') || ' ' ||
|
||||
COALESCE("country", '')
|
||||
)
|
||||
) STORED;
|
||||
|
||||
CREATE INDEX exif_text_searchable_idx
|
||||
ON exif
|
||||
USING GIN (exif_text_searchable_column);
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class MatchMigrationsWithTypeORMEntities1656889061566 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "exifTextSearchableColumn" tsvector GENERATED ALWAYS AS (TO_TSVECTOR('english',
|
||||
COALESCE(make, '') || ' ' ||
|
||||
COALESCE(model, '') || ' ' ||
|
||||
COALESCE(orientation, '') || ' ' ||
|
||||
COALESCE("lensModel", '') || ' ' ||
|
||||
COALESCE("city", '') || ' ' ||
|
||||
COALESCE("state", '') || ' ' ||
|
||||
COALESCE("country", ''))) STORED`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "exifTextSearchableColumn" SET NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`,
|
||||
['GENERATED_COLUMN', 'exifTextSearchableColumn', 'postgres', 'public', 'exif'],
|
||||
);
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[
|
||||
'postgres',
|
||||
'public',
|
||||
'exif',
|
||||
'GENERATED_COLUMN',
|
||||
'exifTextSearchableColumn',
|
||||
"TO_TSVECTOR('english',\n COALESCE(make, '') || ' ' ||\n COALESCE(model, '') || ' ' ||\n COALESCE(orientation, '') || ' ' ||\n COALESCE(\"lensModel\", '') || ' ' ||\n COALESCE(\"city\", '') || ' ' ||\n COALESCE(\"state\", '') || ' ' ||\n COALESCE(\"country\", ''))",
|
||||
],
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "firstName" SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "lastName" SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "isAdmin" SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "profileImagePath" SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "shouldChangePassword" SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_a8b79a84996cef6ba6a3662825d"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_64f2e7d68d1d1d8417acc844a4a"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "UQ_a1e2734a1ce361e7a26f6b28288"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "asset_album" ADD CONSTRAINT "UQ_unique_asset_in_album" UNIQUE ("albumId", "assetId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "asset_album" ADD CONSTRAINT "FK_256a30a03a4a0aff0394051397d" FOREIGN KEY ("albumId") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "asset_album" ADD CONSTRAINT "FK_7ae4e03729895bf87e056d7b598" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "shouldChangePassword" DROP NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "profileImagePath" DROP NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "isAdmin" DROP NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "lastName" DROP NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "firstName" DROP NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`,
|
||||
['GENERATED_COLUMN', 'exifTextSearchableColumn', 'immich', 'public', 'exif'],
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "exifTextSearchableColumn"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_7ae4e03729895bf87e056d7b598"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_256a30a03a4a0aff0394051397d"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "UQ_unique_asset_in_album"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "asset_album" ADD CONSTRAINT "UQ_a1e2734a1ce361e7a26f6b28288" UNIQUE ("albumId", "assetId")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "asset_album" ADD CONSTRAINT "FK_64f2e7d68d1d1d8417acc844a4a" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "asset_album" ADD CONSTRAINT "FK_a8b79a84996cef6ba6a3662825d" FOREIGN KEY ("albumId") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddExifImageNameAsSearchableText1658860470248 implements MigrationInterface {
|
||||
name = 'AddExifImageNameAsSearchableText1658860470248';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "exifTextSearchableColumn"`);
|
||||
await queryRunner.query(
|
||||
`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`,
|
||||
['GENERATED_COLUMN', 'exifTextSearchableColumn', 'immich', 'public', 'exif'],
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "exifTextSearchableColumn" tsvector GENERATED ALWAYS AS (TO_TSVECTOR('english',
|
||||
COALESCE(make, '') || ' ' ||
|
||||
COALESCE(model, '') || ' ' ||
|
||||
COALESCE(orientation, '') || ' ' ||
|
||||
COALESCE("lensModel", '') || ' ' ||
|
||||
COALESCE("imageName", '') || ' ' ||
|
||||
COALESCE("city", '') || ' ' ||
|
||||
COALESCE("state", '') || ' ' ||
|
||||
COALESCE("country", ''))) STORED`);
|
||||
await queryRunner.query(
|
||||
`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`,
|
||||
['GENERATED_COLUMN', 'exifTextSearchableColumn', 'immich', 'public', 'exif'],
|
||||
);
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[
|
||||
'immich',
|
||||
'public',
|
||||
'exif',
|
||||
'GENERATED_COLUMN',
|
||||
'exifTextSearchableColumn',
|
||||
"TO_TSVECTOR('english',\n COALESCE(make, '') || ' ' ||\n COALESCE(model, '') || ' ' ||\n COALESCE(orientation, '') || ' ' ||\n COALESCE(\"lensModel\", '') || ' ' ||\n COALESCE(\"imageName\", '') || ' ' ||\n COALESCE(\"city\", '') || ' ' ||\n COALESCE(\"state\", '') || ' ' ||\n COALESCE(\"country\", ''))",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`,
|
||||
['GENERATED_COLUMN', 'exifTextSearchableColumn', 'immich', 'public', 'exif'],
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "exifTextSearchableColumn"`);
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
['immich', 'public', 'exif', 'GENERATED_COLUMN', 'exifTextSearchableColumn', ''],
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "exifTextSearchableColumn" tsvector NOT NULL`);
|
||||
}
|
||||
}
|
||||
15
server/src/infra/migrations/1661011331242-AddCaption.ts
Normal file
15
server/src/infra/migrations/1661011331242-AddCaption.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddCaption1661011331242 implements MigrationInterface {
|
||||
name = 'AddCaption1661011331242';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "description" text DEFAULT ''`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "fps" double precision`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "fps"`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "description"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class ChangeExifFileSizeInByteToBigInt1661528919411 implements MigrationInterface {
|
||||
name = 'ChangeExifFileSizeInByteToBigInt1661528919411';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE exif
|
||||
ALTER COLUMN "fileSizeInByte" type bigint using "fileSizeInByte"::bigint;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE exif
|
||||
ALTER COLUMN "fileSizeInByte" type integer using "fileSizeInByte"::integer;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddAssetChecksum1661881837496 implements MigrationInterface {
|
||||
name = 'AddAssetChecksum1661881837496';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "checksum" bytea`);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_64c507300988dd1764f9a6530c" ON "assets" ("checksum") WHERE 'checksum' IS NOT NULL`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_64c507300988dd1764f9a6530c"`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "checksum"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class UpdateAssetTableWithNewUniqueConstraint1661971370662 implements MigrationInterface {
|
||||
name = 'UpdateAssetTableWithNewUniqueConstraint1661971370662';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "UQ_b599ab0bd9574958acb0b30a90e"`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD CONSTRAINT "UQ_userid_checksum" UNIQUE ("userId", "checksum")`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "UQ_userid_checksum"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "assets" ADD CONSTRAINT "UQ_b599ab0bd9574958acb0b30a90e" UNIQUE ("deviceAssetId", "userId", "deviceId")`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class FixTimestampDataTypeInAssetTable1662427365521 implements MigrationInterface {
|
||||
name = 'FixTimestampDataTypeInAssetTable1662427365521';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "exifTextSearchableColumn" SET NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "assets" ALTER COLUMN "createdAt" TYPE timestamptz USING "createdAt"::timestamptz`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "assets" ALTER COLUMN "modifiedAt" TYPE timestamptz USING "createdAt"::timestamptz`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "createdAt" TYPE varchar USING "createdAt"::varchar`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "modifiedAt" TYPE varchar USING "createdAt"::varchar`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "exifTextSearchableColumn" DROP NOT NULL`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class CreateSystemConfigTable1665540663419 implements MigrationInterface {
|
||||
name = 'CreateSystemConfigTable1665540663419';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "system_config" ("key" character varying NOT NULL, "value" character varying, CONSTRAINT "PK_aab69295b445016f56731f4d535" PRIMARY KEY ("key"))`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "system_config"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddingDeletedAtColumnInUserEntity1667762360744 implements MigrationInterface {
|
||||
name = 'AddingDeletedAtColumnInUserEntity1667762360744';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "deletedAt" TIMESTAMP`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "deletedAt"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddLivePhotosRelatedColumnToAssetTable1668383120461 implements MigrationInterface {
|
||||
name = 'AddLivePhotosRelatedColumnToAssetTable1668383120461'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "isVisible" boolean NOT NULL DEFAULT true`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "livePhotoVideoId" uuid`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "livePhotoVideoId"`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "isVisible"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class UpdateUserTableForOIDC1668835311083 implements MigrationInterface {
|
||||
name = 'UpdateUserTableForOIDC1668835311083'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "password" SET DEFAULT ''`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "salt" SET DEFAULT ''`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "salt" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "password" DROP DEFAULT`);
|
||||
}
|
||||
|
||||
}
|
||||
14
server/src/infra/migrations/1670104716264-OAuthId.ts
Normal file
14
server/src/infra/migrations/1670104716264-OAuthId.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class OAuthId1670104716264 implements MigrationInterface {
|
||||
name = 'OAuthId1670104716264'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "oauthId" character varying NOT NULL DEFAULT ''`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "oauthId"`);
|
||||
}
|
||||
|
||||
}
|
||||
26
server/src/infra/migrations/1670257571385-CreateTagsTable.ts
Normal file
26
server/src/infra/migrations/1670257571385-CreateTagsTable.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class CreateTagsTable1670257571385 implements MigrationInterface {
|
||||
name = 'CreateTagsTable1670257571385'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "tags" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "type" character varying NOT NULL, "name" character varying NOT NULL, "userId" uuid NOT NULL, "renameTagId" uuid, CONSTRAINT "UQ_tag_name_userId" UNIQUE ("name", "userId"), CONSTRAINT "PK_e7dc17249a1148a1970748eda99" PRIMARY KEY ("id")); COMMENT ON COLUMN "tags"."renameTagId" IS 'The new renamed tagId'`);
|
||||
await queryRunner.query(`CREATE TABLE "tag_asset" ("assetsId" uuid NOT NULL, "tagsId" uuid NOT NULL, CONSTRAINT "PK_ef5346fe522b5fb3bc96454747e" PRIMARY KEY ("assetsId", "tagsId"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f8e8a9e893cb5c54907f1b798e" ON "tag_asset" ("assetsId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_e99f31ea4cdf3a2c35c7287eb4" ON "tag_asset" ("tagsId") `);
|
||||
await queryRunner.query(`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9" FOREIGN KEY ("assetsId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
await queryRunner.query(`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42" FOREIGN KEY ("tagsId") REFERENCES "tags"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42"`);
|
||||
await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9"`);
|
||||
await queryRunner.query(`ALTER TABLE "tags" DROP CONSTRAINT "FK_92e67dc508c705dd66c94615576"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_e99f31ea4cdf3a2c35c7287eb4"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_f8e8a9e893cb5c54907f1b798e"`);
|
||||
await queryRunner.query(`DROP TABLE "tag_asset"`);
|
||||
await queryRunner.query(`DROP TABLE "tags"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class TruncateOldConfigItems1670607437008 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`TRUNCATE TABLE "system_config"`);
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddUserEmailUniqueConstraint1670633210032 implements MigrationInterface {
|
||||
name = 'AddUserEmailUniqueConstraint1670633210032'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email")`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3"`);
|
||||
}
|
||||
|
||||
}
|
||||
14
server/src/infra/migrations/1672109862870-DropSaltColumn.ts
Normal file
14
server/src/infra/migrations/1672109862870-DropSaltColumn.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class DropSaltColumn1672109862870 implements MigrationInterface {
|
||||
name = 'DropSaltColumn1672109862870'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "salt"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "salt" character varying NOT NULL DEFAULT ''`);
|
||||
}
|
||||
|
||||
}
|
||||
16
server/src/infra/migrations/1672502270115-AddAPIKeys.ts
Normal file
16
server/src/infra/migrations/1672502270115-AddAPIKeys.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddAPIKeys1672502270115 implements MigrationInterface {
|
||||
name = 'AddAPIKeys1672502270115'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "api_keys" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "key" character varying NOT NULL, "userId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_5c8a79801b44bd27b79228e1dad" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" ADD CONSTRAINT "FK_6c2e267ae764a9413b863a29342" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" DROP CONSTRAINT "FK_6c2e267ae764a9413b863a29342"`);
|
||||
await queryRunner.query(`DROP TABLE "api_keys"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddSharedLinkTable1673150490490 implements MigrationInterface {
|
||||
name = 'AddSharedLinkTable1673150490490'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "shared_links" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "description" character varying, "userId" character varying NOT NULL, "key" bytea NOT NULL, "type" character varying NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE, "allowUpload" boolean NOT NULL DEFAULT false, "albumId" uuid, CONSTRAINT "UQ_sharedlink_key" UNIQUE ("key"), CONSTRAINT "PK_642e2b0f619e4876e5f90a43465" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_sharedlink_key" ON "shared_links" ("key") `);
|
||||
await queryRunner.query(`CREATE TABLE "shared_link__asset" ("assetsId" uuid NOT NULL, "sharedLinksId" uuid NOT NULL, CONSTRAINT "PK_9b4f3687f9b31d1e311336b05e3" PRIMARY KEY ("assetsId", "sharedLinksId"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_5b7decce6c8d3db9593d6111a6" ON "shared_link__asset" ("assetsId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_c9fab4aa97ffd1b034f3d6581a" ON "shared_link__asset" ("sharedLinksId") `);
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66" FOREIGN KEY ("albumId") REFERENCES "albums"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66" FOREIGN KEY ("assetsId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
await queryRunner.query(`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab" FOREIGN KEY ("sharedLinksId") REFERENCES "shared_links"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "shared_link__asset" DROP CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab"`);
|
||||
await queryRunner.query(`ALTER TABLE "shared_link__asset" DROP CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66"`);
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" DROP CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_c9fab4aa97ffd1b034f3d6581a"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_5b7decce6c8d3db9593d6111a6"`);
|
||||
await queryRunner.query(`DROP TABLE "shared_link__asset"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_sharedlink_key"`);
|
||||
await queryRunner.query(`DROP TABLE "shared_links"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddMorePermissionToSharedLink1673907194740 implements MigrationInterface {
|
||||
name = 'AddMorePermissionToSharedLink1673907194740';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" ADD "allowDownload" boolean NOT NULL DEFAULT true`);
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" ADD "showExif" boolean NOT NULL DEFAULT true`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" DROP COLUMN "showExif"`);
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" DROP COLUMN "allowDownload"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import {MigrationInterface, QueryRunner} from 'typeorm';
|
||||
|
||||
export class RemoveVideoCodecConfigOption1674263302006 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DELETE FROM "system_config" WHERE key = 'ffmpeg.targetVideoCodec'`);
|
||||
await queryRunner.query(`DELETE FROM "system_config" WHERE key = 'ffmpeg.targetAudioCodec'`);
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class CreateUserTokenEntity1674342044239 implements MigrationInterface {
|
||||
name = 'CreateUserTokenEntity1674342044239'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "user_token" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "token" character varying NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "userId" uuid, CONSTRAINT "PK_48cb6b5c20faa63157b3c1baf7f" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "user_token" ADD CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "user_token" DROP CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918"`);
|
||||
await queryRunner.query(`DROP TABLE "user_token"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AlterExifExposureTimeToString1674757936889 implements MigrationInterface {
|
||||
name = 'AlterExifExposureTimeToString1674757936889'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "exposureTime"`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "exposureTime" character varying`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "exposureTime"`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "exposureTime" double precision`);
|
||||
}
|
||||
|
||||
}
|
||||
14
server/src/infra/migrations/1674774248319-TruncateAPIKeys.ts
Normal file
14
server/src/infra/migrations/1674774248319-TruncateAPIKeys.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class TruncateAPIKeys1674774248319 implements MigrationInterface {
|
||||
name = 'TruncateAPIKeys1674774248319'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`TRUNCATE TABLE "api_keys"`);
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
//noop
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddSharedLinkUserForeignKeyConstraint1674939383309 implements MigrationInterface {
|
||||
name = 'AddSharedLinkUserForeignKeyConstraint1674939383309';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" ALTER COLUMN "userId" TYPE varchar(36)`);
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" ALTER COLUMN "userId" TYPE uuid using "userId"::uuid`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_66fe3837414c5a9f1c33ca49340" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" DROP CONSTRAINT "FK_66fe3837414c5a9f1c33ca49340"`);
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" ALTER COLUMN "userId" TYPE character varying`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddUpdatedAtColumnToAlbumsUsersAssets1675667878312 implements MigrationInterface {
|
||||
name = 'AddUpdatedAtColumnToAlbumsUsersAssets1675667878312';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "albums" ADD "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "albums" DROP COLUMN "updatedAt"`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "updatedAt"`);
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "updatedAt"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddAlbumUserForeignKeyConstraint1675701909594 implements MigrationInterface {
|
||||
name = 'AddAlbumUserForeignKeyConstraint1675701909594';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DELETE FROM "albums" WHERE "ownerId"::uuid NOT IN (SELECT id FROM "users") `)
|
||||
await queryRunner.query(`ALTER TABLE "albums" ALTER COLUMN "ownerId" TYPE varchar(36)`);
|
||||
await queryRunner.query(`ALTER TABLE "albums" ALTER COLUMN "ownerId" TYPE uuid using "ownerId"::uuid`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "albums" ADD CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "albums" DROP CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums" ALTER COLUMN "ownerId" TYPE character varying`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class APIKeyUUIDPrimaryKey1675808874445 implements MigrationInterface {
|
||||
name = 'APIKeyUUIDPrimaryKey1675808874445'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" DROP CONSTRAINT "PK_5c8a79801b44bd27b79228e1dad"`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" DROP COLUMN "id"`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" ADD "id" uuid NOT NULL DEFAULT uuid_generate_v4()`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" ADD CONSTRAINT "PK_5c8a79801b44bd27b79228e1dad" PRIMARY KEY ("id")`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" DROP CONSTRAINT "PK_5c8a79801b44bd27b79228e1dad"`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" DROP COLUMN "id"`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" ADD "id" SERIAL NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" ADD CONSTRAINT "PK_5c8a79801b44bd27b79228e1dad" PRIMARY KEY ("id")`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class FixAlbumEntityTypeORM1675812532822 implements MigrationInterface {
|
||||
name = 'FixAlbumEntityTypeORM1675812532822'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" RENAME TO "albums_assets_assets"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" DROP CONSTRAINT "FK_7ae4e03729895bf87e056d7b598"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" DROP CONSTRAINT "FK_256a30a03a4a0aff0394051397d"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" DROP CONSTRAINT "UQ_unique_asset_in_album"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" DROP CONSTRAINT "PK_a34e076afbc601d81938e2c2277"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" DROP COLUMN "id"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" RENAME COLUMN "albumId" TO "albumsId"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" RENAME COLUMN "assetId" TO "assetsId"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "PK_c67bc36fa845fb7b18e0e398180" PRIMARY KEY ("albumsId", "assetsId")`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_e590fa396c6898fcd4a50e4092" ON "albums_assets_assets" ("albumsId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_4bd1303d199f4e72ccdf998c62" ON "albums_assets_assets" ("assetsId") `);
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_e590fa396c6898fcd4a50e40927" FOREIGN KEY ("albumsId") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621" FOREIGN KEY ("assetsId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" RENAME TO "albums_shared_users_users"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" DROP CONSTRAINT "FK_543c31211653e63e080ba882eb5"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" DROP CONSTRAINT "FK_7b3bf0f5f8da59af30519c25f18"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" DROP CONSTRAINT "PK_unique_user_in_album"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" DROP CONSTRAINT "PK_b6562316a98845a7b3e9a25cdd0"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" DROP COLUMN "id"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" RENAME COLUMN "albumId" TO "albumsId"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" RENAME COLUMN "sharedUserId" TO "usersId"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "PK_7df55657e0b2e8b626330a0ebc8" PRIMARY KEY ("albumsId", "usersId")`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_427c350ad49bd3935a50baab73" ON "albums_shared_users_users" ("albumsId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f48513bf9bccefd6ff3ad30bd0" ON "albums_shared_users_users" ("usersId") `);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_427c350ad49bd3935a50baab737" FOREIGN KEY ("albumsId") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06" FOREIGN KEY ("usersId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "albums" DROP CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4"`)
|
||||
await queryRunner.query(`ALTER TABLE "albums" ADD CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "albums_assets_assets" RENAME TO "asset_album"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" RENAME TO "user_shared_album"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_e590fa396c6898fcd4a50e40927"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP CONSTRAINT "FK_427c350ad49bd3935a50baab737"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_427c350ad49bd3935a50baab73"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_f48513bf9bccefd6ff3ad30bd0"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_e590fa396c6898fcd4a50e4092"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_4bd1303d199f4e72ccdf998c62"`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "albums" DROP CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "albums" ADD CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP CONSTRAINT "PK_7df55657e0b2e8b626330a0ebc8"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" ADD CONSTRAINT "PK_323f8dcbe85373722886940f143" PRIMARY KEY ("albumsId")`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP COLUMN "usersId"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP CONSTRAINT "PK_323f8dcbe85373722886940f143"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP COLUMN "albumsId"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" ADD "sharedUserId" uuid NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" ADD "albumId" uuid NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" ADD "id" SERIAL NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" ADD CONSTRAINT "PK_b6562316a98845a7b3e9a25cdd0" PRIMARY KEY ("id")`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" ADD CONSTRAINT "PK_unique_user_in_album" UNIQUE ("albumId", "sharedUserId")`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" ADD CONSTRAINT "FK_7b3bf0f5f8da59af30519c25f18" FOREIGN KEY ("albumId") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "user_shared_album" ADD CONSTRAINT "FK_543c31211653e63e080ba882eb5" FOREIGN KEY ("sharedUserId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "PK_c67bc36fa845fb7b18e0e398180"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "PK_b4f2e5b96efc25cbccd80a04f7a" PRIMARY KEY ("albumsId")`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "PK_b4f2e5b96efc25cbccd80a04f7a"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" RENAME COLUMN "albumsId" TO "albumId"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" RENAME COLUMN "assetsId" TO "assetId"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" ADD "id" SERIAL NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "PK_a34e076afbc601d81938e2c2277" PRIMARY KEY ("id")`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "UQ_unique_asset_in_album" UNIQUE ("albumId", "assetId")`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "FK_256a30a03a4a0aff0394051397d" FOREIGN KEY ("albumId") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "FK_7ae4e03729895bf87e056d7b598" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AppleContentIdentifier1676437878377 implements MigrationInterface {
|
||||
name = 'AppleContentIdentifier1676437878377';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "livePhotoCID" character varying`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_live_photo_cid" ON "exif" ("livePhotoCID") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_live_photo_cid"`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "livePhotoCID"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class FixAssetRelations1676680127415 implements MigrationInterface {
|
||||
name = 'FixAssetRelations1676680127415'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "modifiedAt" TO "fileModifiedAt"`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "createdAt" TO "fileCreatedAt"`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "userId" TO "ownerId"`);
|
||||
await queryRunner.query(`ALTER TABLE assets ALTER COLUMN "ownerId" TYPE uuid USING "ownerId"::uuid;`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD CONSTRAINT "UQ_16294b83fa8c0149719a1f631ef" UNIQUE ("livePhotoVideoId")`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD CONSTRAINT "FK_16294b83fa8c0149719a1f631ef" FOREIGN KEY ("livePhotoVideoId") REFERENCES "assets"("id") ON DELETE SET NULL ON UPDATE CASCADE`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_16294b83fa8c0149719a1f631ef"`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d"`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "UQ_16294b83fa8c0149719a1f631ef"`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "fileCreatedAt" TO "createdAt"`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "fileModifiedAt" TO "modifiedAt"`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "ownerId" TO "userId"`);
|
||||
await queryRunner.query(`ALTER TABLE assets ALTER COLUMN "userId" TYPE varchar`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AssetCreatedAtField1676721296440 implements MigrationInterface {
|
||||
name = 'AssetCreatedAtField1676721296440'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "createdAt"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class ExifEntityDefinitionFixes1676848629119 implements MigrationInterface {
|
||||
name = 'ExifEntityDefinitionFixes1676848629119'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "description" SET NOT NULL`);
|
||||
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_c0117fdbc50b917ef9067740c4"`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "PK_28663352d85078ad0046dafafaa"`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "id"`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "FK_c0117fdbc50b917ef9067740c44"`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD CONSTRAINT "PK_c0117fdbc50b917ef9067740c44" PRIMARY KEY ("assetId")`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD CONSTRAINT "FK_c0117fdbc50b917ef9067740c44" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "description" DROP NOT NULL`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "FK_c0117fdbc50b917ef9067740c44"`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "PK_c0117fdbc50b917ef9067740c44"`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD CONSTRAINT "FK_c0117fdbc50b917ef9067740c44" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "id" SERIAL NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD CONSTRAINT "PK_28663352d85078ad0046dafafaa" PRIMARY KEY ("id")`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c0117fdbc50b917ef9067740c4" ON "exif" ("assetId") `);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class SharedLinkEntityDefinitionFixes1676848694786 implements MigrationInterface {
|
||||
name = 'SharedLinkEntityDefinitionFixes1676848694786'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" ALTER COLUMN "createdAt" SET DEFAULT now()`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" ALTER COLUMN "createdAt" DROP DEFAULT`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class SmartInfoEntityDefinitionFixes1676852143506 implements MigrationInterface {
|
||||
name = 'SmartInfoEntityDefinitionFixes1676852143506'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_5e3753aadd956110bf3ec0244a"`);
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "PK_0beace66440e9713f5c40470e46"`);
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" DROP COLUMN "id"`);
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "FK_5e3753aadd956110bf3ec0244ac"`);
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" ADD CONSTRAINT "PK_5e3753aadd956110bf3ec0244ac" PRIMARY KEY ("assetId")`);
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" ADD CONSTRAINT "FK_5e3753aadd956110bf3ec0244ac" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "FK_5e3753aadd956110bf3ec0244ac"`);
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "PK_5e3753aadd956110bf3ec0244ac"`);
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" ADD CONSTRAINT "FK_5e3753aadd956110bf3ec0244ac" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" ADD "id" SERIAL NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" ADD CONSTRAINT "PK_0beace66440e9713f5c40470e46" PRIMARY KEY ("id")`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5e3753aadd956110bf3ec0244a" ON "smart_info" ("assetId") `);
|
||||
}
|
||||
|
||||
}
|
||||
14
server/src/infra/migrations/1677497925328-AddExifTimeZone.ts
Normal file
14
server/src/infra/migrations/1677497925328-AddExifTimeZone.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddExifTimeZone1677497925328 implements MigrationInterface {
|
||||
name = 'AddExifTimeZone1677497925328'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "timeZone" character varying`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "timeZone"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddIndexForAlbumInSharedLinkTable1677535643119 implements MigrationInterface {
|
||||
name = 'AddIndexForAlbumInSharedLinkTable1677535643119'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE INDEX "IDX_sharedlink_albumId" ON "shared_links" ("albumId") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_sharedlink_albumId"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AlbumThumbnailRelation1677613712565 implements MigrationInterface {
|
||||
name = 'AlbumThumbnailRelation1677613712565';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Make sure all albums have a valid albumThumbnailAssetId UUID or NULL.
|
||||
await queryRunner.query(`
|
||||
UPDATE "albums"
|
||||
SET
|
||||
"albumThumbnailAssetId" = (
|
||||
SELECT
|
||||
"albums_assets2"."assetsId"
|
||||
FROM
|
||||
"assets" "assets",
|
||||
"albums_assets_assets" "albums_assets2"
|
||||
WHERE
|
||||
"albums_assets2"."assetsId" = "assets"."id"
|
||||
AND "albums_assets2"."albumsId" = "albums"."id"
|
||||
ORDER BY
|
||||
"assets"."fileCreatedAt" DESC
|
||||
LIMIT 1
|
||||
),
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
WHERE
|
||||
"albums"."albumThumbnailAssetId" IS NULL
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM "albums_assets_assets" "albums_assets"
|
||||
WHERE "albums"."id" = "albums_assets"."albumsId"
|
||||
)
|
||||
OR "albums"."albumThumbnailAssetId" IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "albums_assets_assets" "albums_assets"
|
||||
WHERE
|
||||
"albums"."id" = "albums_assets"."albumsId"
|
||||
AND "albums"."albumThumbnailAssetId" = "albums_assets"."assetsId"::varchar
|
||||
)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "albums"
|
||||
ALTER COLUMN "albumThumbnailAssetId"
|
||||
TYPE uuid USING "albumThumbnailAssetId"::uuid
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "albums" ADD CONSTRAINT "FK_05895aa505a670300d4816debce" FOREIGN KEY ("albumThumbnailAssetId") REFERENCES "assets"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "albums" DROP CONSTRAINT "FK_05895aa505a670300d4816debce"`);
|
||||
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "albums" ALTER COLUMN "albumThumbnailAssetId" TYPE varchar USING "albumThumbnailAssetId"::varchar
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddCLIPEncodeDataColumn1677971458822 implements MigrationInterface {
|
||||
name = 'AddCLIPEncodeDataColumn1677971458822';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" ADD "clipEmbedding" numeric(20,19) array`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" DROP COLUMN "clipEmbedding"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class UpdateTranscodeOption1679751316282 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
UPDATE system_config
|
||||
SET
|
||||
key = 'ffmpeg.transcode',
|
||||
value = '"all"'
|
||||
WHERE
|
||||
key = 'ffmpeg.transcodeAll' AND value = 'true'
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
UPDATE system_config
|
||||
SET
|
||||
key = 'ffmpeg.transcodeAll',
|
||||
value = 'true'
|
||||
WHERE
|
||||
key = 'ffmpeg.transcode' AND value = '"all"'
|
||||
`);
|
||||
|
||||
await queryRunner.query(`DELETE FROM "system_config" WHERE key = 'ffmpeg.transcode'`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class ClipEmbeddingFloat41679901204458 implements MigrationInterface {
|
||||
name = 'ClipEmbeddingFloat41679901204458';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "smart_info" ALTER COLUMN "clipEmbedding" TYPE real array USING "clipEmbedding"::real array`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "smart_info" ALTER COLUMN "clipEmbedding" TYPE numeric(20,19) array USING "clipEmbedding"::numeric(20,19) array`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddIsArchivedColumn1680632845740 implements MigrationInterface {
|
||||
name = 'AddIsArchivedColumn1680632845740'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "isArchived" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "isArchived"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class RemoveRedundantConstraints1680694465853 implements MigrationInterface {
|
||||
name = 'RemoveRedundantConstraints1680694465853'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "REL_c0117fdbc50b917ef9067740c4"`);
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "UQ_5e3753aadd956110bf3ec0244ac"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "smart_info" ADD CONSTRAINT "UQ_5e3753aadd956110bf3ec0244ac" UNIQUE ("assetId")`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD CONSTRAINT "REL_c0117fdbc50b917ef9067740c4" UNIQUE ("assetId")`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddOriginalFileNameToAssetTable1681144628393 implements MigrationInterface {
|
||||
name = 'AddOriginalFileNameToAssetTable1681144628393';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "originalFileName" character varying`);
|
||||
|
||||
await queryRunner.query(`
|
||||
UPDATE assets a
|
||||
SET "originalFileName" = (
|
||||
select e."imageName"
|
||||
from exif e
|
||||
where e."assetId" = a.id
|
||||
)
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
UPDATE assets a
|
||||
SET "originalFileName" = a.id
|
||||
where a."originalFileName" IS NULL or a."originalFileName" = ''
|
||||
`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "originalFileName" SET NOT NULL`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "originalFileName"`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RemoveImageNameFromEXIFTable1681159594469 implements MigrationInterface {
|
||||
name = 'RemoveImageNameFromEXIFTable1681159594469';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN IF EXISTS "exifTextSearchableColumn"`);
|
||||
await queryRunner.query(
|
||||
`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`,
|
||||
['GENERATED_COLUMN', 'exifTextSearchableColumn', 'immich', 'public', 'exif'],
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "exifTextSearchableColumn" tsvector GENERATED ALWAYS AS (TO_TSVECTOR('english',
|
||||
COALESCE(make, '') || ' ' ||
|
||||
COALESCE(model, '') || ' ' ||
|
||||
COALESCE(orientation, '') || ' ' ||
|
||||
COALESCE("lensModel", '') || ' ' ||
|
||||
COALESCE("city", '') || ' ' ||
|
||||
COALESCE("state", '') || ' ' ||
|
||||
COALESCE("country", ''))) STORED NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[
|
||||
'immich',
|
||||
'public',
|
||||
'exif',
|
||||
'GENERATED_COLUMN',
|
||||
'exifTextSearchableColumn',
|
||||
"TO_TSVECTOR('english',\n COALESCE(make, '') || ' ' ||\n COALESCE(model, '') || ' ' ||\n COALESCE(orientation, '') || ' ' ||\n COALESCE(\"lensModel\", '') || ' ' ||\n COALESCE(\"city\", '') || ' ' ||\n COALESCE(\"state\", '') || ' ' ||\n COALESCE(\"country\", ''))",
|
||||
],
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "imageName"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`,
|
||||
['GENERATED_COLUMN', 'exifTextSearchableColumn', 'immich', 'public', 'exif'],
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "exifTextSearchableColumn"`);
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[
|
||||
'immich',
|
||||
'public',
|
||||
'exif',
|
||||
'GENERATED_COLUMN',
|
||||
'exifTextSearchableColumn',
|
||||
"TO_TSVECTOR('english',\n COALESCE(make, '') || ' ' ||\n COALESCE(model, '') || ' ' ||\n COALESCE(orientation, '') || ' ' ||\n COALESCE(\"lensModel\", '') || ' ' ||\n COALESCE(\"imageName\", '') || ' ' ||\n COALESCE(\"city\", '') || ' ' ||\n COALESCE(\"state\", '') || ' ' ||\n COALESCE(\"country\", ''))",
|
||||
],
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "exifTextSearchableColumn" tsvector GENERATED ALWAYS AS (TO_TSVECTOR('english',
|
||||
COALESCE(make, '') || ' ' ||
|
||||
COALESCE(model, '') || ' ' ||
|
||||
COALESCE(orientation, '') || ' ' ||
|
||||
COALESCE("lensModel", '') || ' ' ||
|
||||
COALESCE("imageName", '') || ' ' ||
|
||||
COALESCE("city", '') || ' ' ||
|
||||
COALESCE("state", '') || ' ' ||
|
||||
COALESCE("country", ''))) STORED NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "exif" ADD "imageName" character varying`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class FixNullableRelations1682371561743 implements MigrationInterface {
|
||||
name = 'FixNullableRelations1682371561743';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "user_token" DROP CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_token" ALTER COLUMN "userId" SET NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_token" ADD CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "user_token" DROP CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_token" ALTER COLUMN "userId" DROP NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user_token" ADD CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddDeviceInfoToUserToken1682371791038 implements MigrationInterface {
|
||||
name = 'AddDeviceInfoToUserToken1682371791038'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "user_token" ADD "deviceType" character varying NOT NULL DEFAULT ''`);
|
||||
await queryRunner.query(`ALTER TABLE "user_token" ADD "deviceOS" character varying NOT NULL DEFAULT ''`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "user_token" DROP COLUMN "deviceOS"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_token" DROP COLUMN "deviceType"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class DropDeviceInfoTable1682710252424 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`drop table device_info`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
create table if not exists device_info
|
||||
(
|
||||
id serial
|
||||
constraint "PK_b1c15a80b0a4e5f4eebadbdd92c"
|
||||
primary key,
|
||||
"userId" varchar not null,
|
||||
"deviceId" varchar not null,
|
||||
"deviceType" varchar not null,
|
||||
"notificationToken" varchar,
|
||||
"createdAt" timestamp default now() not null,
|
||||
"isAutoBackup" boolean default false not null,
|
||||
constraint "UQ_ebad78f36b10d15fbea8560e107"
|
||||
unique ("userId", "deviceId")
|
||||
);
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddPartnersTable1683808254676 implements MigrationInterface {
|
||||
name = 'AddPartnersTable1683808254676'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "partners" ("sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_f1cc8f73d16b367f426261a8736" PRIMARY KEY ("sharedById", "sharedWithId"))`);
|
||||
await queryRunner.query(`ALTER TABLE "partners" ADD CONSTRAINT "FK_7e077a8b70b3530138610ff5e04" FOREIGN KEY ("sharedById") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "partners" ADD CONSTRAINT "FK_d7e875c6c60e661723dbf372fd3" FOREIGN KEY ("sharedWithId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "partners" DROP CONSTRAINT "FK_d7e875c6c60e661723dbf372fd3"`);
|
||||
await queryRunner.query(`ALTER TABLE "partners" DROP CONSTRAINT "FK_7e077a8b70b3530138610ff5e04"`);
|
||||
await queryRunner.query(`DROP TABLE "partners"`);
|
||||
}
|
||||
|
||||
}
|
||||
22
server/src/infra/migrations/1684255168091-AddFacialTables.ts
Normal file
22
server/src/infra/migrations/1684255168091-AddFacialTables.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddFacialTables1684255168091 implements MigrationInterface {
|
||||
name = 'AddFacialTables1684255168091'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "person" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "ownerId" uuid NOT NULL, "name" character varying NOT NULL DEFAULT '', "thumbnailPath" character varying NOT NULL DEFAULT '', CONSTRAINT "PK_5fdaf670315c4b7e70cce85daa3" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "asset_faces" ("assetId" uuid NOT NULL, "personId" uuid NOT NULL, "embedding" real array, CONSTRAINT "PK_bf339a24070dac7e71304ec530a" PRIMARY KEY ("assetId", "personId"))`);
|
||||
await queryRunner.query(`ALTER TABLE "person" ADD CONSTRAINT "FK_5527cc99f530a547093f9e577b6" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_02a43fd0b3c50fb6d7f0cb7282c" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_95ad7106dd7b484275443f580f9" FOREIGN KEY ("personId") REFERENCES "person"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "asset_faces" DROP CONSTRAINT "FK_95ad7106dd7b484275443f580f9"`);
|
||||
await queryRunner.query(`ALTER TABLE "asset_faces" DROP CONSTRAINT "FK_02a43fd0b3c50fb6d7f0cb7282c"`);
|
||||
await queryRunner.query(`ALTER TABLE "person" DROP CONSTRAINT "FK_5527cc99f530a547093f9e577b6"`);
|
||||
await queryRunner.query(`DROP TABLE "asset_faces"`);
|
||||
await queryRunner.query(`DROP TABLE "person"`);
|
||||
}
|
||||
|
||||
}
|
||||
14
server/src/infra/migrations/1684273840676-AddSidecarFile.ts
Normal file
14
server/src/infra/migrations/1684273840676-AddSidecarFile.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddSidecarFile1684273840676 implements MigrationInterface {
|
||||
name = 'AddSidecarFile1684273840676'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "sidecarPath" character varying`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "sidecarPath"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RequireChecksumNotNull1684328185099 implements MigrationInterface {
|
||||
name = 'removeNotNullFromChecksumIndex1684328185099';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_64c507300988dd1764f9a6530c"`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "checksum" SET NOT NULL`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_8d3efe36c0755849395e6ea866" ON "assets" ("checksum") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_8d3efe36c0755849395e6ea866"`);
|
||||
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "checksum" DROP NOT NULL`);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_64c507300988dd1764f9a6530c" ON "assets" ("checksum") WHERE ('checksum' IS NOT NULL)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
16
server/src/infra/migrations/1684410565398-AddStorageLabel.ts
Normal file
16
server/src/infra/migrations/1684410565398-AddStorageLabel.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddStorageLabel1684410565398 implements MigrationInterface {
|
||||
name = 'AddStorageLabel1684410565398'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD "storageLabel" character varying`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ADD CONSTRAINT "UQ_b309cf34fa58137c416b32cea3a" UNIQUE ("storageLabel")`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT "UQ_b309cf34fa58137c416b32cea3a"`);
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "storageLabel"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddUserTokenAndAPIKeyCascades1684867360825 implements MigrationInterface {
|
||||
name = 'AddUserTokenAndAPIKeyCascades1684867360825'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" DROP CONSTRAINT "FK_6c2e267ae764a9413b863a29342"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_token" DROP CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918"`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" ADD CONSTRAINT "FK_6c2e267ae764a9413b863a29342" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
await queryRunner.query(`ALTER TABLE "user_token" ADD CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "user_token" DROP CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918"`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" DROP CONSTRAINT "FK_6c2e267ae764a9413b863a29342"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_token" ADD CONSTRAINT "FK_d37db50eecdf9b8ce4eedd2f918" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "api_keys" ADD CONSTRAINT "FK_6c2e267ae764a9413b863a29342" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddSharedLinkCascade1685044328272 implements MigrationInterface {
|
||||
name = 'AddSharedLinkCascade1685044328272'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" DROP CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66"`);
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66" FOREIGN KEY ("albumId") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" DROP CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66"`);
|
||||
await queryRunner.query(`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66" FOREIGN KEY ("albumId") REFERENCES "albums"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class UserDatesTimestamptz1685370430343 implements MigrationInterface {
|
||||
name = 'UserDatesTimestamptz1685370430343'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "createdAt" TYPE TIMESTAMP WITH TIME ZONE`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "deletedAt" TYPE TIMESTAMP WITH TIME ZONE`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "deletedAt" TYPE TIMESTAMP`);
|
||||
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "createdAt" TYPE TIMESTAMP`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RemoveInvalidCoordinates1685731372040 implements MigrationInterface {
|
||||
name = 'RemoveInvalidCoordinates1685731372040';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`UPDATE "exif" SET "latitude" = NULL WHERE "latitude" IN ('NaN', 'Infinity', '-Infinity')`);
|
||||
await queryRunner.query(
|
||||
`UPDATE "exif" SET "longitude" = NULL WHERE "longitude" IN ('NaN', 'Infinity', '-Infinity')`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
// Empty, data cannot be restored
|
||||
}
|
||||
}
|
||||
23
server/src/infra/redis-io.adapter.ts
Normal file
23
server/src/infra/redis-io.adapter.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { IoAdapter } from '@nestjs/platform-socket.io';
|
||||
import { createAdapter } from '@socket.io/redis-adapter';
|
||||
import Redis from 'ioredis';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { ServerOptions } from 'socket.io';
|
||||
import { redisConfig } from './infra.config';
|
||||
|
||||
export class RedisIoAdapter extends IoAdapter {
|
||||
private readonly logger = new Logger(RedisIoAdapter.name);
|
||||
createIOServer(port: number, options?: ServerOptions): any {
|
||||
const pubClient = new Redis(redisConfig);
|
||||
pubClient.on('error', (error) => {
|
||||
this.logger.error(`Redis pubClient: ${error}`);
|
||||
});
|
||||
const subClient = pubClient.duplicate();
|
||||
subClient.on('error', (error) => {
|
||||
this.logger.error(`Redis subClient: ${error}`);
|
||||
});
|
||||
const server = super.createIOServer(port, options);
|
||||
server.adapter(createAdapter(pubClient, subClient));
|
||||
return server;
|
||||
}
|
||||
}
|
||||
66
server/src/infra/repositories/access.repository.ts
Normal file
66
server/src/infra/repositories/access.repository.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { IAccessRepository } from '@app/domain';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { PartnerEntity, SharedLinkEntity } from '../entities';
|
||||
|
||||
export class AccessRepository implements IAccessRepository {
|
||||
constructor(
|
||||
@InjectRepository(PartnerEntity) private partnerRepository: Repository<PartnerEntity>,
|
||||
@InjectRepository(SharedLinkEntity) private sharedLinkRepository: Repository<SharedLinkEntity>,
|
||||
) {}
|
||||
|
||||
hasPartnerAccess(userId: string, partnerId: string): Promise<boolean> {
|
||||
return this.partnerRepository.exist({
|
||||
where: {
|
||||
sharedWithId: userId,
|
||||
sharedById: partnerId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
hasPartnerAssetAccess(userId: string, assetId: string): Promise<boolean> {
|
||||
return this.partnerRepository.exist({
|
||||
where: {
|
||||
sharedWith: {
|
||||
id: userId,
|
||||
},
|
||||
sharedBy: {
|
||||
assets: {
|
||||
id: assetId,
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
sharedWith: true,
|
||||
sharedBy: {
|
||||
assets: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async hasSharedLinkAssetAccess(sharedLinkId: string, assetId: string): Promise<boolean> {
|
||||
return (
|
||||
// album asset
|
||||
(await this.sharedLinkRepository.exist({
|
||||
where: {
|
||||
id: sharedLinkId,
|
||||
album: {
|
||||
assets: {
|
||||
id: assetId,
|
||||
},
|
||||
},
|
||||
},
|
||||
})) ||
|
||||
// individual asset
|
||||
(await this.sharedLinkRepository.exist({
|
||||
where: {
|
||||
id: sharedLinkId,
|
||||
assets: {
|
||||
id: assetId,
|
||||
},
|
||||
},
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
165
server/src/infra/repositories/album.repository.ts
Normal file
165
server/src/infra/repositories/album.repository.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { AlbumAssetCount, IAlbumRepository } from '@app/domain';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { In, IsNull, Not, Repository } from 'typeorm';
|
||||
import { dataSource } from '../database.config';
|
||||
import { AlbumEntity } from '../entities';
|
||||
|
||||
@Injectable()
|
||||
export class AlbumRepository implements IAlbumRepository {
|
||||
constructor(@InjectRepository(AlbumEntity) private repository: Repository<AlbumEntity>) {}
|
||||
|
||||
getByIds(ids: string[]): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
id: In(ids),
|
||||
},
|
||||
relations: {
|
||||
owner: true,
|
||||
sharedUsers: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getByAssetId(ownerId: string, assetId: string): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
where: { ownerId, assets: { id: assetId } },
|
||||
relations: { owner: true, sharedUsers: true },
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async getAssetCountForIds(ids: string[]): Promise<AlbumAssetCount[]> {
|
||||
// Guard against running invalid query when ids list is empty.
|
||||
if (!ids.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Only possible with query builder because of GROUP BY.
|
||||
const countByAlbums = await this.repository
|
||||
.createQueryBuilder('album')
|
||||
.select('album.id')
|
||||
.addSelect('COUNT(albums_assets.assetsId)', 'asset_count')
|
||||
.leftJoin('albums_assets_assets', 'albums_assets', 'albums_assets.albumsId = album.id')
|
||||
.where('album.id IN (:...ids)', { ids })
|
||||
.groupBy('album.id')
|
||||
.getRawMany();
|
||||
|
||||
return countByAlbums.map<AlbumAssetCount>((albumCount) => ({
|
||||
albumId: albumCount['album_id'],
|
||||
assetCount: Number(albumCount['asset_count']),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the album IDs that have an invalid thumbnail, when:
|
||||
* - Thumbnail references an asset outside the album
|
||||
* - Empty album still has a thumbnail set
|
||||
*/
|
||||
async getInvalidThumbnail(): Promise<string[]> {
|
||||
// Using dataSource, because there is no direct access to albums_assets_assets.
|
||||
const albumHasAssets = dataSource
|
||||
.createQueryBuilder()
|
||||
.select('1')
|
||||
.from('albums_assets_assets', 'albums_assets')
|
||||
.where('"albums"."id" = "albums_assets"."albumsId"');
|
||||
|
||||
const albumContainsThumbnail = albumHasAssets
|
||||
.clone()
|
||||
.andWhere('"albums"."albumThumbnailAssetId" = "albums_assets"."assetsId"');
|
||||
|
||||
const albums = await this.repository
|
||||
.createQueryBuilder('albums')
|
||||
.select('albums.id')
|
||||
.where(`"albums"."albumThumbnailAssetId" IS NULL AND EXISTS (${albumHasAssets.getQuery()})`)
|
||||
.orWhere(`"albums"."albumThumbnailAssetId" IS NOT NULL AND NOT EXISTS (${albumContainsThumbnail.getQuery()})`)
|
||||
.getMany();
|
||||
|
||||
return albums.map((album) => album.id);
|
||||
}
|
||||
|
||||
getOwned(ownerId: string): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
||||
where: { ownerId },
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get albums shared with and shared by owner.
|
||||
*/
|
||||
getShared(ownerId: string): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
||||
where: [
|
||||
{ sharedUsers: { id: ownerId } },
|
||||
{ sharedLinks: { userId: ownerId } },
|
||||
{ ownerId, sharedUsers: { id: Not(IsNull()) } },
|
||||
],
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get albums of owner that are _not_ shared
|
||||
*/
|
||||
getNotShared(ownerId: string): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
||||
where: { ownerId, sharedUsers: { id: IsNull() }, sharedLinks: { id: IsNull() } },
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async deleteAll(userId: string): Promise<void> {
|
||||
await this.repository.delete({ ownerId: userId });
|
||||
}
|
||||
|
||||
getAll(): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
relations: {
|
||||
owner: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async hasAsset(id: string, assetId: string): Promise<boolean> {
|
||||
const count = await this.repository.count({
|
||||
where: {
|
||||
id,
|
||||
assets: {
|
||||
id: assetId,
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
assets: true,
|
||||
},
|
||||
});
|
||||
|
||||
return Boolean(count);
|
||||
}
|
||||
|
||||
async create(album: Partial<AlbumEntity>): Promise<AlbumEntity> {
|
||||
return this.save(album);
|
||||
}
|
||||
|
||||
async update(album: Partial<AlbumEntity>): Promise<AlbumEntity> {
|
||||
return this.save(album);
|
||||
}
|
||||
|
||||
async delete(album: AlbumEntity): Promise<void> {
|
||||
await this.repository.remove(album);
|
||||
}
|
||||
|
||||
private async save(album: Partial<AlbumEntity>) {
|
||||
const { id } = await this.repository.save(album);
|
||||
return this.repository.findOneOrFail({
|
||||
where: { id },
|
||||
relations: {
|
||||
owner: true,
|
||||
sharedUsers: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
45
server/src/infra/repositories/api-key.repository.ts
Normal file
45
server/src/infra/repositories/api-key.repository.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { IKeyRepository } from '@app/domain';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { APIKeyEntity } from '../entities';
|
||||
|
||||
@Injectable()
|
||||
export class APIKeyRepository implements IKeyRepository {
|
||||
constructor(@InjectRepository(APIKeyEntity) private repository: Repository<APIKeyEntity>) {}
|
||||
|
||||
async create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity> {
|
||||
return this.repository.save(dto);
|
||||
}
|
||||
|
||||
async update(userId: string, id: string, dto: Partial<APIKeyEntity>): Promise<APIKeyEntity> {
|
||||
await this.repository.update({ userId, id }, dto);
|
||||
return this.repository.findOneOrFail({ where: { id: dto.id } });
|
||||
}
|
||||
|
||||
async delete(userId: string, id: string): Promise<void> {
|
||||
await this.repository.delete({ userId, id });
|
||||
}
|
||||
|
||||
getKey(hashedToken: string): Promise<APIKeyEntity | null> {
|
||||
return this.repository.findOne({
|
||||
select: {
|
||||
id: true,
|
||||
key: true,
|
||||
userId: true,
|
||||
},
|
||||
where: { key: hashedToken },
|
||||
relations: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getById(userId: string, id: string): Promise<APIKeyEntity | null> {
|
||||
return this.repository.findOne({ where: { userId, id } });
|
||||
}
|
||||
|
||||
getByUserId(userId: string): Promise<APIKeyEntity[]> {
|
||||
return this.repository.find({ where: { userId }, order: { createdAt: 'DESC' } });
|
||||
}
|
||||
}
|
||||
255
server/src/infra/repositories/asset.repository.ts
Normal file
255
server/src/infra/repositories/asset.repository.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import {
|
||||
AssetSearchOptions,
|
||||
IAssetRepository,
|
||||
LivePhotoSearchOptions,
|
||||
MapMarker,
|
||||
MapMarkerSearchOptions,
|
||||
Paginated,
|
||||
PaginationOptions,
|
||||
WithoutProperty,
|
||||
WithProperty,
|
||||
} from '@app/domain';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { FindOptionsRelations, FindOptionsWhere, In, IsNull, Not, Repository } from 'typeorm';
|
||||
import { AssetEntity, AssetType } from '../entities';
|
||||
import OptionalBetween from '../utils/optional-between.util';
|
||||
import { paginate } from '../utils/pagination.util';
|
||||
|
||||
@Injectable()
|
||||
export class AssetRepository implements IAssetRepository {
|
||||
constructor(@InjectRepository(AssetEntity) private repository: Repository<AssetEntity>) {}
|
||||
|
||||
getByIds(ids: string[]): Promise<AssetEntity[]> {
|
||||
return this.repository.find({
|
||||
where: { id: In(ids) },
|
||||
relations: {
|
||||
exifInfo: true,
|
||||
smartInfo: true,
|
||||
tags: true,
|
||||
faces: {
|
||||
person: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
async deleteAll(ownerId: string): Promise<void> {
|
||||
await this.repository.delete({ ownerId });
|
||||
}
|
||||
|
||||
getAll(pagination: PaginationOptions, options: AssetSearchOptions = {}): Paginated<AssetEntity> {
|
||||
return paginate(this.repository, pagination, {
|
||||
where: {
|
||||
isVisible: options.isVisible,
|
||||
type: options.type,
|
||||
},
|
||||
relations: {
|
||||
exifInfo: true,
|
||||
smartInfo: true,
|
||||
tags: true,
|
||||
faces: {
|
||||
person: true,
|
||||
},
|
||||
},
|
||||
order: {
|
||||
// Ensures correct order when paginating
|
||||
createdAt: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async save(asset: Partial<AssetEntity>): Promise<AssetEntity> {
|
||||
const { id } = await this.repository.save(asset);
|
||||
return this.repository.findOneOrFail({
|
||||
where: { id },
|
||||
relations: {
|
||||
exifInfo: true,
|
||||
owner: true,
|
||||
smartInfo: true,
|
||||
tags: true,
|
||||
faces: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null> {
|
||||
const { ownerId, otherAssetId, livePhotoCID, type } = options;
|
||||
|
||||
return this.repository.findOne({
|
||||
where: {
|
||||
id: Not(otherAssetId),
|
||||
ownerId,
|
||||
type,
|
||||
exifInfo: {
|
||||
livePhotoCID,
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
exifInfo: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity> {
|
||||
let relations: FindOptionsRelations<AssetEntity> = {};
|
||||
let where: FindOptionsWhere<AssetEntity> | FindOptionsWhere<AssetEntity>[] = {};
|
||||
|
||||
switch (property) {
|
||||
case WithoutProperty.THUMBNAIL:
|
||||
where = [
|
||||
{ resizePath: IsNull(), isVisible: true },
|
||||
{ resizePath: '', isVisible: true },
|
||||
{ webpPath: IsNull(), isVisible: true },
|
||||
{ webpPath: '', isVisible: true },
|
||||
];
|
||||
break;
|
||||
|
||||
case WithoutProperty.ENCODED_VIDEO:
|
||||
where = [
|
||||
{ type: AssetType.VIDEO, encodedVideoPath: IsNull() },
|
||||
{ type: AssetType.VIDEO, encodedVideoPath: '' },
|
||||
];
|
||||
break;
|
||||
|
||||
case WithoutProperty.EXIF:
|
||||
relations = {
|
||||
exifInfo: true,
|
||||
};
|
||||
where = {
|
||||
isVisible: true,
|
||||
exifInfo: {
|
||||
assetId: IsNull(),
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
||||
case WithoutProperty.CLIP_ENCODING:
|
||||
relations = {
|
||||
smartInfo: true,
|
||||
};
|
||||
where = {
|
||||
isVisible: true,
|
||||
resizePath: Not(IsNull()),
|
||||
smartInfo: {
|
||||
clipEmbedding: IsNull(),
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
||||
case WithoutProperty.OBJECT_TAGS:
|
||||
relations = {
|
||||
smartInfo: true,
|
||||
};
|
||||
where = {
|
||||
resizePath: Not(IsNull()),
|
||||
isVisible: true,
|
||||
smartInfo: {
|
||||
tags: IsNull(),
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
||||
case WithoutProperty.FACES:
|
||||
relations = {
|
||||
faces: true,
|
||||
};
|
||||
where = {
|
||||
resizePath: Not(IsNull()),
|
||||
isVisible: true,
|
||||
faces: {
|
||||
assetId: IsNull(),
|
||||
personId: IsNull(),
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
||||
case WithoutProperty.SIDECAR:
|
||||
where = [
|
||||
{ sidecarPath: IsNull(), isVisible: true },
|
||||
{ sidecarPath: '', isVisible: true },
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Invalid getWithout property: ${property}`);
|
||||
}
|
||||
|
||||
return paginate(this.repository, pagination, {
|
||||
relations,
|
||||
where,
|
||||
order: {
|
||||
// Ensures correct order when paginating
|
||||
createdAt: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getWith(pagination: PaginationOptions, property: WithProperty): Paginated<AssetEntity> {
|
||||
let where: FindOptionsWhere<AssetEntity> | FindOptionsWhere<AssetEntity>[] = {};
|
||||
|
||||
switch (property) {
|
||||
case WithProperty.SIDECAR:
|
||||
where = [{ sidecarPath: Not(IsNull()), isVisible: true }];
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Invalid getWith property: ${property}`);
|
||||
}
|
||||
|
||||
return paginate(this.repository, pagination, {
|
||||
where,
|
||||
order: {
|
||||
// Ensures correct order when paginating
|
||||
createdAt: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getFirstAssetForAlbumId(albumId: string): Promise<AssetEntity | null> {
|
||||
return this.repository.findOne({
|
||||
where: { albums: { id: albumId } },
|
||||
order: { fileCreatedAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async getMapMarkers(ownerId: string, options: MapMarkerSearchOptions = {}): Promise<MapMarker[]> {
|
||||
const { isFavorite, fileCreatedAfter, fileCreatedBefore } = options;
|
||||
|
||||
const assets = await this.repository.find({
|
||||
select: {
|
||||
id: true,
|
||||
exifInfo: {
|
||||
latitude: true,
|
||||
longitude: true,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
ownerId,
|
||||
isVisible: true,
|
||||
isArchived: false,
|
||||
exifInfo: {
|
||||
latitude: Not(IsNull()),
|
||||
longitude: Not(IsNull()),
|
||||
},
|
||||
isFavorite,
|
||||
fileCreatedAt: OptionalBetween(fileCreatedAfter, fileCreatedBefore),
|
||||
},
|
||||
relations: {
|
||||
exifInfo: true,
|
||||
},
|
||||
order: {
|
||||
fileCreatedAt: 'DESC',
|
||||
},
|
||||
});
|
||||
|
||||
return assets.map((asset) => ({
|
||||
id: asset.id,
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
|
||||
lat: asset.exifInfo!.latitude!,
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
|
||||
lon: asset.exifInfo!.longitude!,
|
||||
}));
|
||||
}
|
||||
}
|
||||
12
server/src/infra/repositories/communication.repository.ts
Normal file
12
server/src/infra/repositories/communication.repository.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { CommunicationEvent } from '@app/domain';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CommunicationGateway } from '../communication.gateway';
|
||||
|
||||
@Injectable()
|
||||
export class CommunicationRepository {
|
||||
constructor(private ws: CommunicationGateway) {}
|
||||
|
||||
send(event: CommunicationEvent, userId: string, data: any) {
|
||||
this.ws.server.to(userId).emit(event, JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
16
server/src/infra/repositories/crypto.repository.ts
Normal file
16
server/src/infra/repositories/crypto.repository.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ICryptoRepository } from '@app/domain';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { compareSync, hash } from 'bcrypt';
|
||||
import { randomBytes, createHash } from 'crypto';
|
||||
|
||||
@Injectable()
|
||||
export class CryptoRepository implements ICryptoRepository {
|
||||
randomBytes = randomBytes;
|
||||
|
||||
hashBcrypt = hash;
|
||||
compareBcrypt = compareSync;
|
||||
|
||||
hashSha256(value: string) {
|
||||
return createHash('sha256').update(value).digest('base64');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user