Path: blob/master/src/packages/util/db-schema/groups.ts
1447 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Groups of cocalc accounts.7*/89import { Table } from "./types";10import { SCHEMA } from "./index";11import { uuid } from "../misc";1213export interface Group {14// primary key: a uuid15group_id: string;16// owners -- uuids of owners of the group17owner_account_ids?: string[];18// members -- uuids of members of the group19member_account_ids?: string[];20// the title21title?: string;22color?: string;23// account that will be charged for any resource owned by the group24billing_account_id?: string;25}2627export const MAX_TITLE_LENGTH = 1024;28export const MAX_COLOR_LENGTH = 30;2930Table({31name: "groups",32fields: {33group_id: {34type: "uuid",35desc: "Unique id of this group of accounts.",36},37owner_account_ids: {38type: "array",39pg_type: "UUID[]",40desc: "Unique id's of owners of this group. They can add/remove members or other owners. This can be null, e.g., for implicitly created groups (e.g., to send a group message), there's no need for a management.",41},42member_account_ids: {43type: "array",44pg_type: "UUID[]",45desc: "Unique id's of members of this group.",46},47billing_account_id: {48type: "uuid",49desc: "Account_id that will be **charged money** for any resource owned by the group.",50render: { type: "account" },51title: "Billing Account",52},53title: {54type: "string",55pg_type: `VARCHAR(${MAX_TITLE_LENGTH})`,56desc: "Title of this group of accounts",57},58color: {59type: "string",60desc: "A user configurable color.",61pg_type: `VARCHAR(${MAX_COLOR_LENGTH})`,62render: { type: "color", editable: true },63},64},65rules: {66primary_key: "group_id",67pg_indexes: [68"USING GIN (owner_account_ids)",69"USING GIN (member_account_ids)",70],71changefeed_keys: ["owner_account_ids"],72user_query: {73get: {74pg_where: [{ "$::UUID = ANY(owner_account_ids)": "account_id" }],75fields: {76group_id: null,77owner_account_ids: null,78member_account_ids: null,79title: null,80color: null,81},82},83set: {84fields: {85group_id: true,86owner_account_ids: true,87member_account_ids: true,88title: true,89color: true,90},91async check_hook(database, query, account_id, _project_id, cb) {92// for sets we have to manually check that the this user is an owner, because93// we didn't implement something like `project_id: "project_write"` which is94// usually used for validating writes. Also the where above is obviously95// only for gets and changefeeds.96try {97const client = database._client();98const { rows } = await client.query(99"SELECT COUNT(*) AS count FROM groups WHERE $1=ANY(owner_account_ids) AND group_id=$2",100[account_id, query?.group_id],101);102if (rows[0].count != 1) {103throw Error("user must be an owner of the group");104}105cb();106} catch (err) {107cb(`${err}`);108}109},110},111},112},113});114115// Use the create_groups virtual table to create a new group.116// We have to do this, since users shouldn't assign uuid's117// AND our check_hook above prevents a user from writing118// to a group if they don't already own it, and they don't119// own one they are creating.120// This is a get query, because you do a get for121// {group_id:null, owner_account_ids:...}122// and the group_id gets filled in with your new record's id.123Table({124name: "create_group",125rules: {126virtual: "groups",127primary_key: "group_id",128user_query: {129get: {130fields: {131group_id: null,132owner_account_ids: null,133member_account_ids: null,134title: null,135color: null,136},137async instead_of_query(database, opts, cb): Promise<void> {138try {139// server assigned:140const group_id = uuid();141const client = database._client();142const query = opts.query ?? {};143const owner_account_ids = [...query.owner_account_ids];144if (!owner_account_ids.includes(opts.account_id)) {145owner_account_ids.push(opts.account_id);146}147const { member_account_ids, title, color } = query;148await client.query(149"INSERT INTO groups(group_id, owner_account_ids, member_account_ids, title, color) VALUES($1,$2,$3,$4,$5)",150[group_id, owner_account_ids, member_account_ids, title, color],151);152cb(undefined, {153group_id,154owner_account_ids,155member_account_ids,156title: title?.slice(0, MAX_TITLE_LENGTH),157color: color?.slice(0, MAX_COLOR_LENGTH),158});159} catch (err) {160cb(`${err}`);161}162},163},164},165},166fields: SCHEMA.groups.fields,167});168169Table({170name: "crm_groups",171rules: {172virtual: "groups",173primary_key: "group_id",174user_query: {175get: {176admin: true,177fields: SCHEMA.groups.user_query?.get?.fields ?? {},178},179},180},181fields: SCHEMA.groups.fields,182});183184185