Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/util/db-schema/organizations.ts
5827 views
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
admin_account_ids: {
69
type: "array",
70
pg_type: "UUID[]",
71
desc: "The account_ids of the admins of this organization.",
72
},
73
},
74
rules: {
75
desc: "All organizations.",
76
primary_key: "organization_id",
77
pg_indexes: [
78
"(lower(title) text_pattern_ops)",
79
"(lower(description) text_pattern_ops)",
80
"api_key",
81
],
82
pg_unique_indexes: [
83
"LOWER(name)", // see comments for accounts table.
84
],
85
user_query: {
86
get: {
87
throttle_changes: 500,
88
pg_where: [{ "organization_id = $::UUID": "organization_id" }],
89
fields: {
90
organization_id: null,
91
email_address: null,
92
name: "",
93
title: "",
94
description: "",
95
profile: {
96
image: undefined,
97
color: undefined,
98
},
99
created: null,
100
},
101
},
102
set: {
103
fields: {
104
organization_id: true,
105
name: true,
106
title: true,
107
description: true,
108
profile: true,
109
},
110
required_fields: {
111
organization_id: true,
112
},
113
async check_hook(db, obj, account_id, _project_id, cb) {
114
// Check that account_id is a member of this organization
115
// via a db query, since otherwise no permission to do anything.
116
if (
117
!(await db.accountIsInOrganization({
118
account_id,
119
organization_id: obj["organization_id"],
120
}))
121
) {
122
cb(`account must be a member of the organization`);
123
return;
124
}
125
126
if (obj["name"] != null) {
127
try {
128
checkOrganizationName(obj["name"]);
129
} catch (err) {
130
cb(err.toString());
131
return;
132
}
133
const id = await db.nameToAccountOrOrganization(obj["name"]);
134
if (id != null && id != account_id) {
135
cb(
136
`name "${obj["name"]}" is already taken by another organization or account`,
137
);
138
return;
139
}
140
}
141
142
// Hook to truncate some text fields to at most 254 characters, to avoid
143
// further trouble down the line.
144
for (const field of ["title", "description", "email_address"]) {
145
if (obj[field] != null) {
146
obj[field] = obj[field].slice(0, 254);
147
}
148
}
149
cb();
150
},
151
},
152
},
153
},
154
});
155
156