mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-27 15:07:45 +00:00
feat(server): add memories statistics resource (#19035)
This commit is contained in:
parent
16745e77d4
commit
e88bd74fd2
12 changed files with 176 additions and 17 deletions
BIN
mobile/openapi/README.md
generated
BIN
mobile/openapi/README.md
generated
Binary file not shown.
BIN
mobile/openapi/lib/api.dart
generated
BIN
mobile/openapi/lib/api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/memories_api.dart
generated
BIN
mobile/openapi/lib/api/memories_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api_client.dart
generated
BIN
mobile/openapi/lib/api_client.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/memory_statistics_response_dto.dart
generated
Normal file
BIN
mobile/openapi/lib/model/memory_statistics_response_dto.dart
generated
Normal file
Binary file not shown.
|
|
@ -3599,6 +3599,72 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/memories/statistics": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "memoriesStatistics",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "for",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isSaved",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "isTrashed",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "type",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/MemoryType"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/MemoryStatisticsResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cookie": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"api_key": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Memories"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/memories/{id}": {
|
"/memories/{id}": {
|
||||||
"delete": {
|
"delete": {
|
||||||
"operationId": "deleteMemory",
|
"operationId": "deleteMemory",
|
||||||
|
|
@ -10827,6 +10893,17 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"MemoryStatisticsResponseDto": {
|
||||||
|
"properties": {
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"total"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"MemoryType": {
|
"MemoryType": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"on_this_day"
|
"on_this_day"
|
||||||
|
|
|
||||||
|
|
@ -742,6 +742,9 @@ export type MemoryCreateDto = {
|
||||||
seenAt?: string;
|
seenAt?: string;
|
||||||
"type": MemoryType;
|
"type": MemoryType;
|
||||||
};
|
};
|
||||||
|
export type MemoryStatisticsResponseDto = {
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
export type MemoryUpdateDto = {
|
export type MemoryUpdateDto = {
|
||||||
isSaved?: boolean;
|
isSaved?: boolean;
|
||||||
memoryAt?: string;
|
memoryAt?: string;
|
||||||
|
|
@ -2509,6 +2512,24 @@ export function createMemory({ memoryCreateDto }: {
|
||||||
body: memoryCreateDto
|
body: memoryCreateDto
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
export function memoriesStatistics({ $for, isSaved, isTrashed, $type }: {
|
||||||
|
$for?: string;
|
||||||
|
isSaved?: boolean;
|
||||||
|
isTrashed?: boolean;
|
||||||
|
$type?: MemoryType;
|
||||||
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
|
status: 200;
|
||||||
|
data: MemoryStatisticsResponseDto;
|
||||||
|
}>(`/memories/statistics${QS.query(QS.explode({
|
||||||
|
"for": $for,
|
||||||
|
isSaved,
|
||||||
|
isTrashed,
|
||||||
|
"type": $type
|
||||||
|
}))}`, {
|
||||||
|
...opts
|
||||||
|
}));
|
||||||
|
}
|
||||||
export function deleteMemory({ id }: {
|
export function deleteMemory({ id }: {
|
||||||
id: string;
|
id: string;
|
||||||
}, opts?: Oazapfts.RequestOpts) {
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,13 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put,
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { MemoryCreateDto, MemoryResponseDto, MemorySearchDto, MemoryUpdateDto } from 'src/dtos/memory.dto';
|
import {
|
||||||
|
MemoryCreateDto,
|
||||||
|
MemoryResponseDto,
|
||||||
|
MemorySearchDto,
|
||||||
|
MemoryStatisticsResponseDto,
|
||||||
|
MemoryUpdateDto,
|
||||||
|
} from 'src/dtos/memory.dto';
|
||||||
import { Permission } from 'src/enum';
|
import { Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { MemoryService } from 'src/services/memory.service';
|
import { MemoryService } from 'src/services/memory.service';
|
||||||
|
|
@ -25,6 +31,12 @@ export class MemoryController {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('statistics')
|
||||||
|
@Authenticated({ permission: Permission.MEMORY_READ })
|
||||||
|
memoriesStatistics(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise<MemoryStatisticsResponseDto> {
|
||||||
|
return this.service.statistics(auth, dto);
|
||||||
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated({ permission: Permission.MEMORY_READ })
|
@Authenticated({ permission: Permission.MEMORY_READ })
|
||||||
getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> {
|
getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> {
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,11 @@ export class MemoryCreateDto extends MemoryBaseDto {
|
||||||
assetIds?: string[];
|
assetIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class MemoryStatisticsResponseDto {
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
total!: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class MemoryResponseDto {
|
export class MemoryResponseDto {
|
||||||
id!: string;
|
id!: string;
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,33 @@
|
||||||
-- NOTE: This file is auto generated by ./sql-generator
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- MemoryRepository.statistics
|
||||||
|
select
|
||||||
|
count(*) as "total"
|
||||||
|
from
|
||||||
|
"memories"
|
||||||
|
where
|
||||||
|
"deletedAt" is null
|
||||||
|
and "ownerId" = $1
|
||||||
|
|
||||||
|
-- MemoryRepository.statistics (date filter)
|
||||||
|
select
|
||||||
|
count(*) as "total"
|
||||||
|
from
|
||||||
|
"memories"
|
||||||
|
where
|
||||||
|
(
|
||||||
|
"showAt" is null
|
||||||
|
or "showAt" <= $1
|
||||||
|
)
|
||||||
|
and (
|
||||||
|
"hideAt" is null
|
||||||
|
or "hideAt" >= $2
|
||||||
|
)
|
||||||
|
and "deletedAt" is null
|
||||||
|
and "ownerId" = $3
|
||||||
|
|
||||||
-- MemoryRepository.search
|
-- MemoryRepository.search
|
||||||
select
|
select
|
||||||
"memories".*,
|
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
coalesce(json_agg(agg), '[]')
|
coalesce(json_agg(agg), '[]')
|
||||||
|
|
@ -20,7 +45,8 @@ select
|
||||||
order by
|
order by
|
||||||
"assets"."fileCreatedAt" asc
|
"assets"."fileCreatedAt" asc
|
||||||
) as agg
|
) as agg
|
||||||
) as "assets"
|
) as "assets",
|
||||||
|
"memories".*
|
||||||
from
|
from
|
||||||
"memories"
|
"memories"
|
||||||
where
|
where
|
||||||
|
|
@ -31,7 +57,6 @@ order by
|
||||||
|
|
||||||
-- MemoryRepository.search (date filter)
|
-- MemoryRepository.search (date filter)
|
||||||
select
|
select
|
||||||
"memories".*,
|
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
coalesce(json_agg(agg), '[]')
|
coalesce(json_agg(agg), '[]')
|
||||||
|
|
@ -49,7 +74,8 @@ select
|
||||||
order by
|
order by
|
||||||
"assets"."fileCreatedAt" asc
|
"assets"."fileCreatedAt" asc
|
||||||
) as agg
|
) as agg
|
||||||
) as "assets"
|
) as "assets",
|
||||||
|
"memories".*
|
||||||
from
|
from
|
||||||
"memories"
|
"memories"
|
||||||
where
|
where
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,36 @@ export class MemoryRepository implements IBulkAsset {
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchBuilder(ownerId: string, dto: MemorySearchDto) {
|
||||||
|
return this.db
|
||||||
|
.selectFrom('memories')
|
||||||
|
.$if(dto.isSaved !== undefined, (qb) => qb.where('isSaved', '=', dto.isSaved!))
|
||||||
|
.$if(dto.type !== undefined, (qb) => qb.where('type', '=', dto.type!))
|
||||||
|
.$if(dto.for !== undefined, (qb) =>
|
||||||
|
qb
|
||||||
|
.where((where) => where.or([where('showAt', 'is', null), where('showAt', '<=', dto.for!)]))
|
||||||
|
.where((where) => where.or([where('hideAt', 'is', null), where('hideAt', '>=', dto.for!)])),
|
||||||
|
)
|
||||||
|
.where('deletedAt', dto.isTrashed ? 'is not' : 'is', null)
|
||||||
|
.where('ownerId', '=', ownerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GenerateSql(
|
||||||
|
{ params: [DummyValue.UUID, {}] },
|
||||||
|
{ name: 'date filter', params: [DummyValue.UUID, { for: DummyValue.DATE }] },
|
||||||
|
)
|
||||||
|
statistics(ownerId: string, dto: MemorySearchDto) {
|
||||||
|
return this.searchBuilder(ownerId, dto)
|
||||||
|
.select((qb) => qb.fn.countAll<number>().as('total'))
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
}
|
||||||
|
|
||||||
@GenerateSql(
|
@GenerateSql(
|
||||||
{ params: [DummyValue.UUID, {}] },
|
{ params: [DummyValue.UUID, {}] },
|
||||||
{ name: 'date filter', params: [DummyValue.UUID, { for: DummyValue.DATE }] },
|
{ name: 'date filter', params: [DummyValue.UUID, { for: DummyValue.DATE }] },
|
||||||
)
|
)
|
||||||
search(ownerId: string, dto: MemorySearchDto) {
|
search(ownerId: string, dto: MemorySearchDto) {
|
||||||
return this.db
|
return this.searchBuilder(ownerId, dto)
|
||||||
.selectFrom('memories')
|
|
||||||
.selectAll('memories')
|
|
||||||
.select((eb) =>
|
.select((eb) =>
|
||||||
jsonArrayFrom(
|
jsonArrayFrom(
|
||||||
eb
|
eb
|
||||||
|
|
@ -48,15 +70,7 @@ export class MemoryRepository implements IBulkAsset {
|
||||||
.where('assets.deletedAt', 'is', null),
|
.where('assets.deletedAt', 'is', null),
|
||||||
).as('assets'),
|
).as('assets'),
|
||||||
)
|
)
|
||||||
.$if(dto.isSaved !== undefined, (qb) => qb.where('isSaved', '=', dto.isSaved!))
|
.selectAll('memories')
|
||||||
.$if(dto.type !== undefined, (qb) => qb.where('type', '=', dto.type!))
|
|
||||||
.$if(dto.for !== undefined, (qb) =>
|
|
||||||
qb
|
|
||||||
.where((where) => where.or([where('showAt', 'is', null), where('showAt', '<=', dto.for!)]))
|
|
||||||
.where((where) => where.or([where('hideAt', 'is', null), where('hideAt', '>=', dto.for!)])),
|
|
||||||
)
|
|
||||||
.where('deletedAt', dto.isTrashed ? 'is not' : 'is', null)
|
|
||||||
.where('ownerId', '=', ownerId)
|
|
||||||
.orderBy('memoryAt', 'desc')
|
.orderBy('memoryAt', 'desc')
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,10 @@ export class MemoryService extends BaseService {
|
||||||
return memories.map((memory) => mapMemory(memory, auth));
|
return memories.map((memory) => mapMemory(memory, auth));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statistics(auth: AuthDto, dto: MemorySearchDto) {
|
||||||
|
return this.memoryRepository.statistics(auth.user.id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
async get(auth: AuthDto, id: string): Promise<MemoryResponseDto> {
|
async get(auth: AuthDto, id: string): Promise<MemoryResponseDto> {
|
||||||
await this.requireAccess({ auth, permission: Permission.MEMORY_READ, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.MEMORY_READ, ids: [id] });
|
||||||
const memory = await this.findOrFail(id);
|
const memory = await this.findOrFail(id);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue