refactor(server): flatten infra folders (#2120)

* refactor: flatten infra folders

* fix: database migrations

* fix: test related import

* fix: github actions workflow

* chore: rename schemas to typesense-schemas
This commit is contained in:
Jason Rasmussen
2023-03-30 15:38:55 -04:00
committed by GitHub
parent 468e620372
commit 34d300d1da
176 changed files with 185 additions and 176 deletions

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!: string;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt!: string;
@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)
@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)
user?: UserEntity;
@Column()
userId!: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt!: string;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt!: string;
}

View File

@@ -0,0 +1,113 @@
import {
Column,
CreateDateColumn,
Entity,
Index,
JoinColumn,
JoinTable,
ManyToMany,
ManyToOne,
OneToOne,
PrimaryGeneratedColumn,
Unique,
UpdateDateColumn,
} from 'typeorm';
import { AlbumEntity } from './album.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!: string;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt!: string;
@Column({ type: 'timestamptz' })
fileCreatedAt!: string;
@Column({ type: 'timestamptz' })
fileModifiedAt!: string;
@Column({ type: 'boolean', default: false })
isFavorite!: boolean;
@Column({ type: 'varchar', nullable: true })
mimeType!: string | null;
@Column({ type: 'bytea', nullable: true, select: false })
@Index({ where: `'checksum' IS NOT NULL` }) // avoid null index
checksum?: Buffer | null; // 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;
@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)
albums?: AlbumEntity[];
}
export enum AssetType {
IMAGE = 'IMAGE',
VIDEO = 'VIDEO',
AUDIO = 'AUDIO',
OTHER = 'OTHER',
}

View File

@@ -0,0 +1,32 @@
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';
@Entity('device_info')
@Unique(['userId', 'deviceId'])
export class DeviceInfoEntity {
@PrimaryGeneratedColumn()
id!: number;
@Column()
userId!: string;
@Column()
deviceId!: string;
@Column()
deviceType!: DeviceType;
@Column({ type: 'varchar', nullable: true })
notificationToken!: string | null;
@CreateDateColumn()
createdAt!: string;
@Column({ type: 'bool', default: false })
isAutoBackup!: boolean;
}
export enum DeviceType {
IOS = 'IOS',
ANDROID = 'ANDROID',
WEB = 'WEB',
}

View File

@@ -0,0 +1,100 @@
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: '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 })
imageName!: 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("imageName", '') || ' ' ||
COALESCE("city", '') || ' ' ||
COALESCE("state", '') || ' ' ||
COALESCE("country", ''))`,
})
exifTextSearchableColumn!: string;
}

View File

@@ -0,0 +1,33 @@
import { AlbumEntity } from './album.entity';
import { APIKeyEntity } from './api-key.entity';
import { AssetEntity } from './asset.entity';
import { DeviceInfoEntity } from './device-info.entity';
import { SharedLinkEntity } from './shared-link.entity';
import { SmartInfoEntity } from './smart-info.entity';
import { SystemConfigEntity } from './system-config.entity';
import { UserTokenEntity } from './user-token.entity';
import { UserEntity } from './user.entity';
export * from './album.entity';
export * from './api-key.entity';
export * from './asset.entity';
export * from './device-info.entity';
export * from './exif.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 = [
AssetEntity,
AlbumEntity,
APIKeyEntity,
DeviceInfoEntity,
UserEntity,
SharedLinkEntity,
SmartInfoEntity,
SystemConfigEntity,
UserTokenEntity,
];

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!: string;
@Column({ type: 'timestamptz', nullable: true })
expiresAt!: string | 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)
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,69 @@
import { Column, Entity, PrimaryColumn } from 'typeorm';
@Entity('system_config')
export class SystemConfigEntity<T = string | boolean> {
@PrimaryColumn()
key!: SystemConfigKey;
@Column({ type: 'varchar', nullable: true, transformer: { to: JSON.stringify, from: JSON.parse } })
value!: T;
}
export type SystemConfigValue = any;
// dot notation matches path in `SystemConfig`
export enum SystemConfigKey {
FFMPEG_CRF = 'ffmpeg.crf',
FFMPEG_PRESET = 'ffmpeg.preset',
FFMPEG_TARGET_VIDEO_CODEC = 'ffmpeg.targetVideoCodec',
FFMPEG_TARGET_AUDIO_CODEC = 'ffmpeg.targetAudioCodec',
FFMPEG_TARGET_SCALING = 'ffmpeg.targetScaling',
FFMPEG_TRANSCODE = 'ffmpeg.transcode',
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',
}
export interface SystemConfig {
ffmpeg: {
crf: string;
preset: string;
targetVideoCodec: string;
targetAudioCodec: string;
targetScaling: string;
transcode: TranscodePreset;
};
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;
@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,20 @@
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;
@ManyToOne(() => UserEntity)
user!: UserEntity;
@CreateDateColumn({ type: 'timestamptz' })
createdAt!: string;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt!: string;
}

View File

@@ -0,0 +1,56 @@
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({ default: '', select: false })
password?: string;
@Column({ default: '' })
oauthId!: string;
@Column({ default: '' })
profileImagePath!: string;
@Column({ default: true })
shouldChangePassword!: boolean;
@CreateDateColumn()
createdAt!: string;
@DeleteDateColumn()
deletedAt?: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt!: string;
@OneToMany(() => TagEntity, (tag) => tag.user)
tags!: TagEntity[];
@OneToMany(() => AssetEntity, (asset) => asset.owner)
assets!: AssetEntity[];
}