Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c3d2626
fix(contact): seed the invite handshake into the DM, not the workspac…
luongtrieuvy202 Sep 21, 2026
7ffc5c8
fix(task): reclaim an uploaded attachment once its last link goes
luongtrieuvy202 Sep 21, 2026
4a62a26
feat(invite): an invitation you answer, instead of a membership you find
EddyOne81 Sep 22, 2026
52e7f8e
fix(hub): send invite emails in batches of five instead of one awaite…
tobao17 Sep 22, 2026
5e46a8a
Merge pull request #227 from drumee/fix/invite-mail-batch
tobao17 Sep 22, 2026
c4055b6
fix(hub): send invite emails 25 at a time, the SMTP session cost domi…
tobao17 Sep 22, 2026
72cb573
Merge pull request #228 from drumee/fix/invite-mail-batch-25
tobao17 Sep 22, 2026
3c21a25
fix(hub): send invite emails in batches of five instead of one awaite…
tobao17 Sep 22, 2026
9f33fe7
fix(hub): send invite emails 25 at a time, the SMTP session cost domi…
tobao17 Sep 22, 2026
2a600e1
feat(task): broadcast to the caller's other sessions, skip only the c…
tranh0anghuan Sep 23, 2026
e321ce6
fix(over-limit): hub.invitations is a read, not a mutation
EddyOne81 Sep 23, 2026
1cdfe6f
feat(invite): supersede older invitations when a new one is sent
EddyOne81 Sep 23, 2026
d93d4fa
feat(invite): stamp invite_status on invitation rows in the feed
EddyOne81 Sep 23, 2026
ac8b2f4
Merge remote-tracking branch 'origin/test' into feat/workspace-invite…
EddyOne81 Sep 23, 2026
4ea07dd
feat(invite): push hub.invitations_changed when an invitation is answ…
EddyOne81 Sep 23, 2026
c4ca5da
Merge pull request #230 from drumee/feat/workspace-invite-accept-flow
EddyOne81 Sep 23, 2026
c484f8d
fix(channel): wait for the sbox copy before purging a staged attachment
aaron-tsar Sep 23, 2026
35daf43
docs(chat): record the chat attachment investigations of 22-23 September
aaron-tsar Sep 23, 2026
dfeec3b
feat(upload): chunked, parallel, resumable uploads (upload_init/chunk…
Sep 23, 2026
ecccaf4
feat(upload): chunked, parallel, resumable uploads (#231)
tobao17 Sep 23, 2026
7d76460
perf(channel): move staged attachments into the sbox and list them all
aaron-tsar Sep 24, 2026
53fd1b9
docs(chat): record the multi-file attachment investigation and fix di…
aaron-tsar Sep 24, 2026
25e2080
fix(channel): keep same-hub staged moves as message attachments
aaron-tsar Sep 24, 2026
fab1dab
docs(chat): record the multi-file attachment fixes and stage results
aaron-tsar Sep 24, 2026
add8aa3
docs(plan): conference.invite through the mobile push pipeline
aaron-tsar Sep 24, 2026
8a01368
feat(room): carry hub_id and pid on the room.scheduled push
EddyOne81 Sep 25, 2026
d6fa56f
feat(env): tours_new_user_since cutoff for contextual tours (#232)
tranh0anghuan Sep 25, 2026
b2f4881
Feat/tours new users only test (#233)
tranh0anghuan Sep 25, 2026
257a321
fix(channel): show only current readers in chat read receipts
aaron-tsar Sep 25, 2026
f3d763f
fix(media): unzip and server import write canonical media paths
EddyOne81 Sep 25, 2026
44d1d0f
fix(media): trash every node a batch request names, not only the ACL …
Sep 25, 2026
6381112
fix(media): trash every node a batch request names (#235)
tobao17 Sep 25, 2026
bd1d67d
fix(conference): stop announcing meetings that did not just start
luongtrieuvy202 Sep 26, 2026
4b0f03f
fix(channel): let channel.messages list without marking read
luongtrieuvy202 Sep 26, 2026
de190c1
fix(activity): per-row read of media and chat notifications persists
EddyOne81 Sep 26, 2026
65c2d07
fix(activity): one definition of an unread contact notification
EddyOne81 Sep 26, 2026
1b1a156
fix(activity): reading a contact notification keeps it in history
EddyOne81 Sep 26, 2026
e062cb0
feat(activity): pin bookmarked notifications on top (bookmark_rows)
EddyOne81 Sep 27, 2026
2f773d3
Feat/trash filters (#237)
tranh0anghuan Sep 27, 2026
93ad576
chore: merge preview into test (keep test invite flow in hub.js)
tobao17 Sep 27, 2026
574d56e
Merge pull request #238 from drumee/test
tobao17 Sep 27, 2026
1a05c26
feat(activity): unread_counts.files_by_hub — new files and folders pe…
EddyOne81 Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 61 additions & 3 deletions acl/activity.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@
"permission": {
"src": "read"
},
"params": {},
"params": {
"files_since": {
"type": "object",
"required": false,
"description": "Web rail only: { \"<hub_id>\": <unix ts> } - per workspace, count files_by_hub only after the last time the user opened its Files tab. Malformed entries are ignored."
}
},
"returns": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -64,6 +70,11 @@
"type": "integer",
"description": "Other tab: workspace and team invites, contacts, tickets, access requests and system alerts",
"example": 1
},
"files_by_hub": {
"type": "array",
"description": "Per workspace, NEW files and folders (media.new; not chat attachments, not workspace creation) newer than files_since: [{ hub_id, cnt, last_ts }]. Not added to any total above. [] until mfs_new_by_hub is deployed.",
"example": [{ "hub_id": "ac248fb1ac248fb9", "cnt": 3, "last_ts": 1790568840 }]
}
}
},
Expand Down Expand Up @@ -234,6 +245,10 @@
"lastname": { "type": "string" },
"fullname": { "type": "string" },
"hub_name": { "type": "string" },
"invite_status": {
"type": "string",
"description": "Workspace invitations only: pending | accepted | declined | expired | invalid. Absent when it could not be resolved; the client then falls back to invite_token alone"
},
"link_label": { "type": "string" },
"task_title": { "type": "string" },
"meeting_action": { "type": "string" },
Expand Down Expand Up @@ -496,6 +511,38 @@
}
},

"read_contact_event": {
"doc": "Mark a single contact_activity row (task, meeting, invitation, etc.) as read for the caller. Stamps yp.contact_activity.dismissed_at only, so the row stays in Activity history with is_read = 1. Removal is dismiss_contact_event / delete_contact_event.",
"scope": "hub",
"permission": {
"src": "read"
},
"params": {
"activity_id": {
"type": "integer",
"required": true,
"description": "The yp.contact_activity row ID to mark read"
}
},
"returns": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "ok on success"
},
"activity_id": {
"type": "integer",
"description": "The contact_activity ID"
},
"dismissed_at": {
"type": "integer",
"description": "Unix timestamp of the read"
}
}
}
},

"dismiss_contact_event": {
"doc": "Dismiss a single contact_activity row (hub invite, contact invite, etc.) so it no longer appears in the activity feed. Stamps yp.contact_activity.dismissed_at without deleting the underlying audit row.",
"scope": "hub",
Expand Down Expand Up @@ -595,11 +642,12 @@
},

"bookmark_add": {
"doc": "Save one visible Activity item in the caller's personal notification bookmark store. The key is a server-generated SHA-256 activity identity.",
"doc": "Save one visible Activity item in the caller's personal notification bookmark store. The key is a server-generated SHA-256 activity identity. The web panel may also send the row itself, kept (when it matches the key) so bookmark_rows can pin it on top.",
"scope": "hub",
"permission": { "src": "read" },
"params": {
"bookmark_key": { "type": "string", "required": true }
"bookmark_key": { "type": "string", "required": true },
"row": { "type": "object", "required": false }
},
"returns": {
"type": "object",
Expand All @@ -626,6 +674,16 @@
}
},

"bookmark_rows": {
"doc": "The caller's saved Activity items (snapshots stored by bookmark_add), newest first, to pin on top of the panel. Optional bucket scopes to one tab. is_read is always 1: the client uses the live feed row when it has one.",
"scope": "hub",
"permission": { "src": "read" },
"params": {
"bucket": { "type": "string", "required": false }
},
"returns": { "type": "array" }
},

"read": {
"doc": "Mark a rollup as read (advance read pointer) without hiding it. For chat/teamchat/ticket this is identical to dismiss; for media we only advance mfs_ack; for contact this is a no-op (use dismiss to hide).",
"scope": "hub",
Expand Down
5 changes: 5 additions & 0 deletions acl/channel.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@
"type": "number",
"required": false,
"doc": "Page number for pagination (default: 1)"
},
"mark_read": {
"type": "number",
"required": false,
"doc": "0 = list without marking the messages seen (a workspace team chat shown, not read). Default 1."
}
},
"returns": {
Expand Down
51 changes: 50 additions & 1 deletion acl/hub.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,57 @@
}
},

"decline_invite": {
"doc": "Turn down a workspace invitation, by the token from its email link or its notification row. Holding the secret is the authorisation, exactly as it is for accept_invite - and this is the less dangerous of the two, since accepting grants access while declining only destroys an invitation. No session is required: an invitation usually goes to somebody with no account, and saying no must not cost them a signup. Never removes membership (that is desk.leave_hub) and refuses any token that is not an ACTIVE hub_invite, so a stale link pressed after joining cannot rewrite how somebody got in. Idempotent - declining twice keeps the first answer and reports the same result.",
"scope": "hub",
"permission": {
"src": "anonymous",
"fast_check": "public-api"
},
"params": {
"token": {
"type": "string",
"required": true,
"doc": "The invite token secret from the email link or the notification row"
}
},
"returns": {
"status": { "type": "string", "doc": "declined on success; invalid when the token is not a live hub invite; already_used when it was already accepted" },
"hub_id": { "type": "string", "doc": "Workspace the invitation was for, when the token resolved one" }
}
},

"invitations": {
"doc": "Invitations this workspace is still waiting on, plus the ones that were turned down - the Pending Invitations section of the Access panel. One row per person, newest invitation wins. Accepted invitations are absent (those people are members) and so are lapsed ones. Current members are filtered out here rather than in the proc, because membership lives in the workspace database and hub_invitations reads yellow_page.",
"scope": "hub",
"permission": {
"src": "admin"
},
"params": {},
"returns": {
"type": "array",
"doc": "Pending and declined invitations",
"items": {
"email": { "type": "string", "doc": "Address the invitation was sent to" },
"status": { "type": "string", "doc": "pending | declined" },
"ctime": { "type": "integer", "doc": "Unix timestamp the invitation was sent" },
"permission": { "type": "number", "doc": "Privilege bitmask the invitation offers" },
"invitee_uid": { "type": "string", "doc": "Invitee's Drumee user id, null when they have no account yet" },
"invitee_firstname": { "type": "string", "doc": "Invitee's first name when they have an account" },
"invitee_lastname": { "type": "string", "doc": "Invitee's last name when they have an account" },
"invitee_fullname": { "type": "string", "doc": "Invitee's display name when they have an account" },
"invitee_avatar": { "type": "string", "doc": "Invitee's avatar when they have an account" },
"inviter_id": { "type": "string", "doc": "Who sent it" },
"inviter_firstname": { "type": "string", "doc": "Inviter's first name" },
"inviter_lastname": { "type": "string", "doc": "Inviter's last name" },
"inviter_fullname": { "type": "string", "doc": "Inviter's display name" }
}
},
"errors": []
},

"invite": {
"doc": "Invite one or more emails into this workspace, branching on area (share-link vs restricted) and drumate status.",
"doc": "Invite one or more emails into this workspace. Mints an invitation the recipient answers with Accept or Decline - it does NOT add anybody. An address that already has an account additionally gets a notification row carrying the token; every address gets the email. Membership is written by accept_invite.",
"scope": "hub",
"permission": {
"src": "admin"
Expand Down
Loading
Loading