refactor(server)*: tsconfigs (#2689)

* refactor(server): tsconfigs

* chore: dummy commit

* fix: start.sh

* chore: restore original entry scripts
This commit is contained in:
Jason Rasmussen
2023-06-08 11:01:07 -04:00
committed by GitHub
parent a2130aa6c5
commit 8ebac41318
465 changed files with 209 additions and 332 deletions

View 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();
}
}
}

View 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);

View 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[];
}

View 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;
}

View 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;
}

View 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',
}

View 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;
}

View 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,
];

View 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;
}

View 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[];
}

View 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',
}

View 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;
}

View 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;
};
}

View 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',
}

View 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;
}

View 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[];
}

View File

@@ -0,0 +1,4 @@
export * from './database.config';
export * from './infra.config';
export * from './infra.module';
export * from './redis-io.adapter';

View 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();

View 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 {}

View 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`);
}
}

View File

@@ -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`);
}
}

View File

@@ -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`);
}
}

View 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`);
}
}

View File

@@ -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;
`);
}
}

View File

@@ -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;
`);
}
}

View File

@@ -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;
`);
}
}

View File

@@ -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;
`);
}
}

View File

@@ -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;
`);
}
}

View File

@@ -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;
`);
}
}

View File

@@ -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;
`);
}
}

View File

@@ -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";
`);
}
}

View File

@@ -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";
`);
}
}

View File

@@ -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";
`);
}
}

View File

@@ -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;
`);
}
}

View File

@@ -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";
`);
}
}

View File

@@ -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);`,
);
}
}

View File

@@ -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);
`);
}
}

View File

@@ -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`,
);
}
}

View File

@@ -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`);
}
}

View 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"`);
}
}

View File

@@ -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;
`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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")`,
);
}
}

View File

@@ -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`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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`);
}
}

View 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"`);
}
}

View 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"`);
}
}

View File

@@ -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
}
}

View File

@@ -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"`);
}
}

View 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 ''`);
}
}

View 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"`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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
}
}

View File

@@ -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"`);
}
}

View File

@@ -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`);
}
}

View 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
}
}

View File

@@ -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`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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`);
}
}

View File

@@ -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")`);
}
}

View File

@@ -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`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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") `);
}
}

View File

@@ -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`);
}
}

View File

@@ -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") `);
}
}

View 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"`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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
`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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'`);
}
}

View File

@@ -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`,
);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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")`);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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`);
}
}

View File

@@ -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`,
);
}
}

View File

@@ -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"`);
}
}

View File

@@ -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")
);
`);
}
}

View File

@@ -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"`);
}
}

View 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"`);
}
}

View 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"`);
}
}

View File

@@ -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)`,
);
}
}

View 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"`);
}
}

View File

@@ -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`);
}
}

View File

@@ -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`);
}
}

View File

@@ -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`);
}
}

View File

@@ -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
}
}

View 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;
}
}

View 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,
},
},
}))
);
}
}

View 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,
},
});
}
}

View 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' } });
}
}

View 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!,
}));
}
}

View 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));
}
}

View 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