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.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/util/db-schema/organizations.ts
Views: 687
1
/*
2
Table of organizations.
3
4
WARNING: Organizations are far from actually being implemented
5
fully in Cocalc! I just figure defining this can't hurt to get
6
the ball rollowing.
7
*/
8
9
import { Table } from "./types";
10
import { checkAccountName as checkOrganizationName } from "./name-rules";
11
12
Table({
13
name: "organizations",
14
fields: {
15
organization_id: {
16
type: "uuid",
17
desc: "The uuid that determines this organization",
18
},
19
created: {
20
type: "timestamp",
21
desc: "When the organization was created.",
22
},
23
deleted: {
24
type: "boolean",
25
desc: "True if this organization has been deleted.",
26
},
27
name: {
28
type: "string",
29
pg_type: "VARCHAR(39)",
30
desc: "The name of this organization (used for URL's). This is optional but globally unique across all organizations *and* accounts. It can be between 1 and 39 characters from a-z A-Z 0-9 - and must not start with a dash.",
31
},
32
title: {
33
type: "string",
34
pg_type: "VARCHAR(254)",
35
desc: "Title of this organization",
36
},
37
description: {
38
type: "string",
39
pg_type: "VARCHAR(254)",
40
desc: "Description of this organization.",
41
},
42
link: {
43
type: "string",
44
pg_type: "VARCHAR(254)",
45
desc: "Optional URL of this organization (e.g., their webpage).",
46
},
47
email_address: {
48
type: "string",
49
pg_type: "VARCHAR(254)",
50
desc: "Optional email address to reach this organization.",
51
},
52
api_key: {
53
type: "string",
54
desc: "Optional API key that grants full API access to all projects that this organization owns. Key is of the form 'sk_9QabcrqJFy7JIhvAGih5c6Nb', where the random part is 24 characters (base 62).",
55
},
56
profile: {
57
type: "map",
58
desc: "Information related to displaying an avatar for this organization.",
59
},
60
users: {
61
type: "map",
62
desc: "This is a map from account_id to 'owner' | 'member'.",
63
},
64
invitations: {
65
type: "map",
66
desc: "This is a map from account_id to {created:timestamp, status:'pending'|'invited'|'accepted'|'denied', emailed:timestamp}",
67
},
68
},
69
rules: {
70
desc: "All organizations.",
71
primary_key: "organization_id",
72
pg_indexes: [
73
"(lower(title) text_pattern_ops)",
74
"(lower(description) text_pattern_ops)",
75
"api_key",
76
],
77
pg_unique_indexes: [
78
"LOWER(name)", // see comments for accounts table.
79
],
80
user_query: {
81
get: {
82
throttle_changes: 500,
83
pg_where: [{ "organization_id = $::UUID": "organization_id" }],
84
fields: {
85
organization_id: null,
86
email_address: null,
87
name: "",
88
title: "",
89
description: "",
90
profile: {
91
image: undefined,
92
color: undefined,
93
},
94
created: null,
95
},
96
},
97
set: {
98
fields: {
99
organization_id: true,
100
name: true,
101
title: true,
102
description: true,
103
profile: true,
104
},
105
required_fields: {
106
organization_id: true,
107
},
108
async check_hook(db, obj, account_id, _project_id, cb) {
109
// Check that account_id is a member of this organization
110
// via a db query, since otherwise no permission to do anything.
111
if (
112
!(await db.accountIsInOrganization({
113
account_id,
114
organization_id: obj["organization_id"],
115
}))
116
) {
117
cb(`account must be a member of the organization`);
118
return;
119
}
120
121
if (obj["name"] != null) {
122
try {
123
checkOrganizationName(obj["name"]);
124
} catch (err) {
125
cb(err.toString());
126
return;
127
}
128
const id = await db.nameToAccountOrOrganization(obj["name"]);
129
if (id != null && id != account_id) {
130
cb(
131
`name "${obj["name"]}" is already taken by another organization or account`
132
);
133
return;
134
}
135
}
136
137
// Hook to truncate some text fields to at most 254 characters, to avoid
138
// further trouble down the line.
139
for (const field of ["title", "description", "email_address"]) {
140
if (obj[field] != null) {
141
obj[field] = obj[field].slice(0, 254);
142
}
143
}
144
cb();
145
},
146
},
147
},
148
},
149
});
150
151