logo logo
Design: Database: Members meta

Database Members
Purpose Track the members in the engagement.
Cardinality one Members database in the engagement
one NextMemberItem per engagement
one MemberItem per member in the engagement
Source Code
Items src/ts/dbitems/Members.ts
Zod Models
item MembersItem - union of NextMemberItem, MemberItem below
item.item one of NextMemberRecord, MemberRecord
NextMemberItem
itemId nextmember
item: NextMemberRecord
    kind 'nextmember'
    nextmnum mnum to assign to next member
MemberItem
itemId number assigned to member. first (host) is 1.
item: MemberRecord
    kind 'member'
    mnum number assigned to member (same as itemId)
    role 'host', 'guest' or 'removed'
    userid userbase userid, saved from when member's user is signed up
    dbids:
        user dbid of member's User database

Description

The Members database is created when the host creates a new engagement. It holds one item to track the next member number and also one item for each guest invited to the engagement that has not been subsequently removed.

(see userbase-js/types/index.d.ts)

  export interface Item {              userbase item
    itemId: string                    ╭────────────────────────┐
    item: any ────────────────────┬──▶│ kind: 'member'         │
    createdBy: Attribution        │   │ mnum: 1                │
    updatedBy?: Attribution       │   │ ...                    │
    fileId?: string               │   ╰────────────────────────┘
    fileName?: string             │
    fileSize?: number             │
    fileUploadedBy?: Attribution  │   ╭────────────────────────┐
    writeAccess?: AccessControl   └──▶│ kind: 'nextmember'     │
  }                                   │ nextmnum: 2            │
                                      ╰────────────────────────┘

MembersRecord represents the specific type of Item.item for an entry
in the Members database - either NextMemberRecord or MemberRecord.

Queries

Securepub queries the Members database in order to determine the members of the engagement and find the dbids of each member's User database.

           one per              one per
           member               engagement
   AppId ╶╶╶╶╶╶╶╶╶╶╶► ULID-Role ╶╶╶╶╶╶╶╶╶╶╶► Members
    +                 database               database
   RoleID                                      ╵
                                               ╵
                                               └╶╶╶╶╶╶► User
                                                        database

Securepub queries the Members database to derive Userbase usernames for the members in the engagement with the following procedure:

1. selecting the database from getDatabases()
   whose databaseId matches dbids.user

2. searching the database's users array for an object
   with isOwner = true and

3. selecting the corresponding username from that object

    ╭─────────────╮     ╭──────────────────────╮
    │ Members     │     │ getDatabases         │
    ├─────────────┤     ├──────────────────────┤
    │ dbids.user  │╶╶╶╶►│ databaseId (User)    │
    ╰─────────────╯     │ users                │
                        │    ╶╶► isOwner true  │
                        │        username      │
                        ╰──────────────────────╯

Securepub records but does not directly use member Userbase userids.
Userids are visible to the host and can be used in the Userbase Admin interface.

NextMemberItem

The Members database holds one NextMemberItem which keeps track of the next "mnum" (member number) which will be assigned to the next member invited by the host.

Member numbers are monotonically increasing integers starting with 1.

MemberItem

The Members database holds one MemberItem for each invited member.

Ownership and Access

The engagement host is the owner of the Members database.

Only the host may write to the Members database.

The Members database is shared readable with all members.

Zod models

The following Zod models show how the Userbase items are represented in memory.

Note that in addition to the values specified by the Userbase Item interface the kind and database attributes are added during deserialization.

export type MembersItem =
  | NextMemberItem
  | MemberItem
;
export const MembersItem = z.discriminatedUnion('kind', [
  NextMemberItem,
  MemberItem
]);

NextMemberItem

export type  NextMemberItem = z.infer<typeof NextMemberItem>;
export const NextMemberItem = z.object({
  kind:                 z.literal('nextmemberitem'),
  itemId:               z.string(),
  item:                 NextMemberRecord,
  updatedBy:            z.custom<Attribution>(),
  database:             z.custom<Database>()
}).strip();
/*
 * NextMemberRecord
 */
export type  NextMemberRecord = z.infer<typeof NextMemberRecord>;
export const NextMemberRecord = z.object({
  kind:                 z.literal('nextmember'),
  nextbnum:             z.number()
}).strip();

MemberItem

export type  MemberItem = z.infer<typeof MemberItem>;
export const MemberItem = z.object({
  kind:                 z.literal('memberitem'),
  itemId:               z.string(),
  item:                 MemberRecord,
  updatedBy:            z.custom<Attribution>(),
  database:             z.custom<Database>()
}).strip();
export type  MemberRecord = z.infer<typeof MemberRecord>;
export const MemberRecord = z.object({
  kind:                 z.literal('member'),
  mnum:                 z.number(),
  role:                 z.union([
                          z.literal("host"),
                          z.literal("guest"),
                          z.literal("removed")
  ]),
  userid:               z.string(),
  dbids:                z.object({
    user:                 z.string()
  })
}).strip();