add ability to make image expire
This commit is contained in:
parent
08af514758
commit
e0e804d27d
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { TypeOrmOptionsFactory } from '@nestjs/typeorm';
|
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';
|
||||||
import { ParseInt, ParseString } from 'picsur-shared/dist/util/parse-simple';
|
import { ParseInt, ParseString } from 'picsur-shared/dist/util/parse-simple';
|
||||||
import { EntityList } from '../../database/entities';
|
import { EntityList } from '../../database/entities';
|
||||||
import { MigrationList } from '../../database/migrations';
|
import { MigrationList } from '../../database/migrations';
|
||||||
|
@ -52,19 +52,21 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory {
|
||||||
const varOptions = this.getTypeOrmServerOptions();
|
const varOptions = this.getTypeOrmServerOptions();
|
||||||
return {
|
return {
|
||||||
type: 'postgres' as 'postgres',
|
type: 'postgres' as 'postgres',
|
||||||
synchronize: false, //!this.hostService.isProduction(),
|
synchronize: !this.hostService.isProduction(),
|
||||||
|
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
|
|
||||||
entities: EntityList,
|
entities: EntityList,
|
||||||
migrations: MigrationList,
|
migrations: MigrationList,
|
||||||
|
|
||||||
|
useUTC: true,
|
||||||
|
|
||||||
cli: {
|
cli: {
|
||||||
migrationsDir: 'src/database/migrations',
|
migrationsDir: 'src/database/migrations',
|
||||||
entitiesDir: 'src/database/entities',
|
entitiesDir: 'src/database/entities',
|
||||||
},
|
},
|
||||||
|
|
||||||
...varOptions,
|
...varOptions,
|
||||||
};
|
} as TypeOrmModuleOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,13 +41,13 @@ export class EApiKeyBackend<
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'timestamp',
|
type: 'timestamptz',
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
created: Date;
|
created: Date;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'timestamp',
|
type: 'timestamptz',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
last_used: Date;
|
last_used: Date;
|
||||||
|
|
|
@ -36,7 +36,11 @@ export class EImageDerivativeBackend {
|
||||||
@Column({ nullable: false })
|
@Column({ nullable: false })
|
||||||
filetype: string;
|
filetype: string;
|
||||||
|
|
||||||
@Column({ type: 'timestamp', name: 'last_read', nullable: false })
|
@Column({
|
||||||
|
type: 'timestamptz',
|
||||||
|
name: 'last_read',
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
last_read: Date;
|
last_read: Date;
|
||||||
|
|
||||||
// Binary data
|
// Binary data
|
||||||
|
|
|
@ -15,7 +15,7 @@ export class EImageBackend implements EImage {
|
||||||
user_id: string;
|
user_id: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'timestamp',
|
type: 'timestamptz',
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
created: Date;
|
created: Date;
|
||||||
|
@ -27,7 +27,7 @@ export class EImageBackend implements EImage {
|
||||||
file_name: string;
|
file_name: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'timestamp',
|
type: "timestamptz",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
expires_at: Date | null;
|
expires_at: Date | null;
|
||||||
|
|
22
backend/src/database/migrations/1662728275448-V_0_4_0_d.ts
Normal file
22
backend/src/database/migrations/1662728275448-V_0_4_0_d.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class V040D1662728275448 implements MigrationInterface {
|
||||||
|
name = 'V040D1662728275448'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "e_api_key_backend" ALTER COLUMN "created" SET DATA TYPE TIMESTAMP WITH TIME ZONE, ALTER COLUMN "created" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "e_api_key_backend" ALTER COLUMN "last_used" SET DATA TYPE TIMESTAMP WITH TIME ZONE`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "e_image_backend" ALTER COLUMN "created" SET DATA TYPE TIMESTAMP WITH TIME ZONE, ALTER COLUMN "created" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "e_image_backend" ALTER COLUMN "expires_at" SET DATA TYPE TIMESTAMP WITH TIME ZONE`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "e_image_derivative_backend" ALTER COLUMN "last_read" SET DATA TYPE TIMESTAMP WITH TIME ZONE, ALTER COLUMN "last_read" SET NOT NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "e_image_derivative_backend" ALTER COLUMN "last_read" SET DATA TYPE TIMESTAMP, ALTER COLUMN "last_read" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "e_image_backend" ALTER COLUMN "expires_at" SET DATA TYPE TIMESTAMP`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "e_image_backend" ALTER COLUMN "created" SET DATA TYPE TIMESTAMP, ALTER COLUMN "created" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "e_api_key_backend" ALTER COLUMN "last_used" SET DATA TYPE TIMESTAMP`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "e_api_key_backend" ALTER COLUMN "created" SET DATA TYPE TIMESTAMP, ALTER COLUMN "created" SET NOT NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import { V032A1662029904716 } from './1662029904716-V_0_3_2_a';
|
||||||
import { V040A1662314197741 } from './1662314197741-V_0_4_0_a';
|
import { V040A1662314197741 } from './1662314197741-V_0_4_0_a';
|
||||||
import { V040B1662485374471 } from './1662485374471-V_0_4_0_b';
|
import { V040B1662485374471 } from './1662485374471-V_0_4_0_b';
|
||||||
import { V040C1662535484200 } from './1662535484200-V_0_4_0_c';
|
import { V040C1662535484200 } from './1662535484200-V_0_4_0_c';
|
||||||
|
import { V040D1662728275448 } from './1662728275448-V_0_4_0_d';
|
||||||
|
|
||||||
export const MigrationList: Function[] = [
|
export const MigrationList: Function[] = [
|
||||||
V030A1661692206479,
|
V030A1661692206479,
|
||||||
|
@ -10,4 +11,5 @@ export const MigrationList: Function[] = [
|
||||||
V040A1662314197741,
|
V040A1662314197741,
|
||||||
V040B1662485374471,
|
V040B1662485374471,
|
||||||
V040C1662535484200,
|
V040C1662535484200,
|
||||||
|
V040D1662728275448,
|
||||||
];
|
];
|
||||||
|
|
|
@ -17,7 +17,7 @@ async function createDataSource() {
|
||||||
const configFactory = app.get(TypeOrmConfigService);
|
const configFactory = app.get(TypeOrmConfigService);
|
||||||
const config = await configFactory.createTypeOrmOptions();
|
const config = await configFactory.createTypeOrmOptions();
|
||||||
|
|
||||||
return new DataSource(config);
|
return new DataSource(config as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createDataSource();
|
export default createDataSource();
|
||||||
|
|
|
@ -61,7 +61,7 @@ export class ImageManagerModule implements OnModuleInit, OnModuleDestroy {
|
||||||
|
|
||||||
const result = await this.imageFileDB.cleanupDerivatives(after_ms / 1000);
|
const result = await this.imageFileDB.cleanupDerivatives(after_ms / 1000);
|
||||||
if (HasFailed(result)) {
|
if (HasFailed(result)) {
|
||||||
this.logger.warn(`Failed to cleanup derivatives`);
|
this.logger.warn(result.print());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(`Cleaned up ${result} derivatives`);
|
this.logger.log(`Cleaned up ${result} derivatives`);
|
||||||
|
@ -71,7 +71,7 @@ export class ImageManagerModule implements OnModuleInit, OnModuleDestroy {
|
||||||
const cleanedUp = await this.imageDB.cleanupExpired();
|
const cleanedUp = await this.imageDB.cleanupExpired();
|
||||||
|
|
||||||
if (HasFailed(cleanedUp)) {
|
if (HasFailed(cleanedUp)) {
|
||||||
this.logger.warn(`Failed to cleanup expired images`);
|
this.logger.warn(cleanedUp.print());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(`Cleaned up ${cleanedUp} expired images`);
|
this.logger.log(`Cleaned up ${cleanedUp} expired images`);
|
||||||
|
|
|
@ -57,6 +57,11 @@ export class ImageManagerService {
|
||||||
userid: string | undefined,
|
userid: string | undefined,
|
||||||
options: Partial<Pick<EImageBackend, 'file_name' | 'expires_at'>>,
|
options: Partial<Pick<EImageBackend, 'file_name' | 'expires_at'>>,
|
||||||
): AsyncFailable<EImageBackend> {
|
): AsyncFailable<EImageBackend> {
|
||||||
|
if (options.expires_at !== undefined && options.expires_at !== null) {
|
||||||
|
if (options.expires_at < new Date()) {
|
||||||
|
return Fail(FT.UsrValidation, 'Expiration date must be in the future');
|
||||||
|
}
|
||||||
|
}
|
||||||
return await this.imagesService.update(id, userid, options);
|
return await this.imagesService.update(id, userid, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,11 @@
|
||||||
<mat-card-title>{{ image.file_name | truncate }}</mat-card-title>
|
<mat-card-title>{{ image.file_name | truncate }}</mat-card-title>
|
||||||
<mat-card-subtitle>
|
<mat-card-subtitle>
|
||||||
Uploaded {{ image.created | amTimeAgo }}
|
Uploaded {{ image.created | amTimeAgo }}
|
||||||
|
{{
|
||||||
|
image.expires_at === null
|
||||||
|
? ''
|
||||||
|
: '| Expires ' + (image.expires_at | amTimeAgo)
|
||||||
|
}}
|
||||||
</mat-card-subtitle>
|
</mat-card-subtitle>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<picsur-img
|
<picsur-img
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { ImageFileType } from 'picsur-shared/dist/dto/mimes.dto';
|
||||||
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types/failable';
|
import { HasFailed } from 'picsur-shared/dist/types/failable';
|
||||||
import { ImageService } from 'src/app/services/api/image.service';
|
import { ImageService } from 'src/app/services/api/image.service';
|
||||||
|
import { UserService } from 'src/app/services/api/user.service';
|
||||||
import { Logger } from 'src/app/services/logger/logger.service';
|
import { Logger } from 'src/app/services/logger/logger.service';
|
||||||
import { BootstrapService, BSScreenSize } from 'src/app/util/bootstrap.service';
|
import { BootstrapService, BSScreenSize } from 'src/app/util/bootstrap.service';
|
||||||
import { DialogService } from 'src/app/util/dialog-manager/dialog.service';
|
import { DialogService } from 'src/app/util/dialog-manager/dialog.service';
|
||||||
|
@ -27,6 +28,7 @@ export class ImagesComponent implements OnInit {
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
private readonly router: Router,
|
private readonly router: Router,
|
||||||
private readonly bootstrapService: BootstrapService,
|
private readonly bootstrapService: BootstrapService,
|
||||||
|
private readonly userService: UserService,
|
||||||
private readonly imageService: ImageService,
|
private readonly imageService: ImageService,
|
||||||
private readonly errorService: ErrorService,
|
private readonly errorService: ErrorService,
|
||||||
private readonly dialogService: DialogService,
|
private readonly dialogService: DialogService,
|
||||||
|
@ -44,14 +46,7 @@ export class ImagesComponent implements OnInit {
|
||||||
this.page = thispage;
|
this.page = thispage;
|
||||||
|
|
||||||
this.subscribeMobile();
|
this.subscribeMobile();
|
||||||
|
this.subscribeUser();
|
||||||
const list = await this.imageService.ListMyImages(24, this.page - 1);
|
|
||||||
if (HasFailed(list)) {
|
|
||||||
return this.logger.error(list.getReason());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pages = list.pages;
|
|
||||||
this.images = list.results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoUnsubscribe()
|
@AutoUnsubscribe()
|
||||||
|
@ -67,6 +62,19 @@ export class ImagesComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AutoUnsubscribe()
|
||||||
|
private subscribeUser() {
|
||||||
|
return this.userService.live.subscribe(async () => {
|
||||||
|
const list = await this.imageService.ListMyImages(24, this.page - 1);
|
||||||
|
if (HasFailed(list)) {
|
||||||
|
return this.logger.error(list.getReason());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pages = list.pages;
|
||||||
|
this.images = list.results;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getThumbnailUrl(image: EImage) {
|
getThumbnailUrl(image: EImage) {
|
||||||
return (
|
return (
|
||||||
this.imageService.GetImageURL(image.id, ImageFileType.QOI) +
|
this.imageService.GetImageURL(image.id, ImageFileType.QOI) +
|
||||||
|
|
|
@ -91,6 +91,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-buttons">
|
<div class="dialog-buttons">
|
||||||
<button mat-stroked-button (click)="close()">Close</button>
|
<button mat-flat-button (click)="close()">CLOSE</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<div class="dialog-text">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<h1>Edit Image Properties</h1>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<mat-form-field appearance="outline" color="accent">
|
||||||
|
<mat-label>Title</mat-label>
|
||||||
|
<input matInput type="text" [(ngModel)]="image.file_name" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Expires After</mat-label>
|
||||||
|
<mat-select [(value)]="expiresAfter" >
|
||||||
|
<mat-option *ngFor="let option of ExpireOptions" [value]="option[1]">
|
||||||
|
{{ option[0] }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-buttons">
|
||||||
|
<button mat-flat-button (click)="close()">CANCEL</button>
|
||||||
|
<button mat-raised-button color="accent" (click)="save()">SAVE</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,3 @@
|
||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
||||||
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
|
import { ImageService } from 'src/app/services/api/image.service';
|
||||||
|
import { Logger } from 'src/app/services/logger/logger.service';
|
||||||
|
import { ErrorService } from 'src/app/util/error-manager/error.service';
|
||||||
|
|
||||||
|
export interface EditDialogData {
|
||||||
|
image: EImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'edit-dialog',
|
||||||
|
templateUrl: './edit-dialog.component.html',
|
||||||
|
styleUrls: ['./edit-dialog.component.scss'],
|
||||||
|
})
|
||||||
|
export class EditDialogComponent implements OnInit {
|
||||||
|
private readonly logger = new Logger(EditDialogComponent.name);
|
||||||
|
|
||||||
|
public readonly ExpireOptions: Array<[string, null | number]> = [
|
||||||
|
['Never', 0],
|
||||||
|
['5 Minutes', 5 * 60],
|
||||||
|
['10 Minutes', 10 * 60],
|
||||||
|
['30 Minutes', 15 * 60],
|
||||||
|
['1 Hour', 60 * 60],
|
||||||
|
['6 Hours', 2 * 60 * 60],
|
||||||
|
['12 Hours', 12 * 60 * 60],
|
||||||
|
['1 Day', 24 * 60 * 60],
|
||||||
|
['1 Week', 7 * 24 * 60 * 60],
|
||||||
|
['1 Month', 30 * 24 * 60 * 60],
|
||||||
|
];
|
||||||
|
|
||||||
|
public expiresAfter: number = 0;
|
||||||
|
public image: EImage;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly dialogRef: MatDialogRef<EditDialogComponent>,
|
||||||
|
private readonly imageService: ImageService,
|
||||||
|
private readonly errorService: ErrorService,
|
||||||
|
@Inject(MAT_DIALOG_DATA) data: EditDialogData,
|
||||||
|
) {
|
||||||
|
if (!data.image) {
|
||||||
|
throw new Error('imageID is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.image = data.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
console.log(this.image);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.dialogRef.close(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
const result = await this.imageService.UpdateImage(this.image.id, {
|
||||||
|
file_name: this.image.file_name,
|
||||||
|
expires_at:
|
||||||
|
this.expiresAfter === 0
|
||||||
|
? null
|
||||||
|
: new Date(Date.now() + this.expiresAfter * 1000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (HasFailed(result)) {
|
||||||
|
this.errorService.showFailure(result, this.logger);
|
||||||
|
return this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.errorService.success('Image successfully updated');
|
||||||
|
|
||||||
|
this.dialogRef.close(result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12" *ngIf="image !== null">
|
<div class="col-12" *ngIf="image !== null">
|
||||||
<h3>Uploaded {{ image.created | amTimeAgo }}</h3>
|
<h3>
|
||||||
|
Uploaded {{ image.created | amTimeAgo }}
|
||||||
|
{{
|
||||||
|
image.expires_at === null
|
||||||
|
? ''
|
||||||
|
: '| Expires ' + (image.expires_at | amTimeAgo)
|
||||||
|
}}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 py-3">
|
<div class="col-12 py-3">
|
||||||
|
@ -66,7 +73,15 @@
|
||||||
<mat-icon fontSet="material-icons-outlined"> share </mat-icon>
|
<mat-icon fontSet="material-icons-outlined"> share </mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
*ngIf="canDelete"
|
*ngIf="canManage"
|
||||||
|
mat-mini-fab
|
||||||
|
matTooltip="Edit image"
|
||||||
|
(click)="editImage()"
|
||||||
|
>
|
||||||
|
<mat-icon fontSet="material-icons-outlined"> edit </mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
*ngIf="canManage"
|
||||||
mat-mini-fab
|
mat-mini-fab
|
||||||
matTooltip="Delete image"
|
matTooltip="Delete image"
|
||||||
(click)="deleteImage()"
|
(click)="deleteImage()"
|
||||||
|
|
|
@ -28,6 +28,10 @@ import {
|
||||||
CustomizeDialogComponent,
|
CustomizeDialogComponent,
|
||||||
CustomizeDialogData,
|
CustomizeDialogData,
|
||||||
} from './customize-dialog/customize-dialog.component';
|
} from './customize-dialog/customize-dialog.component';
|
||||||
|
import {
|
||||||
|
EditDialogComponent,
|
||||||
|
EditDialogData,
|
||||||
|
} from './edit-dialog/edit-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './view.component.html',
|
templateUrl: './view.component.html',
|
||||||
|
@ -70,7 +74,7 @@ export class ViewComponent implements OnInit {
|
||||||
public image: EImage | null = null;
|
public image: EImage | null = null;
|
||||||
public imageUser: EUser | null = null;
|
public imageUser: EUser | null = null;
|
||||||
|
|
||||||
public canDelete: boolean = false;
|
public canManage: boolean = false;
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.subscribePermissions();
|
this.subscribePermissions();
|
||||||
|
@ -194,6 +198,26 @@ export class ViewComponent implements OnInit {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async editImage() {
|
||||||
|
if (this.image === null) return;
|
||||||
|
|
||||||
|
const options: EditDialogData = {
|
||||||
|
image: { ...this.image },
|
||||||
|
};
|
||||||
|
|
||||||
|
const res: EImage | null = await this.dialogService.showCustomDialog(
|
||||||
|
EditDialogComponent,
|
||||||
|
options,
|
||||||
|
{
|
||||||
|
dismissable: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res !== null) {
|
||||||
|
this.image = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@AutoUnsubscribe()
|
@AutoUnsubscribe()
|
||||||
private subscribePermissions() {
|
private subscribePermissions() {
|
||||||
return this.permissionService.live.subscribe(
|
return this.permissionService.live.subscribe(
|
||||||
|
@ -204,21 +228,21 @@ export class ViewComponent implements OnInit {
|
||||||
private updatePermissions() {
|
private updatePermissions() {
|
||||||
const permissions = this.permissionService.snapshot;
|
const permissions = this.permissionService.snapshot;
|
||||||
if (permissions.includes(Permission.ImageAdmin)) {
|
if (permissions.includes(Permission.ImageAdmin)) {
|
||||||
this.canDelete = true;
|
this.canManage = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.imageUser === null) return;
|
if (this.imageUser === null) return;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
permissions.includes(Permission.ImageUpload) &&
|
permissions.includes(Permission.ImageManage) &&
|
||||||
this.imageUser.id === this.userService.snapshot?.id
|
this.imageUser.id === this.userService.snapshot?.id
|
||||||
) {
|
) {
|
||||||
this.canDelete = true;
|
this.canManage = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canDelete = false;
|
this.canManage = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateFormatOptions() {
|
private updateFormatOptions() {
|
||||||
|
|
|
@ -17,10 +17,12 @@ import { PipesModule } from 'src/app/pipes/pipes.module';
|
||||||
import { DownloadManagerModule } from 'src/app/util/download-manager/dialog-manager.module';
|
import { DownloadManagerModule } from 'src/app/util/download-manager/dialog-manager.module';
|
||||||
import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module';
|
import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module';
|
||||||
import { CustomizeDialogComponent } from './customize-dialog/customize-dialog.component';
|
import { CustomizeDialogComponent } from './customize-dialog/customize-dialog.component';
|
||||||
|
import { EditDialogComponent } from './edit-dialog/edit-dialog.component';
|
||||||
import { ViewComponent } from './view.component';
|
import { ViewComponent } from './view.component';
|
||||||
import { ViewRoutingModule } from './view.routing.module';
|
import { ViewRoutingModule } from './view.routing.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [ViewComponent, CustomizeDialogComponent],
|
declarations: [ViewComponent, CustomizeDialogComponent, EditDialogComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
ErrorManagerModule,
|
ErrorManagerModule,
|
||||||
|
|
|
@ -4,6 +4,8 @@ import {
|
||||||
ImageDeleteResponse,
|
ImageDeleteResponse,
|
||||||
ImageListRequest,
|
ImageListRequest,
|
||||||
ImageListResponse,
|
ImageListResponse,
|
||||||
|
ImageUpdateRequest,
|
||||||
|
ImageUpdateResponse,
|
||||||
ImageUploadResponse,
|
ImageUploadResponse,
|
||||||
} from 'picsur-shared/dist/dto/api/image-manage.dto';
|
} from 'picsur-shared/dist/dto/api/image-manage.dto';
|
||||||
import {
|
import {
|
||||||
|
@ -79,6 +81,21 @@ export class ImageService {
|
||||||
return await this.ListAllImages(count, page, userID);
|
return await this.ListAllImages(count, page, userID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async UpdateImage(
|
||||||
|
id: string,
|
||||||
|
settings: Partial<Pick<EImage, 'file_name' | 'expires_at'>>,
|
||||||
|
): AsyncFailable<EImage> {
|
||||||
|
return await this.api.post(
|
||||||
|
ImageUpdateRequest,
|
||||||
|
ImageUpdateResponse,
|
||||||
|
'/api/image/update',
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
...settings,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public async DeleteImages(
|
public async DeleteImages(
|
||||||
images: string[],
|
images: string[],
|
||||||
): AsyncFailable<ImageDeleteResponse> {
|
): AsyncFailable<ImageDeleteResponse> {
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { IsEntityID } from '../validators/entity-id.validator';
|
import { IsEntityID } from '../validators/entity-id.validator';
|
||||||
|
import { IsPosInt } from '../validators/positive-int.validator';
|
||||||
|
|
||||||
|
const MONTH_IN_SECONDS = 60 * 60 * 24 * 30;
|
||||||
|
const FIVE_MIN_IN_SECONDS = 60 * 5;
|
||||||
|
|
||||||
export const EImageSchema = z.object({
|
export const EImageSchema = z.object({
|
||||||
id: IsEntityID(),
|
id: IsEntityID(),
|
||||||
user_id: IsEntityID(),
|
user_id: IsEntityID(),
|
||||||
created: z.preprocess((data: any) => new Date(data), z.date()),
|
created: z.preprocess((data: any) => new Date(data), z.date()),
|
||||||
file_name: z.string(),
|
file_name: z.string(),
|
||||||
expires_at: z.preprocess((data: any) => new Date(data), z.date()).nullable(),
|
expires_at: z
|
||||||
|
.preprocess((data: any) => new Date(data), z.date())
|
||||||
|
.nullable(),
|
||||||
});
|
});
|
||||||
export type EImage = z.infer<typeof EImageSchema>;
|
export type EImage = z.infer<typeof EImageSchema>;
|
||||||
|
|
Loading…
Reference in a new issue