Skip to content

Commit e79a8ad

Browse files
committed
database/postgres: drop upgrades and stripe sync
1 parent 2466a6b commit e79a8ad

File tree

10 files changed

+44
-398
lines changed

10 files changed

+44
-398
lines changed

src/packages/database/postgres.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ import { calc_stats } from "./postgres/stats/stats";
143143
import { default as registrationTokens } from "./postgres/account/registration-tokens";
144144
import { default as centralLog } from "./postgres/central-log";
145145
import { updateUnreadMessageCount } from "./postgres/changefeed/messages";
146+
import membershipTiers from "./postgres/membership-tiers";
146147

147148
import {
148149
get_client_error_log,
@@ -1938,6 +1939,10 @@ export class PostgreSQL extends EventEmitter implements PostgreSQLMethods {
19381939
return await registrationTokens(this, options, query);
19391940
}
19401941

1942+
async membershipTiers(options, query) {
1943+
return await membershipTiers(this, options, query);
1944+
}
1945+
19411946
async updateUnreadMessageCount(
19421947
opts: FunctionOpts<typeof updateUnreadMessageCount>,
19431948
) {

src/packages/database/postgres/account/change-email-address.test.ts

Lines changed: 8 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* This file is part of CoCalc: Copyright © 2025 Sagemath, Inc.
2+
* This file is part of CoCalc: Copyright © 2025-2026 Sagemath, Inc.
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

@@ -67,64 +67,13 @@ describe("change_email_address", () => {
6767
return result.rows?.[0]?.email_address;
6868
}
6969

70-
/**
71-
* Helper to set stripe_customer_id for an account
72-
*/
73-
async function setStripeCustomerId(
74-
account_id: string,
75-
customer_id: string,
76-
): Promise<void> {
77-
await database.async_query({
78-
query: "UPDATE accounts",
79-
set: { stripe_customer_id: customer_id },
80-
where: { "account_id = $::UUID": account_id },
81-
});
82-
}
83-
84-
/**
85-
* Mock Stripe client
86-
*/
87-
function createMockStripe(options?: {
88-
shouldThrow?: boolean;
89-
errorMessage?: string;
90-
}) {
91-
const calls: any[] = [];
92-
return {
93-
customers: {
94-
retrieve: jest.fn(async (customer_id) => {
95-
calls.push({ method: "retrieve", customer_id });
96-
if (options?.shouldThrow) {
97-
throw new Error(options.errorMessage ?? "Stripe API error");
98-
}
99-
return {
100-
id: customer_id,
101-
email: "old@example.com",
102-
subscriptions: { data: [] },
103-
};
104-
}),
105-
update: jest.fn(async (customer_id, update) => {
106-
calls.push({ method: "update", customer_id, update });
107-
if (options?.shouldThrow) {
108-
throw new Error(options.errorMessage ?? "Stripe API error");
109-
}
110-
return {
111-
id: customer_id,
112-
email: update.email,
113-
subscriptions: { data: [] },
114-
};
115-
}),
116-
},
117-
_calls: calls,
118-
};
119-
}
120-
12170
describe("successful email change", () => {
12271
it("should change email address when new email is not taken", async () => {
12372
const unique_id = uuid().substring(0, 8);
12473
const account_id = await createTestAccount(
12574
`original-${unique_id}@example.com`,
12675
);
127-
const stripe = createMockStripe();
76+
const stripe = {} as any;
12877

12978
await change_email_address_wrapper({
13079
account_id,
@@ -135,46 +84,6 @@ describe("change_email_address", () => {
13584
const email = await getAccountEmail(account_id);
13685
expect(email).toBe(`new-${unique_id}@example.com`);
13786
});
138-
139-
it("should not call Stripe when account has no stripe_customer_id", async () => {
140-
const unique_id = uuid().substring(0, 8);
141-
const account_id = await createTestAccount(
142-
`test1-${unique_id}@example.com`,
143-
);
144-
const stripe = createMockStripe();
145-
146-
await change_email_address_wrapper({
147-
account_id,
148-
email_address: `test1-new-${unique_id}@example.com`,
149-
stripe,
150-
});
151-
152-
expect(stripe.customers.retrieve).not.toHaveBeenCalled();
153-
expect(stripe.customers.update).not.toHaveBeenCalled();
154-
});
155-
156-
it("should call Stripe sync when account has stripe_customer_id", async () => {
157-
const unique_id = uuid().substring(0, 8);
158-
const account_id = await createTestAccount(
159-
`test2-${unique_id}@example.com`,
160-
);
161-
const customer_id = "cus_" + uuid().replace(/-/g, "");
162-
await setStripeCustomerId(account_id, customer_id);
163-
164-
const stripe = createMockStripe();
165-
166-
await change_email_address_wrapper({
167-
account_id,
168-
email_address: `test2-new-${unique_id}@example.com`,
169-
stripe,
170-
});
171-
172-
const email = await getAccountEmail(account_id);
173-
expect(email).toBe(`test2-new-${unique_id}@example.com`);
174-
expect(stripe.customers.retrieve).toHaveBeenCalledWith(customer_id, {
175-
expand: ["sources", "subscriptions"],
176-
});
177-
});
17887
});
17988

18089
describe("email_already_taken error", () => {
@@ -184,7 +93,7 @@ describe("change_email_address", () => {
18493
const email2 = `user2-${unique_id}@example.com`;
18594
const account_id1 = await createTestAccount(email1);
18695
await createTestAccount(email2);
187-
const stripe = createMockStripe();
96+
const stripe = {} as any;
18897

18998
// Try to change account_id1 to user2 email (already taken)
19099
await expect(
@@ -206,9 +115,7 @@ describe("change_email_address", () => {
206115
const email2 = `taken2-${unique_id}@example.com`;
207116
const account_id1 = await createTestAccount(email1);
208117
await createTestAccount(email2);
209-
await setStripeCustomerId(account_id1, "cus_test" + unique_id);
210-
211-
const stripe = createMockStripe();
118+
const stripe = {} as any;
212119

213120
await expect(
214121
change_email_address_wrapper({
@@ -217,58 +124,6 @@ describe("change_email_address", () => {
217124
stripe,
218125
}),
219126
).rejects.toMatch(/email_already_taken/);
220-
221-
expect(stripe.customers.retrieve).not.toHaveBeenCalled();
222-
});
223-
});
224-
225-
describe("Stripe synchronization errors", () => {
226-
it("should propagate Stripe errors when sync fails", async () => {
227-
const unique_id = uuid().substring(0, 8);
228-
const account_id = await createTestAccount(
229-
`stripe-error-${unique_id}@example.com`,
230-
);
231-
const customer_id = "cus_" + uuid().replace(/-/g, "");
232-
await setStripeCustomerId(account_id, customer_id);
233-
234-
const stripe = createMockStripe({
235-
shouldThrow: true,
236-
errorMessage: "Stripe service unavailable",
237-
});
238-
239-
await expect(
240-
change_email_address_wrapper({
241-
account_id,
242-
email_address: `stripe-error-new-${unique_id}@example.com`,
243-
stripe,
244-
}),
245-
).rejects.toThrow(/Stripe service unavailable/);
246-
});
247-
248-
it("should still update email in database even if Stripe sync fails", async () => {
249-
const unique_id = uuid().substring(0, 8);
250-
const account_id = await createTestAccount(
251-
`stripe-error2-${unique_id}@example.com`,
252-
);
253-
const customer_id = "cus_" + uuid().replace(/-/g, "");
254-
await setStripeCustomerId(account_id, customer_id);
255-
256-
const stripe = createMockStripe({
257-
shouldThrow: true,
258-
errorMessage: "Network error",
259-
});
260-
261-
await expect(
262-
change_email_address_wrapper({
263-
account_id,
264-
email_address: `stripe-error2-new-${unique_id}@example.com`,
265-
stripe,
266-
}),
267-
).rejects.toThrow(/Network error/);
268-
269-
// Email should be updated despite Stripe error (happens in step 2, before Stripe sync in step 3)
270-
const email = await getAccountEmail(account_id);
271-
expect(email).toBe(`stripe-error2-new-${unique_id}@example.com`);
272127
});
273128
});
274129

@@ -277,7 +132,7 @@ describe("change_email_address", () => {
277132
const unique_id = uuid().substring(0, 8);
278133
const email = `same-${unique_id}@example.com`;
279134
const account_id = await createTestAccount(email);
280-
const stripe = createMockStripe();
135+
const stripe = {} as any;
281136

282137
// CoffeeScript checks account_exists which finds the same account's email
283138
await expect(
@@ -298,7 +153,7 @@ describe("change_email_address", () => {
298153
const account_id = await createTestAccount(
299154
`lower-${unique_id}@example.com`,
300155
);
301-
const stripe = createMockStripe();
156+
const stripe = {} as any;
302157

303158
await change_email_address_wrapper({
304159
account_id,
@@ -316,7 +171,7 @@ describe("change_email_address", () => {
316171
const email2 = `other-${unique_id}@example.com`;
317172
await createTestAccount(email1);
318173
const account_id2 = await createTestAccount(email2);
319-
const stripe = createMockStripe();
174+
const stripe = {} as any;
320175

321176
// CoffeeScript uses = which is case-sensitive, so lowercase version is allowed
322177
await change_email_address_wrapper({
@@ -336,7 +191,7 @@ describe("change_email_address", () => {
336191
const account_id = await createTestAccount(
337192
`concurrent-${unique_id}@example.com`,
338193
);
339-
const stripe = createMockStripe();
194+
const stripe = {} as any;
340195

341196
await change_email_address_wrapper({
342197
account_id,

src/packages/database/postgres/account/change-email-address.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
/*
2-
* This file is part of CoCalc: Copyright © 2025 Sagemath, Inc.
2+
* This file is part of CoCalc: Copyright © 2025-2026 Sagemath, Inc.
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

66
import { account_exists } from "./basic";
7-
import syncCustomer from "../stripe/sync-customer";
87
import type { PostgreSQL } from "../types";
98

109
interface ChangeEmailAddressOptions {
@@ -17,7 +16,6 @@ interface ChangeEmailAddressOptions {
1716
* Change the email address for an account.
1817
*
1918
* Throws "email_already_taken" error (string) if the email is already in use.
20-
* Calls Stripe sync if account has stripe_customer_id.
2119
*
2220
* NOTE: Matches CoffeeScript behavior but fixes bug where undefined account
2321
* would crash when accessing stripe_customer_id.
@@ -48,22 +46,4 @@ export async function changeEmailAddress(
4846
set: { email_address: opts.email_address },
4947
where: { "account_id = $::UUID": opts.account_id },
5048
});
51-
52-
// Step 3: Sync with Stripe if customer exists
53-
const result = await db.async_query({
54-
query: "SELECT stripe_customer_id FROM accounts",
55-
where: { "account_id = $::UUID": opts.account_id },
56-
});
57-
58-
const row = result.rows?.[0];
59-
60-
// FIX: Check if row exists before accessing stripe_customer_id
61-
// This fixes the CoffeeScript bug where undefined account would crash
62-
if (row?.stripe_customer_id) {
63-
await syncCustomer({
64-
account_id: opts.account_id,
65-
stripe: opts.stripe,
66-
customer_id: row.stripe_customer_id,
67-
});
68-
}
6949
}

src/packages/database/postgres/project/queries.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* This file is part of CoCalc: Copyright © 2020 – 2025 Sagemath, Inc.
2+
* This file is part of CoCalc: Copyright © 2020–2026 Sagemath, Inc.
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

@@ -44,17 +44,6 @@ export async function project_has_network_access(
4444
if (x.settings != null && x.settings.network) {
4545
return true;
4646
}
47-
if (x.users != null) {
48-
for (const account_id in x.users) {
49-
if (
50-
x.users[account_id] != null &&
51-
x.users[account_id].upgrades != null &&
52-
x.users[account_id].upgrades.network
53-
) {
54-
return true;
55-
}
56-
}
57-
}
5847
return false;
5948
}
6049

src/packages/database/postgres/project/user-set-query-project-users.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6-
import { PROJECT_UPGRADES } from "@cocalc/util/schema";
76
import {
87
assert_valid_account_id,
98
is_object,
@@ -15,15 +14,14 @@ type UserGroup = "owner" | "collaborator";
1514
type AllowedUserFields = {
1615
group?: UserGroup;
1716
hide?: boolean;
18-
upgrades?: Record<string, unknown>;
1917
ssh_keys?: Record<string, Record<string, unknown> | undefined>;
2018
};
2119

2220
function ensureAllowedKeys(
2321
user: Record<string, unknown>,
2422
allowGroupChanges: boolean,
2523
): void {
26-
const allowed = new Set(["hide", "upgrades", "ssh_keys"]);
24+
const allowed = new Set(["hide", "ssh_keys"]);
2725
for (const key of Object.keys(user)) {
2826
if (key === "group") {
2927
if (!allowGroupChanges) {
@@ -39,19 +37,6 @@ function ensureAllowedKeys(
3937
}
4038
}
4139

42-
function sanitizeUpgrades(upgrades: unknown): Record<string, unknown> {
43-
if (!is_object(upgrades)) {
44-
throw Error("invalid type for field 'upgrades'");
45-
}
46-
const allowedUpgrades = PROJECT_UPGRADES.params;
47-
for (const key of Object.keys(upgrades)) {
48-
if (!Object.prototype.hasOwnProperty.call(allowedUpgrades, key)) {
49-
throw Error(`invalid upgrades field '${key}'`);
50-
}
51-
}
52-
return upgrades as Record<string, unknown>;
53-
}
54-
5540
function sanitizeSshKeys(
5641
ssh_keys: unknown,
5742
): Record<string, Record<string, unknown> | undefined> {
@@ -83,7 +68,7 @@ function sanitizeSshKeys(
8368
/**
8469
* Sanitize and security-check project user mutations submitted via user set query.
8570
*
86-
* Only permits modifying the requesting user's own entry (hide/upgrades/ssh_keys).
71+
* Only permits modifying the requesting user's own entry (hide/ssh_keys).
8772
* Collaborator role changes must use dedicated APIs that enforce ownership rules.
8873
*/
8974
export function sanitizeUserSetQueryProjectUsers(
@@ -136,14 +121,6 @@ export function sanitizeUserSetQueryProjectUsers(
136121
}
137122
entry.hide = (user as any).hide;
138123
}
139-
if ("upgrades" in user) {
140-
if (!isSelf) {
141-
throw Error(
142-
"users set queries may only change upgrades for the requesting account",
143-
);
144-
}
145-
entry.upgrades = sanitizeUpgrades((user as any).upgrades);
146-
}
147124
if ("ssh_keys" in user) {
148125
if (!isSelf) {
149126
throw Error(

0 commit comments

Comments
 (0)