feat(api): cursor listing contract — PageCursor trait + resource field
- Add src/application/dtos/cursor.rs with three shared types:
· PageCursor trait — default base64url+JSON encode/decode; one bare
impl line per cursor struct
· CursorQuery struct — standard limit/cursor/sort_by query params with
limit_clamped() and decode_cursor<C>() helpers; compose via flatten
· CursorListResponse<T> — standard {items, next_cursor?} envelope with
from_oversized() and with_cursor() builders
- Migrate GrantCursor to impl PageCursor (remove duplicate encode/decode)
- Update GET /api/grants/incoming/resources:
· SharedWithMeQuery now embeds CursorQuery via #[serde(flatten)]
· Replace file/folder nullable pair with ResourceContentDto (untagged
enum) under a single always-present 'resource' field
· SharedWithMeDto is now a type alias for CursorListResponse<SharedWithMeItemDto>
· Handler uses q.paging.limit_clamped() and decode_cursor<GrantCursor>()
- Add docs/architecture/resource-listing.md — authoritative contract for
all listing endpoints (cursor design, SQL keyset WHERE, sort_by naming,
Rust + JS skeletons, compliance table, migration guide)
- Register doc in VitePress sidebar and architecture index
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -297,14 +297,13 @@
|
||||
|
||||
/**
|
||||
* One item returned by `GET /api/grants/incoming/resources`.
|
||||
* Exactly one of `file` / `folder` is populated (indicated by `resource_type`).
|
||||
* `resource_type` discriminates the shape of `resource`.
|
||||
* @typedef {Object} SharedWithMeItem
|
||||
* @property {ResourceTypeEnum} resource_type
|
||||
* @property {PermissionTypeEnum[]} permissions - All permissions the caller holds on this resource.
|
||||
* @property {string} granted_at - ISO-8601 timestamp of the earliest grant.
|
||||
* @property {string} granted_by - UUID of the user who created the grant.
|
||||
* @property {FileItem|undefined} [file] - Populated when resource_type === 'file'.
|
||||
* @property {FolderItem|undefined} [folder] - Populated when resource_type === 'folder'.
|
||||
* @property {FileItem|FolderItem} resource - Full resource details; shape follows resource_type.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -150,8 +150,8 @@ const sharedWithMeView = {
|
||||
const ownerMap = new Map();
|
||||
|
||||
for (const item of items) {
|
||||
if (item.resource_type === 'folder' && item.folder) {
|
||||
const f = item.folder;
|
||||
if (item.resource_type === 'folder') {
|
||||
const f = /** @type {FolderItem} */ (item.resource);
|
||||
folders.push(
|
||||
/** @type {FolderItem} */ ({
|
||||
id: f.id,
|
||||
@@ -168,8 +168,8 @@ const sharedWithMeView = {
|
||||
})
|
||||
);
|
||||
ownerMap.set(f.id, item.granted_by);
|
||||
} else if (item.resource_type === 'file' && item.file) {
|
||||
const f = item.file;
|
||||
} else if (item.resource_type === 'file') {
|
||||
const f = /** @type {FileItem} */ (item.resource);
|
||||
files.push(
|
||||
/** @type {FileItem} */ ({
|
||||
id: f.id,
|
||||
|
||||
Reference in New Issue
Block a user