@@ -2,10 +2,13 @@ import * as Sentry from '@sentry/node';
22import { ListToken } from '@stately-cloud/client' ;
33import express from 'express' ;
44import asyncHandler from 'express-async-handler' ;
5+ import { readTransaction } from '../db/index.js' ;
6+ import { getSettings } from '../db/settings-queries.js' ;
57import { metrics } from '../metrics/index.js' ;
68import { ApiApp } from '../shapes/app.js' ;
79import { DestinyVersion } from '../shapes/general.js' ;
810import { ProfileResponse } from '../shapes/profile.js' ;
11+ import { defaultSettings } from '../shapes/settings.js' ;
912import { UserInfo } from '../shapes/user.js' ;
1013import { getProfile , syncProfile } from '../stately/bulk-queries.js' ;
1114import { cannedSearches } from '../stately/searches-queries.js' ;
@@ -141,10 +144,10 @@ function extractSyncToken(syncTokenParam: string | undefined) {
141144 }
142145
143146 try {
144- const tokenMap = JSON . parse ( syncTokenParam ) as { [ component : string ] : string } ;
145- return Object . entries ( tokenMap ) . reduce < { [ component : string ] : Buffer } > (
147+ const tokenMap = JSON . parse ( syncTokenParam ) as { [ component : string ] : string | number } ;
148+ return Object . entries ( tokenMap ) . reduce < { [ component : string ] : Buffer | number } > (
146149 ( acc , [ component , token ] ) => {
147- acc [ component ] = Buffer . from ( token , 'base64' ) ;
150+ acc [ component ] = typeof token === 'string' ? Buffer . from ( token , 'base64' ) : token ;
148151 return acc ;
149152 } ,
150153 { } ,
@@ -162,40 +165,70 @@ async function statelyProfile(
162165 bungieMembershipId : number ,
163166 platformMembershipId : string | undefined ,
164167 destinyVersion : DestinyVersion ,
165- incomingSyncTokens ?: { [ component : string ] : Buffer } ,
168+ incomingSyncTokens ?: { [ component : string ] : Buffer | number } ,
166169) {
167170 let response : ProfileResponse = {
168171 sync : Boolean ( incomingSyncTokens ) ,
169172 } ;
170173 const timerPrefix = response . sync ? 'profileSync' : 'profileStately' ;
171174 const counterPrefix = response . sync ? 'sync' : 'stately' ;
172175 const syncTokens : { [ component : string ] : string } = { } ;
173- const addSyncToken = ( name : string , token : ListToken ) => {
176+ const addSyncToken = (
177+ name : string ,
178+ token : ListToken | { canSync : boolean ; tokenData : number } ,
179+ ) => {
174180 if ( token . canSync ) {
175- syncTokens [ name ] = Buffer . from ( token . tokenData ) . toString ( 'base64' ) ;
181+ syncTokens [ name ] =
182+ token . tokenData instanceof Uint8Array
183+ ? Buffer . from ( token . tokenData ) . toString ( 'base64' )
184+ : token . tokenData . toString ( ) ;
176185 }
177186 } ;
178- const getSyncToken = ( name : string ) => {
187+ const getSyncToken = < T extends number | Buffer > ( name : string ) => {
179188 const tokenData = incomingSyncTokens ?. [ name ] ;
180189 if ( incomingSyncTokens && ! tokenData ) {
181190 throw new Error ( `Missing sync token: ${ name } ` ) ;
182191 }
183- return tokenData ;
192+ return tokenData as T | undefined ;
184193 } ;
185194
186195 // We'll accumulate promises and await them all at the end
187196 const promises : Promise < void > [ ] = [ ] ;
197+
188198 if ( components . includes ( 'settings' ) ) {
189199 // TODO: should settings be stored under profile too?? maybe primary profile ID?
190200 promises . push (
191201 ( async ( ) => {
202+ // Load settings from Stately. If they're there, you're done. Otherwise load from Postgres.
192203 const start = new Date ( ) ;
193- const tokenData = getSyncToken ( 'settings' ) ;
194- const { settings : storedSettings , token : settingsToken } = tokenData
195- ? await syncSettings ( tokenData )
196- : await querySettings ( bungieMembershipId ) ;
197- response . settings = storedSettings ;
198- addSyncToken ( 'settings' , settingsToken ) ;
204+
205+ const statelySettings = await querySettings ( bungieMembershipId ) ;
206+ if ( ! statelySettings . settings ) {
207+ const now = Date . now ( ) ;
208+ const pgSettings = await readTransaction ( async ( pgClient ) =>
209+ getSettings ( pgClient , bungieMembershipId ) ,
210+ ) ;
211+ if ( pgSettings ) {
212+ const tokenData = getSyncToken < number > ( 's' ) ;
213+ if ( tokenData === undefined || pgSettings . lastModifiedAt > tokenData ) {
214+ response . settings = { ...defaultSettings , ...pgSettings . settings } ;
215+ }
216+ } else {
217+ response . settings = defaultSettings ;
218+ }
219+ addSyncToken ( 's' , { canSync : true , tokenData : pgSettings ?. lastModifiedAt ?? now } ) ;
220+ } else {
221+ const tokenData = getSyncToken < Buffer > ( 'settings' ) ;
222+ const { settings : storedSettings , token : settingsToken } = tokenData
223+ ? await syncSettings ( tokenData )
224+ : {
225+ settings : statelySettings . settings ?? defaultSettings ,
226+ token : statelySettings . token ,
227+ } ;
228+ response . settings = storedSettings ;
229+ addSyncToken ( 'settings' , settingsToken ) ;
230+ }
231+
199232 metrics . timing ( `${ timerPrefix } .settings` , start ) ;
200233 } ) ( ) ,
201234 ) ;
@@ -225,7 +258,7 @@ async function statelyProfile(
225258 promises . push (
226259 ( async ( ) => {
227260 const start = new Date ( ) ;
228- const tokenData = getSyncToken ( name ) ;
261+ const tokenData = getSyncToken < Buffer > ( name ) ;
229262 const { profile, token } = tokenData
230263 ? await syncProfile ( tokenData )
231264 : await getProfile ( platformMembershipId , destinyVersion , suffix ) ;
0 commit comments