CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

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