actor.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { Context } from "./context"
  2. import { UserRole } from "./schema/user.sql"
  3. import { Log } from "./util/log"
  4. export namespace Actor {
  5. interface Account {
  6. type: "account"
  7. properties: {
  8. accountID: string
  9. email: string
  10. }
  11. }
  12. interface Public {
  13. type: "public"
  14. properties: {}
  15. }
  16. interface User {
  17. type: "user"
  18. properties: {
  19. userID: string
  20. workspaceID: string
  21. accountID: string
  22. role: (typeof UserRole)[number]
  23. }
  24. }
  25. interface System {
  26. type: "system"
  27. properties: {
  28. workspaceID: string
  29. }
  30. }
  31. export type Info = Account | Public | User | System
  32. const ctx = Context.create<Info>()
  33. export const use = ctx.use
  34. const log = Log.create().tag("namespace", "actor")
  35. export function provide<R, T extends Info["type"]>(
  36. type: T,
  37. properties: Extract<Info, { type: T }>["properties"],
  38. cb: () => R,
  39. ) {
  40. return ctx.provide(
  41. {
  42. type,
  43. properties,
  44. } as any,
  45. () => {
  46. return Log.provide({ ...properties }, () => {
  47. log.info("provided")
  48. return cb()
  49. })
  50. },
  51. )
  52. }
  53. export function assert<T extends Info["type"]>(type: T) {
  54. const actor = use()
  55. if (actor.type !== type) {
  56. throw new Error(`Expected actor type ${type}, got ${actor.type}`)
  57. }
  58. return actor as Extract<Info, { type: T }>
  59. }
  60. export const assertAdmin = () => {
  61. if (userRole() === "admin") return
  62. throw new Error(`Action not allowed. Ask your workspace admin to perform this action.`)
  63. }
  64. export function workspace() {
  65. const actor = use()
  66. if ("workspaceID" in actor.properties) {
  67. return actor.properties.workspaceID
  68. }
  69. throw new Error(`actor of type "${actor.type}" is not associated with a workspace`)
  70. }
  71. export function account() {
  72. const actor = use()
  73. if ("accountID" in actor.properties) {
  74. return actor.properties.accountID
  75. }
  76. throw new Error(`actor of type "${actor.type}" is not associated with an account`)
  77. }
  78. export function userID() {
  79. return Actor.assert("user").properties.userID
  80. }
  81. export function userRole() {
  82. return Actor.assert("user").properties.role
  83. }
  84. }