feat(server): add memories statistics resource (#19035)

This commit is contained in:
Jonathan Gilbert 2025-06-10 23:47:46 +10:00 committed by GitHub
parent 16745e77d4
commit e88bd74fd2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 176 additions and 17 deletions

BIN
mobile/openapi/README.md generated

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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}": {
"delete": {
"operationId": "deleteMemory",
@ -10827,6 +10893,17 @@
],
"type": "object"
},
"MemoryStatisticsResponseDto": {
"properties": {
"total": {
"type": "integer"
}
},
"required": [
"total"
],
"type": "object"
},
"MemoryType": {
"enum": [
"on_this_day"

View file

@ -742,6 +742,9 @@ export type MemoryCreateDto = {
seenAt?: string;
"type": MemoryType;
};
export type MemoryStatisticsResponseDto = {
total: number;
};
export type MemoryUpdateDto = {
isSaved?: boolean;
memoryAt?: string;
@ -2509,6 +2512,24 @@ export function createMemory({ 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 }: {
id: string;
}, opts?: Oazapfts.RequestOpts) {

View file

@ -2,7 +2,13 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put,
import { ApiTags } from '@nestjs/swagger';
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.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 { Auth, Authenticated } from 'src/middleware/auth.guard';
import { MemoryService } from 'src/services/memory.service';
@ -25,6 +31,12 @@ export class MemoryController {
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')
@Authenticated({ permission: Permission.MEMORY_READ })
getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> {

View file

@ -71,6 +71,11 @@ export class MemoryCreateDto extends MemoryBaseDto {
assetIds?: string[];
}
export class MemoryStatisticsResponseDto {
@ApiProperty({ type: 'integer' })
total!: number;
}
export class MemoryResponseDto {
id!: string;
createdAt!: Date;

View file

@ -1,8 +1,33 @@
-- 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
select
"memories".*,
(
select
coalesce(json_agg(agg), '[]')
@ -20,7 +45,8 @@ select
order by
"assets"."fileCreatedAt" asc
) as agg
) as "assets"
) as "assets",
"memories".*
from
"memories"
where
@ -31,7 +57,6 @@ order by
-- MemoryRepository.search (date filter)
select
"memories".*,
(
select
coalesce(json_agg(agg), '[]')
@ -49,7 +74,8 @@ select
order by
"assets"."fileCreatedAt" asc
) as agg
) as "assets"
) as "assets",
"memories".*
from
"memories"
where

View file

@ -28,14 +28,36 @@ export class MemoryRepository implements IBulkAsset {
.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(
{ params: [DummyValue.UUID, {}] },
{ name: 'date filter', params: [DummyValue.UUID, { for: DummyValue.DATE }] },
)
search(ownerId: string, dto: MemorySearchDto) {
return this.db
.selectFrom('memories')
.selectAll('memories')
return this.searchBuilder(ownerId, dto)
.select((eb) =>
jsonArrayFrom(
eb
@ -48,15 +70,7 @@ export class MemoryRepository implements IBulkAsset {
.where('assets.deletedAt', 'is', null),
).as('assets'),
)
.$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)
.selectAll('memories')
.orderBy('memoryAt', 'desc')
.execute();
}

View file

@ -82,6 +82,10 @@ export class MemoryService extends BaseService {
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> {
await this.requireAccess({ auth, permission: Permission.MEMORY_READ, ids: [id] });
const memory = await this.findOrFail(id);