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/database/postgres/account-queries.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
// Various functions involving the database and accounts.
7
8
import { callback2 } from "@cocalc/util/async-utils";
9
import {
10
assert_valid_account_id,
11
assert_valid_email_address,
12
len,
13
} from "@cocalc/util/misc";
14
import { is_a_site_license_manager } from "./site-license/search";
15
import { PostgreSQL } from "./types";
16
//import getLogger from "@cocalc/backend/logger";
17
//const L = getLogger("db:pg:account-queries");
18
19
/* For now we define "paying customer" to mean they have a subscription.
20
It's OK if it expired. They at least bought one once.
21
This is mainly used for anti-abuse purposes...
22
23
TODO: modernize this or don't use this at all...
24
*/
25
export async function is_paying_customer(
26
db: PostgreSQL,
27
account_id: string,
28
): Promise<boolean> {
29
let x;
30
try {
31
x = await callback2(db.get_account, {
32
account_id,
33
columns: ["stripe_customer"],
34
});
35
} catch (_err) {
36
// error probably means there is no such account or account_id is badly formatted.
37
return false;
38
}
39
if (!!x.stripe_customer?.subscriptions?.total_count) {
40
// they have at least one subscription of some form -- so that's enough to count.
41
return true;
42
}
43
// If they manage any licenses then they also count:
44
return await is_a_site_license_manager(db, account_id);
45
}
46
47
interface SetAccountFields {
48
db: PostgreSQL;
49
account_id: string;
50
email_address?: string | undefined;
51
first_name?: string | undefined;
52
last_name?: string | undefined;
53
}
54
55
// this is like set_account_info_if_different, but only sets the fields if they're not set
56
export async function set_account_info_if_not_set(
57
opts: SetAccountFields,
58
): Promise<void> {
59
return await set_account_info_if_different(opts, false);
60
}
61
62
// This sets the given fields of an account, if it is different from the current value – except for the email address, which we only set but not change
63
export async function set_account_info_if_different(
64
opts: SetAccountFields,
65
overwrite = true,
66
): Promise<void> {
67
const columns = ["email_address", "first_name", "last_name"];
68
69
// this could throw an error for "no such account"
70
const account = await get_account<{
71
email_address: string;
72
first_name: string;
73
last_name: string;
74
}>(opts.db, opts.account_id, columns);
75
76
const do_set: { [field: string]: string } = {};
77
let do_email: string | undefined = undefined;
78
79
for (const field of columns) {
80
if (typeof opts[field] !== "string") continue;
81
if (!overwrite && account[field] != null) continue;
82
if (account[field] != opts[field]) {
83
if (field === "email_address") {
84
do_email = opts[field];
85
} else {
86
do_set[field] = opts[field];
87
}
88
}
89
}
90
if (len(do_set) > 0) {
91
await set_account(opts.db, opts.account_id, do_set);
92
}
93
94
if (do_email) {
95
if (account["email_address"] != null) {
96
// if it changes, we have to call the change_email_address function
97
await callback2(opts.db.change_email_address.bind(opts.db), {
98
account_id: opts.account_id,
99
email_address: do_email,
100
});
101
}
102
// Just changed email address - might be added to a project...
103
await callback2(opts.db.do_account_creation_actions.bind(opts.db), {
104
email_address: do_email,
105
account_id: opts.account_id,
106
});
107
}
108
}
109
110
export async function set_account(
111
db: PostgreSQL,
112
account_id: string,
113
set: { [field: string]: any },
114
): Promise<void> {
115
await db.async_query({
116
query: "UPDATE accounts",
117
where: { "account_id = $::UUID": account_id },
118
set,
119
});
120
}
121
122
// TODO typing: pick the column fields from the actual account type stored in the database
123
export async function get_account<T>(
124
db: PostgreSQL,
125
account_id: string,
126
columns: string[],
127
): Promise<T> {
128
return await callback2(db.get_account.bind(db), {
129
account_id,
130
columns,
131
});
132
}
133
134
interface SetEmailAddressVerifiedOpts {
135
db: PostgreSQL;
136
account_id: string;
137
email_address: string;
138
}
139
140
export async function set_email_address_verified(
141
opts: SetEmailAddressVerifiedOpts,
142
): Promise<void> {
143
const { db, account_id, email_address } = opts;
144
assert_valid_account_id(account_id);
145
assert_valid_email_address(email_address);
146
await db.async_query({
147
query: "UPDATE accounts",
148
jsonb_set: { email_address_verified: { [email_address]: new Date() } },
149
where: { "account_id = $::UUID": account_id },
150
});
151
}
152
153
export async function is_admin(
154
db: PostgreSQL,
155
account_id: string,
156
): Promise<boolean> {
157
const { groups } = await get_account<{ groups?: string[] }>(db, account_id, [
158
"groups",
159
]);
160
return Array.isArray(groups) && groups.includes("admin");
161
}
162
163