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