Skip to content

Commit 91ff681

Browse files
authored
Remove Stately paths (#303)
* Postgres only settings, sync * Remove stately from profile * Remove Stately from update * Remove Stately from import/export * Remove Stately from delete-all-data * Update tests
1 parent 14e7f2c commit 91ff681

7 files changed

Lines changed: 66 additions & 470 deletions

File tree

api/db/settings-queries.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,32 @@ export async function getSettings(
2626
: undefined;
2727
}
2828

29+
/**
30+
* Get settings for a particular account.
31+
*/
32+
export async function syncSettings(
33+
client: ClientBase,
34+
bungieMembershipId: number,
35+
syncTimestamp: number,
36+
): Promise<{ settings: Partial<Settings>; deleted: boolean; lastModifiedAt: number } | undefined> {
37+
const results = await client.query<{
38+
settings: Partial<Settings>;
39+
deleted_at: Date | null;
40+
last_updated_at: Date;
41+
}>({
42+
name: 'sync_settings',
43+
text: 'SELECT settings, deleted_at, last_updated_at FROM settings WHERE membership_id = $1 and last_updated_at > $2',
44+
values: [bungieMembershipId, new Date(syncTimestamp)],
45+
});
46+
return results.rows.length > 0
47+
? {
48+
settings: results.rows[0].settings,
49+
deleted: Boolean(results.rows[0].deleted_at),
50+
lastModifiedAt: results.rows[0].last_updated_at.getTime(),
51+
}
52+
: undefined;
53+
}
54+
2955
/**
3056
* Insert or update (upsert) an entire settings tree, totally replacing whatever's there.
3157
*/

api/routes/delete-all-data.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,21 @@ import { softDeleteAllTrackedTriumphs } from '../db/triumphs-queries.js';
1111
import { DeleteAllResponse } from '../shapes/delete-all.js';
1212
import { DestinyVersion } from '../shapes/general.js';
1313
import { UserInfo } from '../shapes/user.js';
14-
import { deleteAllDataForUser } from '../stately/bulk-queries.js';
1514

1615
/**
1716
* Delete My Data - this allows a user to wipe all their data from DIM storage.
1817
*/
1918
export const deleteAllDataHandler = asyncHandler(async (req, res) => {
2019
const { bungieMembershipId, profileIds } = req.user as UserInfo;
2120

22-
let result = await deleteAllDataForUser(bungieMembershipId, profileIds);
21+
let result = {
22+
settings: 1,
23+
loadouts: 0,
24+
tags: 0,
25+
itemHashTags: 0,
26+
triumphs: 0,
27+
searches: 0,
28+
};
2329

2430
await transaction(async (client) => {
2531
await deleteSettings(client, bungieMembershipId);

api/routes/export.ts

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,12 @@ import { readTransaction } from '../db/index.js';
44
import { getItemAnnotationsForProfile } from '../db/item-annotations-queries.js';
55
import { getItemHashTagsForProfile } from '../db/item-hash-tags-queries.js';
66
import { getLoadoutsForProfile } from '../db/loadouts-queries.js';
7-
import { getMigrationState, MigrationState } from '../db/migration-state-queries.js';
87
import { getSearchesForProfile } from '../db/searches-queries.js';
98
import { getSettings as getSettingsFromPostgres } from '../db/settings-queries.js';
109
import { getTrackedTriumphsForProfile } from '../db/triumphs-queries.js';
1110
import { ExportResponse } from '../shapes/export.js';
1211
import { DestinyVersion } from '../shapes/general.js';
13-
import { defaultSettings, Settings } from '../shapes/settings.js';
1412
import { UserInfo } from '../shapes/user.js';
15-
import { exportDataForProfile } from '../stately/bulk-queries.js';
16-
import { getSettings } from '../stately/settings-queries.js';
17-
import { subtractObject } from '../utils.js';
1813

1914
export const exportHandler = asyncHandler(async (req, res) => {
2015
const { bungieMembershipId, profileIds } = req.user as UserInfo;
@@ -31,20 +26,11 @@ export const exportHandler = asyncHandler(async (req, res) => {
3126
};
3227

3328
for (const profileId of profileIds) {
34-
const migrationState = await readTransaction(async (client) =>
35-
getMigrationState(client, profileId),
36-
);
37-
38-
let partialResponse: ExportResponse;
39-
if (migrationState.state === MigrationState.Postgres) {
40-
partialResponse = await readTransaction(async (client) => {
41-
const d1Response = await pgExport(client, profileId, 1);
42-
const d2Response = await pgExport(client, profileId, 2);
43-
return mergeResponses(d1Response, d2Response);
44-
});
45-
} else {
46-
partialResponse = await exportDataForProfile(profileId);
47-
}
29+
const partialResponse = await readTransaction(async (client) => {
30+
const d1Response = await pgExport(client, profileId, 1);
31+
const d2Response = await pgExport(client, profileId, 2);
32+
return mergeResponses(d1Response, d2Response);
33+
});
4834

4935
response = mergeResponses(response, partialResponse);
5036
}
@@ -71,16 +57,10 @@ function mergeResponses(base: ExportResponse, addition: ExportResponse): ExportR
7157
export async function exportSettings(
7258
bungieMembershipId: number,
7359
): Promise<ExportResponse['settings']> {
74-
let settings: Partial<Settings>;
7560
const pgSettings = await readTransaction((client) =>
7661
getSettingsFromPostgres(client, bungieMembershipId),
7762
);
78-
if (pgSettings) {
79-
settings = pgSettings.settings;
80-
} else {
81-
settings = subtractObject((await getSettings(bungieMembershipId)) ?? {}, defaultSettings);
82-
}
83-
return settings;
63+
return pgSettings?.settings ?? {};
8464
}
8565

8666
export async function pgExport(

api/routes/import.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
} from '../db/item-annotations-queries.js';
88
import { softDeleteAllItemHashTags, updateItemHashTag } from '../db/item-hash-tags-queries.js';
99
import { softDeleteAllLoadouts, updateLoadout } from '../db/loadouts-queries.js';
10-
import { doMigration, getMigrationState, MigrationState } from '../db/migration-state-queries.js';
1110
import { importSearch, softDeleteAllSearches } from '../db/searches-queries.js';
1211
import { replaceSettings } from '../db/settings-queries.js';
1312
import { softDeleteAllTrackedTriumphs, trackTriumph } from '../db/triumphs-queries.js';
@@ -18,7 +17,6 @@ import { ItemAnnotation, ItemHashTag } from '../shapes/item-annotations.js';
1817
import { Loadout } from '../shapes/loadouts.js';
1918
import { defaultSettings, Settings } from '../shapes/settings.js';
2019
import { UserInfo } from '../shapes/user.js';
21-
import { deleteAllDataForUser } from '../stately/bulk-queries.js';
2220
import { badRequest, subtractObject } from '../utils.js';
2321

2422
export const importHandler = asyncHandler(async (req, res) => {
@@ -120,25 +118,7 @@ export const importHandler = asyncHandler(async (req, res) => {
120118
response.itemHashTags += importResp.itemHashTags;
121119
};
122120

123-
const migrationState = await transaction(async (client) =>
124-
getMigrationState(client, profileId),
125-
);
126-
127-
if (migrationState.state === MigrationState.MigratingToPostgres) {
128-
badRequest(
129-
res,
130-
`Unable to import data for profile ${profileId} - migration in progress. Please wait a bit and try again.`,
131-
);
132-
return;
133-
}
134-
135-
if (migrationState.state === MigrationState.Stately) {
136-
await doMigration(bungieMembershipId, profileId, doImport, async () =>
137-
deleteAllDataForUser(bungieMembershipId, [profileId]),
138-
);
139-
} else {
140-
await doImport();
141-
}
121+
await doImport();
142122
}
143123

144124
// default 200 OK

api/routes/profile.ts

Lines changed: 16 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as Sentry from '@sentry/node';
2-
import { ListToken } from '@stately-cloud/client';
32
import express from 'express';
43
import asyncHandler from 'express-async-handler';
54
import { readTransaction } from '../db/index.js';
@@ -12,9 +11,8 @@ import {
1211
syncItemHashTagsForProfile,
1312
} from '../db/item-hash-tags-queries.js';
1413
import { getLoadoutsForProfile, syncLoadoutsForProfile } from '../db/loadouts-queries.js';
15-
import { getMigrationState, MigrationState } from '../db/migration-state-queries.js';
1614
import { getSearchesForProfile, syncSearchesForProfile } from '../db/searches-queries.js';
17-
import { getSettings } from '../db/settings-queries.js';
15+
import { getSettings, syncSettings } from '../db/settings-queries.js';
1816
import {
1917
getTrackedTriumphsForProfile,
2018
syncTrackedTriumphsForProfile,
@@ -26,8 +24,6 @@ import { ProfileResponse } from '../shapes/profile.js';
2624
import { Search, SearchType } from '../shapes/search.js';
2725
import { defaultSettings } from '../shapes/settings.js';
2826
import { UserInfo } from '../shapes/user.js';
29-
import { getProfile, syncProfile } from '../stately/bulk-queries.js';
30-
import { querySettings, syncSettings } from '../stately/settings-queries.js';
3127
import { badRequest, checkPlatformMembershipId, isValidPlatformMembershipId } from '../utils.js';
3228

3329
type ProfileComponent = 'settings' | 'loadouts' | 'tags' | 'hashtags' | 'triumphs' | 'searches';
@@ -224,21 +220,15 @@ async function loadProfile(
224220
destinyVersion: DestinyVersion,
225221
incomingSyncTokens?: { [component: string]: Buffer | number },
226222
) {
227-
let response: ProfileResponse = {
223+
const response: ProfileResponse = {
228224
sync: Boolean(incomingSyncTokens),
229225
};
230226
const timerPrefix = response.sync ? 'profileSync' : 'profileStately';
231227
const counterPrefix = response.sync ? 'sync' : 'stately';
232-
const syncTokens: { [component: string]: string | number } = {};
233-
const addSyncToken = (
234-
name: string,
235-
token: ListToken | { canSync: boolean; tokenData: number },
236-
) => {
228+
const syncTokens: { [component: string]: number } = {};
229+
const addSyncToken = (name: string, token: { canSync: boolean; tokenData: number }) => {
237230
if (token.canSync) {
238-
syncTokens[name] =
239-
token.tokenData instanceof Uint8Array
240-
? Buffer.from(token.tokenData).toString('base64')
241-
: token.tokenData;
231+
syncTokens[name] = token.tokenData;
242232
}
243233
};
244234
const getSyncToken = <T extends number | Buffer>(name: string) => {
@@ -256,55 +246,38 @@ async function loadProfile(
256246
// TODO: should settings be stored under profile too?? maybe primary profile ID?
257247
promises.push(
258248
(async () => {
259-
// Load settings from Postgres. If they're there, you're done. Otherwise load from Stately.
260249
const start = new Date();
261-
262250
const now = Date.now();
251+
const tokenData = getSyncToken<number>('s');
263252
// TODO: Should add the token to the query to avoid fetching if unchanged
264253
const pgSettings = await readTransaction(async (pgClient) =>
265-
getSettings(pgClient, bungieMembershipId),
254+
tokenData
255+
? syncSettings(pgClient, bungieMembershipId, tokenData)
256+
: getSettings(pgClient, bungieMembershipId),
266257
);
267-
if (pgSettings) {
268-
const tokenData = getSyncToken<number>('s');
269-
if (tokenData === undefined || pgSettings.lastModifiedAt > tokenData) {
270-
response.settings = { ...defaultSettings, ...pgSettings.settings };
271-
}
272-
addSyncToken('s', { canSync: true, tokenData: now });
273-
} else {
274-
const tokenData = getSyncToken<Buffer>('settings');
275-
const { settings: storedSettings, token: settingsToken } = tokenData
276-
? await syncSettings(tokenData)
277-
: await querySettings(bungieMembershipId);
278-
response.settings = storedSettings;
279-
addSyncToken('settings', settingsToken);
258+
if (
259+
tokenData === undefined ||
260+
(pgSettings !== undefined && pgSettings.lastModifiedAt > tokenData)
261+
) {
262+
response.settings = { ...defaultSettings, ...pgSettings?.settings };
280263
}
264+
addSyncToken('s', { canSync: true, tokenData: now });
281265

282266
metrics.timing(`${timerPrefix}.settings`, start);
283267
})(),
284268
);
285269
}
286270

287-
let loadFromPostgres = false;
288271
if (
289-
platformMembershipId &&
290272
(['loadouts', 'tags', 'hashtags', 'triumphs', 'searches'] as const).some((c) =>
291273
components.includes(c),
292274
)
293275
) {
294-
const { state: migrationState } = await readTransaction(async (client) =>
295-
getMigrationState(client, platformMembershipId),
296-
);
297-
298-
if (migrationState === MigrationState.Postgres) {
299-
loadFromPostgres = true;
300-
}
301-
}
302-
303-
if (loadFromPostgres) {
304276
if (!platformMembershipId) {
305277
badRequest(res, `Need a platformMembershipId to return ${components.join(', ')}`);
306278
return;
307279
}
280+
308281
promises.push(
309282
(async () => {
310283
const now = Date.now();
@@ -446,71 +419,6 @@ async function loadProfile(
446419
});
447420
})(),
448421
);
449-
} else {
450-
// Special case: DIM wants everything, so we can get it in a single query
451-
if (
452-
platformMembershipId &&
453-
(['loadouts', 'tags', 'hashtags', 'triumphs', 'searches'] as const).every((c) =>
454-
components.includes(c),
455-
)
456-
) {
457-
// Replace the individual components with a bulk fetch
458-
components = components.includes('settings') ? ['settings', 'p'] : ['p'];
459-
}
460-
461-
const loadComponent = (
462-
name: Exclude<ProfileComponent, 'settings'> | 'p',
463-
suffix: string,
464-
handleEmpty: () => void,
465-
) => {
466-
if (components.includes(name)) {
467-
if (!platformMembershipId) {
468-
badRequest(res, `Need a platformMembershipId to return ${name}`);
469-
return;
470-
}
471-
promises.push(
472-
(async () => {
473-
const start = new Date();
474-
const tokenData = getSyncToken<Buffer>(name);
475-
const { profile, token } = tokenData
476-
? await syncProfile(tokenData)
477-
: await getProfile(platformMembershipId, destinyVersion, suffix);
478-
response = { ...response, ...profile };
479-
if (!tokenData) {
480-
handleEmpty();
481-
}
482-
addSyncToken(name, token);
483-
metrics.timing(`${timerPrefix}.${name}`, start);
484-
})(),
485-
);
486-
}
487-
};
488-
489-
loadComponent('p', '', () => {
490-
response.loadouts ??= [];
491-
response.searches ??= [];
492-
response.tags ??= [];
493-
response.itemHashTags ??= [];
494-
response.triumphs ??= [];
495-
response.searches ??= [];
496-
});
497-
loadComponent('loadouts', '/loadout', () => {
498-
response.loadouts ??= [];
499-
});
500-
loadComponent('tags', '/ia', () => {
501-
response.tags ??= [];
502-
});
503-
if (destinyVersion === 2) {
504-
loadComponent('hashtags', '/iht', () => {
505-
response.itemHashTags ??= [];
506-
});
507-
}
508-
loadComponent('triumphs', '/triumph', () => {
509-
response.triumphs ??= [];
510-
});
511-
loadComponent('searches', '/search', () => {
512-
response.searches ??= [];
513-
});
514422
}
515423

516424
await Promise.all(promises);

0 commit comments

Comments
 (0)