Shared access between users
Grant other users access to your data using a shares table and existence-based permissions.After user-owned data, the next pattern you're likely to need is sharing. This recipe shows how to let one user grant another access to specific rows using a shares table.
Schema
Add a todoShares table that records which user has access to which todo.
schema.tsconst schema = {todos: s.table({title: s.string(),done: s.boolean(),},{ shares: s.reverse("todoShares", "todo") },),todoShares: s.table({todoId: s.uuid(),user_id: s.uuid(),can_edit: s.boolean(),},{ todo: s.rel("todos", "todoId") },),};type AppSchema = s.Schema<typeof schema>;export const app: s.App<AppSchema> = s.defineApp(schema);
Permissions
The creator can do everything. Share recipients get read access, and optionally edit access via can_edit.
permissions.tss.definePermissions(app, ({ policy, anyOf, session }) => {policy.todos.allowRead.where((todo) =>anyOf([{ "$createdBy.account": session.user.account },policy.todoShares.exists.where({todoId: todo.id,user_id: session.user.account,}),]),);policy.todos.allowInsert.always();policy.todos.allowUpdate.where((todo) =>anyOf([{ "$createdBy.account": session.user.account },policy.todoShares.exists.where({todoId: todo.id,user_id: session.user.account,can_edit: true,}),]),);policy.todos.allowDelete.where({ "$createdBy.account": session.user.account });// Only the todo creator can manage sharespolicy.todoShares.allowInsert.where((share) =>policy.todos.exists.where({id: share.todoId,"$createdBy.account": session.user.account,}),);policy.todoShares.allowRead.where({ user_id: session.user.account });policy.todoShares.allowDelete.where((share) =>policy.todos.exists.where({id: share.todoId,"$createdBy.account": session.user.account,}),);});
See Permissions for more on exists.where, anyOf, and allOf.
Granting access
To share a todo, insert a row into todoShares.
export function shareTodo(db: ReturnType<typeof useDb>,todoId: string,recipientAccountId: string,) {db.insert(app.todoShares, {todoId,user_id: recipientAccountId,can_edit: false,});}
Querying shared items
SharedWithMe.tsxexport function SharedWithMe() {const session = useSession();const {data: shares,isLoading,error,} = useAll(session?.user.account? app.todoShares.where({ user_id: session.user.account }).include({ todo: true }): undefined,);if (isLoading) return <p>Loading…</p>;if (error) return <p>Something went wrong!</p>;return (<ul>{shares?.map((share) =>share.todo ? (<li key={share.id}>{share.todo.title}{share.can_edit ? " (can edit)" : " (read-only)"}</li>) : null,)}</ul>);}
Revoking access
Delete the share row to revoke access. The server will stop syncing the todo to the former recipient.
export function unshareTodo(db: ReturnType<typeof useDb>, shareId: string) {db.delete(app.todoShares, shareId);}