@cocalc/database
Version:
CoCalc: code for working with our PostgreSQL database
145 lines (130 loc) • 4.29 kB
text/typescript
/*
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
*/
// Various functions involving the database and accounts.
import { callback2 } from "@cocalc/util/async-utils";
import {
assert_valid_account_id,
assert_valid_email_address,
len,
} from "@cocalc/util/misc";
import { is_a_site_license_manager } from "./site-license/search";
import { PostgreSQL } from "./types";
//import getLogger from "@cocalc/backend/logger";
//const L = getLogger("db:pg:account-queries");
/* For now we define "paying customer" to mean they have a subscription.
It's OK if it expired. They at least bought one once.
This is mainly used for anti-abuse purposes...
*/
export async function is_paying_customer(
db: PostgreSQL,
account_id: string
): Promise<boolean> {
let x;
try {
x = await callback2(db.get_account, {
account_id,
columns: ["stripe_customer"],
});
} catch (_err) {
// error probably means there is no such account or account_id is badly formatted.
return false;
}
if (!!x.stripe_customer?.subscriptions?.total_count) {
// they have at least one subscription of some form -- so that's enough to count.
return true;
}
// If they manage any licenses then they also count:
return await is_a_site_license_manager(db, account_id);
}
interface SetAccountFields {
db: PostgreSQL;
account_id: string;
email_address?: string | undefined;
first_name?: string | undefined;
last_name?: string | undefined;
}
// this is like set_account_info_if_different, but only sets the fields if they're not set
export async function set_account_info_if_not_set(
opts: SetAccountFields
): Promise<void> {
return await set_account_info_if_different(opts, false);
}
// 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
export async function set_account_info_if_different(
opts: SetAccountFields,
overwrite = true
): Promise<void> {
const columns = ["email_address", "first_name", "last_name"];
// this could throw an error for "no such account"
const account = await get_account(opts.db, opts.account_id, columns);
const do_set: { [field: string]: string } = {};
let do_email: string | undefined = undefined;
for (const field of columns) {
if (typeof opts[field] !== "string") continue;
if (!overwrite && account[field] != null) continue;
if (account[field] != opts[field]) {
if (field === "email_address") {
do_email = opts[field];
} else {
do_set[field] = opts[field];
}
}
}
if (len(do_set) > 0) {
await set_account(opts.db, opts.account_id, do_set);
}
if (do_email) {
if (account["email_address"] != null) {
// if it changes, we have to call the change_email_address function
await callback2(opts.db.change_email_address.bind(opts.db), {
account_id: opts.account_id,
email_address: do_email,
});
}
// Just changed email address - might be added to a project...
await callback2(opts.db.do_account_creation_actions.bind(opts.db), {
email_address: do_email,
account_id: opts.account_id,
});
}
}
export async function set_account(
db: PostgreSQL,
account_id: string,
set: { [field: string]: any }
): Promise<void> {
await db.async_query({
query: "UPDATE accounts",
where: { "account_id = $::UUID": account_id },
set,
});
}
export async function get_account(
db: PostgreSQL,
account_id: string,
columns: string[]
): Promise<void> {
return await callback2(db.get_account.bind(db), {
account_id,
columns,
});
}
interface SetEmailAddressVerifiedOpts {
db: PostgreSQL;
account_id: string;
email_address: string;
}
export async function set_email_address_verified(
opts: SetEmailAddressVerifiedOpts
): Promise<void> {
const { db, account_id, email_address } = opts;
assert_valid_account_id(account_id);
assert_valid_email_address(email_address);
return await db.async_query({
query: "UPDATE accounts",
jsonb_set: { email_address_verified: { [email_address]: new Date() } },
where: { "account_id = $::UUID": account_id },
});
}