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