fix: use local time for time buckets and improve memories (#4072)

* fix: timezone bucket timezones

* chore: open api

* fix: interpret local time in utc

* fix: tests

* fix: refactor memory lane

* fix(web): use local date in memory viewer

* chore: set localDateTime non-null

* fix: filter out memories from the current year

* wip: move localDateTime to asset

* fix: correct sorting from db

* fix: migration

* fix: web typo

* fix: formatting

* fix: e2e

* chore: localDateTime is non-null

* chore: more non-nulliness

* fix: asset stub

* fix: tests

* fix: use extract and index for day of year

* fix: don't show memories before today

* fix: cleanup

* fix: tests

* fix: only use localtime for tz

* fix: display memories in client timezone

* fix: tests

* fix: svelte tests

* fix: bugs

* chore: open api

---------

Co-authored-by: Jonathan Jogenfors <jonathan@jogenfors.se>
This commit is contained in:
Jason Rasmussen
2023-10-04 18:11:11 -04:00
committed by GitHub
parent 126dd45751
commit 192e950567
32 changed files with 337 additions and 147 deletions

View File

@@ -28,6 +28,8 @@ export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_library_checksum';
@Index(ASSET_CHECKSUM_CONSTRAINT, ['owner', 'library', 'checksum'], {
unique: true,
})
@Index('IDX_day_of_month', { synchronize: false })
@Index('IDX_month', { synchronize: false })
// For all assets, each originalpath must be unique per user and library
export class AssetEntity {
@PrimaryGeneratedColumn('uuid')
@@ -78,6 +80,9 @@ export class AssetEntity {
@Column({ type: 'timestamptz' })
fileCreatedAt!: Date;
@Column({ type: 'timestamp' })
localDateTime!: Date;
@Column({ type: 'timestamptz' })
fileModifiedAt!: Date;

View File

@@ -0,0 +1,30 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddLocalDateTime1694525143117 implements MigrationInterface {
name = 'AddLocalDateTime1694525143117';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assets" ADD "localDateTime" TIMESTAMP`);
await queryRunner.query(`
update "assets"
set "localDateTime" = "fileCreatedAt"`);
await queryRunner.query(`
update "assets"
set "localDateTime" = "fileCreatedAt" at TIME ZONE "exif"."timeZone"
from "exif"
where
"exif"."assetId" = "assets"."id" and
"exif"."timeZone" is not null`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "localDateTime" SET NOT NULL`);
await queryRunner.query(`CREATE INDEX "IDX_day_of_month" ON assets (EXTRACT(DAY FROM "localDateTime"))`);
await queryRunner.query(`CREATE INDEX "IDX_month" ON assets (EXTRACT(MONTH FROM "localDateTime"))`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "localDateTime"`);
await queryRunner.query(`DROP INDEX "IDX_day_of_month"`);
await queryRunner.query(`DROP INDEX "IDX_month"`);
}
}

View File

@@ -1,4 +1,5 @@
import {
AssetCreate,
AssetSearchOptions,
AssetStats,
AssetStatsOptions,
@@ -6,6 +7,7 @@ import {
LivePhotoSearchOptions,
MapMarker,
MapMarkerSearchOptions,
MonthDay,
Paginated,
PaginationOptions,
TimeBucketItem,
@@ -38,9 +40,7 @@ export class AssetRepository implements IAssetRepository {
await this.exifRepository.upsert(exif, { conflictPaths: ['assetId'] });
}
create(
asset: Omit<AssetEntity, 'id' | 'createdAt' | 'updatedAt' | 'ownerId' | 'livePhotoVideoId'>,
): Promise<AssetEntity> {
create(asset: AssetCreate): Promise<AssetEntity> {
return this.repository.save(asset);
}
@@ -78,6 +78,26 @@ export class AssetRepository implements IAssetRepository {
});
}
getByDayOfYear(ownerId: string, { day, month }: MonthDay): Promise<AssetEntity[]> {
return this.repository
.createQueryBuilder('entity')
.where(
`entity.ownerId = :ownerId
AND entity.isVisible = true
AND entity.isArchived = false
AND entity.resizePath IS NOT NULL
AND EXTRACT(DAY FROM entity.localDateTime) = :day
AND EXTRACT(MONTH FROM entity.localDateTime) = :month`,
{
ownerId,
day,
month,
},
)
.orderBy('entity.localDateTime', 'DESC')
.getMany();
}
getByIds(ids: string[]): Promise<AssetEntity[]> {
return this.repository.find({
where: { id: In(ids) },
@@ -454,8 +474,9 @@ export class AssetRepository implements IAssetRepository {
getByTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]> {
const truncateValue = truncateMap[options.size];
return this.getBuilder(options)
.andWhere(`date_trunc('${truncateValue}', "fileCreatedAt") = :timeBucket`, { timeBucket })
.orderBy('asset.fileCreatedAt', 'DESC')
.andWhere(`date_trunc('${truncateValue}', "localDateTime") = :timeBucket`, { timeBucket })
.orderBy(`date_trunc('day', "localDateTime")`, 'DESC')
.addOrderBy('asset.fileCreatedAt', 'DESC')
.getMany();
}