diff --git a/api/db/apps-queries.test.ts b/api/db/apps-queries.test.ts index 3457b51..9341904 100644 --- a/api/db/apps-queries.test.ts +++ b/api/db/apps-queries.test.ts @@ -1,7 +1,7 @@ import { v4 as uuid } from 'uuid'; import { ApiApp } from '../shapes/app.js'; import { getAllApps, getAppById, insertApp } from './apps-queries.js'; -import { pool, transaction } from './index.js'; +import { closeDbPool, pool, transaction } from './index.js'; const appId = 'apps-queries-test-app'; const app: ApiApp = { @@ -13,7 +13,7 @@ const app: ApiApp = { beforeEach(() => pool.query({ text: 'delete from apps where id = $1', values: [appId] })); -afterAll(() => pool.end()); +afterAll(() => closeDbPool()); it('can create a new app', async () => { await transaction(async (client) => { diff --git a/api/db/index.test.ts b/api/db/index.test.ts index 63c2b32..6c098fb 100644 --- a/api/db/index.test.ts +++ b/api/db/index.test.ts @@ -1,4 +1,4 @@ -import { pool, readTransaction, transaction } from './index.js'; +import { closeDbPool, pool, readTransaction, transaction } from './index.js'; beforeEach(async () => { try { @@ -14,7 +14,7 @@ afterAll(async () => { try { await pool.query(`DROP TABLE transaction_test`); } catch {} - await pool.end(); + await closeDbPool(); }); describe('transaction', () => { diff --git a/api/db/index.ts b/api/db/index.ts index a8b843c..f0628cb 100644 --- a/api/db/index.ts +++ b/api/db/index.ts @@ -27,12 +27,17 @@ pool.on('remove', () => { metrics.increment('db.pool.remove.count'); }); -setInterval(() => { +const metricsInterval = setInterval(() => { metrics.gauge('db.pool.total', pool.totalCount); metrics.gauge('db.pool.idle', pool.idleCount); metrics.gauge('db.pool.waiting', pool.waitingCount); }, 10000); +export async function closeDbPool() { + clearInterval(metricsInterval); + return pool.end(); +} + /** * A helper that gets a connection from the pool and then executes fn within a transaction. */ diff --git a/api/db/item-annotations-queries.test.ts b/api/db/item-annotations-queries.test.ts index 167b096..9142e80 100644 --- a/api/db/item-annotations-queries.test.ts +++ b/api/db/item-annotations-queries.test.ts @@ -1,5 +1,5 @@ import { TagVariant } from '../shapes/item-annotations.js'; -import { pool, transaction } from './index.js'; +import { closeDbPool, transaction } from './index.js'; import { deleteAllItemAnnotations, deleteItemAnnotation, @@ -18,7 +18,7 @@ beforeEach(() => }), ); -afterAll(() => pool.end()); +afterAll(() => closeDbPool()); it('can insert tags where none exist before', async () => { await transaction(async (client) => { diff --git a/api/db/item-hash-tags-queries.test.ts b/api/db/item-hash-tags-queries.test.ts index 63bcb4d..3b29d20 100644 --- a/api/db/item-hash-tags-queries.test.ts +++ b/api/db/item-hash-tags-queries.test.ts @@ -1,4 +1,4 @@ -import { pool, transaction } from './index.js'; +import { closeDbPool, transaction } from './index.js'; import { deleteAllItemHashTags, deleteItemHashTag, @@ -15,7 +15,7 @@ beforeEach(() => }), ); -afterAll(() => pool.end()); +afterAll(() => closeDbPool()); it('can insert item hash tags where none exist before', async () => { await transaction(async (client) => { diff --git a/api/db/loadout-share-queries.test.ts b/api/db/loadout-share-queries.test.ts index c677401..1454a1e 100644 --- a/api/db/loadout-share-queries.test.ts +++ b/api/db/loadout-share-queries.test.ts @@ -1,6 +1,6 @@ import { v4 as uuid } from 'uuid'; import { Loadout, LoadoutItem } from '../shapes/loadouts.js'; -import { pool, transaction } from './index.js'; +import { closeDbPool, transaction } from './index.js'; import { addLoadoutShare, getLoadoutShare, recordAccess } from './loadout-share-queries.js'; const appId = 'settings-queries-test-app'; @@ -15,7 +15,7 @@ beforeEach(() => }), ); -afterAll(() => pool.end()); +afterAll(() => closeDbPool()); const loadout: Loadout = { id: uuid(), diff --git a/api/db/loadouts-queries.test.ts b/api/db/loadouts-queries.test.ts index 973d4ae..7785585 100644 --- a/api/db/loadouts-queries.test.ts +++ b/api/db/loadouts-queries.test.ts @@ -1,6 +1,6 @@ import { v4 as uuid } from 'uuid'; import { Loadout, LoadoutItem } from '../shapes/loadouts.js'; -import { pool, transaction } from './index.js'; +import { closeDbPool, transaction } from './index.js'; import { deleteLoadout, getLoadoutsForProfile, updateLoadout } from './loadouts-queries.js'; const appId = 'settings-queries-test-app'; @@ -13,7 +13,7 @@ beforeEach(() => }), ); -afterAll(() => pool.end()); +afterAll(() => closeDbPool()); const loadout: Loadout = { id: uuid(), diff --git a/api/db/searches-queries.test.ts b/api/db/searches-queries.test.ts index a9f66f1..1c63591 100644 --- a/api/db/searches-queries.test.ts +++ b/api/db/searches-queries.test.ts @@ -1,4 +1,5 @@ -import { pool, transaction } from './index.js'; +import { SearchType } from '../shapes/search.js'; +import { closeDbPool, transaction } from './index.js'; import { deleteAllSearches, deleteSearch, @@ -18,11 +19,11 @@ beforeEach(() => }), ); -afterAll(() => pool.end()); +afterAll(() => closeDbPool()); it('can record a used search where none was recorded before', async () => { await transaction(async (client) => { - await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk'); + await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item); const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter( (s) => s.usageCount > 0, @@ -35,8 +36,8 @@ it('can record a used search where none was recorded before', async () => { it('can track search multiple times', async () => { await transaction(async (client) => { - await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk'); - await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk'); + await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item); + await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item); const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter( (s) => s.usageCount > 0, @@ -49,8 +50,8 @@ it('can track search multiple times', async () => { it('can mark a search as favorite', async () => { await transaction(async (client) => { - await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk'); - await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', true); + await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item); + await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item, true); const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter( (s) => s.usageCount > 0, @@ -59,7 +60,7 @@ it('can mark a search as favorite', async () => { expect(searches[0].saved).toBe(true); expect(searches[0].usageCount).toBe(1); - await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', false); + await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item, false); const searches2 = await getSearchesForProfile(client, bungieMembershipId, 2); expect(searches2[0].query).toBe('tag:junk'); @@ -71,7 +72,7 @@ it('can mark a search as favorite', async () => { }); it('can mark a search as favorite even when it hasnt been used', async () => { await transaction(async (client) => { - await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', true); + await saveSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item, true); const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter( (s) => s.usageCount > 0, @@ -84,8 +85,8 @@ it('can mark a search as favorite even when it hasnt been used', async () => { it('can get all searches across profiles', async () => { await transaction(async (client) => { - await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk'); - await updateUsedSearch(client, appId, bungieMembershipId, 1, 'is:tagged'); + await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item); + await updateUsedSearch(client, appId, bungieMembershipId, 1, 'is:tagged', SearchType.Item); const searches = await getSearchesForUser(client, bungieMembershipId); expect(searches.length).toEqual(2); @@ -97,7 +98,7 @@ it('can increment usage for one of the built-in searches', async () => { const searches = await getSearchesForProfile(client, bungieMembershipId, 2); const query = searches[searches.length - 1].query; - await updateUsedSearch(client, appId, bungieMembershipId, 2, query); + await updateUsedSearch(client, appId, bungieMembershipId, 2, query, SearchType.Item); const searches2 = await getSearchesForProfile(client, bungieMembershipId, 2); const search = searches2.find((s) => s.query === query); @@ -108,8 +109,8 @@ it('can increment usage for one of the built-in searches', async () => { it('can delete a search', async () => { await transaction(async (client) => { - await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk'); - await deleteSearch(client, bungieMembershipId, 2, 'tag:junk'); + await updateUsedSearch(client, appId, bungieMembershipId, 2, 'tag:junk', SearchType.Item); + await deleteSearch(client, bungieMembershipId, 2, 'tag:junk', SearchType.Item); const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter( (s) => s.usageCount > 0, @@ -120,7 +121,17 @@ it('can delete a search', async () => { it('can import a search', async () => { await transaction(async (client) => { - await importSearch(client, appId, bungieMembershipId, 2, 'tag:junk', true, 1598199188576, 5); + await importSearch( + client, + appId, + bungieMembershipId, + 2, + 'tag:junk', + true, + 1598199188576, + 5, + SearchType.Item, + ); const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter( (s) => s.usageCount > 0, @@ -130,3 +141,24 @@ it('can import a search', async () => { expect(searches[0].usageCount).toBe(5); }); }); + +it('can record searches for loadouts', async () => { + await transaction(async (client) => { + await updateUsedSearch( + client, + appId, + bungieMembershipId, + 2, + 'subclass:void', + SearchType.Loadout, + ); + + const searches = (await getSearchesForProfile(client, bungieMembershipId, 2)).filter( + (s) => s.usageCount > 0, + ); + expect(searches[0].query).toBe('subclass:void'); + expect(searches[0].saved).toBe(false); + expect(searches[0].usageCount).toBe(1); + expect(searches[0].type).toBe(SearchType.Loadout); + }); +}); diff --git a/api/db/searches-queries.ts b/api/db/searches-queries.ts index 03f46c3..5411cc9 100644 --- a/api/db/searches-queries.ts +++ b/api/db/searches-queries.ts @@ -3,7 +3,7 @@ import { ClientBase, QueryResult } from 'pg'; import { metrics } from '../metrics/index.js'; import { ExportResponse } from '../shapes/export.js'; import { DestinyVersion } from '../shapes/general.js'; -import { Search } from '../shapes/search.js'; +import { Search, SearchType } from '../shapes/search.js'; /* * These "canned searches" get sent to everyone as a "starter pack" of example searches that'll show up in the recent search dropdown and autocomplete. @@ -17,6 +17,7 @@ const cannedSearchesForD2: Search[] = [ saved: false, usageCount: 0, lastUsage: 0, + type: SearchType.Item, })); const cannedSearchesForD1: Search[] = ['-is:equipped is:haslight is:incurrentchar'].map( @@ -25,6 +26,7 @@ const cannedSearchesForD1: Search[] = ['-is:equipped is:haslight is:incurrentcha saved: false, usageCount: 0, lastUsage: 0, + type: SearchType.Item, }), ); /* @@ -48,7 +50,7 @@ export async function getSearchesForProfile( const results = await client.query({ name: 'get_searches', // TODO: order by frecency - text: 'SELECT query, saved, usage_count, last_updated_at FROM searches WHERE membership_id = $1 and destiny_version = $2 order by last_updated_at DESC, usage_count DESC LIMIT 500', + text: 'SELECT query, saved, usage_count, search_type, last_updated_at FROM searches WHERE membership_id = $1 and destiny_version = $2 order by last_updated_at DESC, usage_count DESC LIMIT 500', values: [bungieMembershipId, destinyVersion], }); return _.uniqBy( @@ -73,7 +75,7 @@ export async function getSearchesForUser( try { const results = await client.query({ name: 'get_all_searches', - text: 'SELECT destiny_version, query, saved, usage_count, last_updated_at FROM searches WHERE membership_id = $1', + text: 'SELECT destiny_version, query, saved, usage_count, search_type, last_updated_at FROM searches WHERE membership_id = $1', values: [bungieMembershipId], }); return results.rows.map((row) => ({ @@ -91,6 +93,7 @@ function convertSearch(row: any): Search { usageCount: row.usage_count, saved: row.saved, lastUsage: row.last_updated_at.getTime(), + type: row.search_type, }; } @@ -105,15 +108,16 @@ export async function updateUsedSearch( bungieMembershipId: number, destinyVersion: DestinyVersion, query: string, + type: SearchType, ): Promise> { try { const response = await client.query({ name: 'upsert_search', - text: `insert INTO searches (membership_id, destiny_version, query, created_by, last_updated_by) -values ($1, $2, $3, $4, $4) + text: `insert INTO searches (membership_id, destiny_version, query, search_type, created_by, last_updated_by) +values ($1, $2, $3, $5, $4, $4) on conflict (membership_id, destiny_version, qhash) do update set (usage_count, last_used, last_updated_at, last_updated_by) = (searches.usage_count + 1, current_timestamp, current_timestamp, $4)`, - values: [bungieMembershipId, destinyVersion, query, appId], + values: [bungieMembershipId, destinyVersion, query, appId, type], }); if (response.rowCount < 1) { @@ -137,6 +141,7 @@ export async function saveSearch( bungieMembershipId: number, destinyVersion: DestinyVersion, query: string, + type: SearchType, saved?: boolean, ): Promise> { try { @@ -151,9 +156,9 @@ export async function saveSearch( metrics.increment('db.searches.noRowUpdated.count', 1); const insertSavedResponse = await client.query({ name: 'insert_search_fallback', - text: `insert INTO searches (membership_id, destiny_version, query, saved, created_by, last_updated_by) - values ($1, $2, $3, true, $4, $4)`, - values: [bungieMembershipId, destinyVersion, query, appId], + text: `insert INTO searches (membership_id, destiny_version, query, search_type, saved, created_by, last_updated_by) + values ($1, $2, $3, $5, true, $4, $4)`, + values: [bungieMembershipId, destinyVersion, query, appId, type], }); return insertSavedResponse; } @@ -175,12 +180,13 @@ export async function importSearch( saved: boolean, lastUsage: number, usageCount: number, + type: SearchType, ): Promise> { try { const response = await client.query({ name: 'insert_search', - text: `insert INTO searches (membership_id, destiny_version, query, saved, usage_count, last_used, created_by, last_updated_by) -values ($1, $2, $3, $4, $5, $6, $7, $7)`, + text: `insert INTO searches (membership_id, destiny_version, query, saved, search_type, usage_count, last_used, created_by, last_updated_by) +values ($1, $2, $3, $4, $8, $5, $6, $7, $7)`, values: [ bungieMembershipId, destinyVersion, @@ -189,6 +195,7 @@ values ($1, $2, $3, $4, $5, $6, $7, $7)`, usageCount, new Date(lastUsage), appId, + type, ], }); @@ -212,12 +219,13 @@ export async function deleteSearch( bungieMembershipId: number, destinyVersion: DestinyVersion, query: string, + type: SearchType, ): Promise> { try { return client.query({ name: 'delete_search', - text: `delete from searches where membership_id = $1 and destiny_version = $2 and qhash = decode(md5($3), 'hex') and query = $3`, - values: [bungieMembershipId, destinyVersion, query], + text: `delete from searches where membership_id = $1 and destiny_version = $2 and qhash = decode(md5($3), 'hex') and query = $3 and search_type = $4`, + values: [bungieMembershipId, destinyVersion, query, type], }); } catch (e) { throw new Error(e.name + ': ' + e.message); diff --git a/api/db/settings-queries.test.ts b/api/db/settings-queries.test.ts index d7e0650..494b03d 100644 --- a/api/db/settings-queries.test.ts +++ b/api/db/settings-queries.test.ts @@ -1,10 +1,10 @@ -import { pool, transaction } from './index.js'; +import { closeDbPool, transaction } from './index.js'; import { getSettings, setSetting } from './settings-queries.js'; const appId = 'settings-queries-test-app'; const bungieMembershipId = 4321; -afterAll(() => pool.end()); +afterAll(() => closeDbPool()); it('can insert settings where none exist before', async () => { await transaction(async (client) => { diff --git a/api/db/triumphs-queries.test.ts b/api/db/triumphs-queries.test.ts index 14c8e9c..0ce8f54 100644 --- a/api/db/triumphs-queries.test.ts +++ b/api/db/triumphs-queries.test.ts @@ -1,4 +1,4 @@ -import { pool, transaction } from './index.js'; +import { closeDbPool, transaction } from './index.js'; import { deleteAllTrackedTriumphs, getAllTrackedTriumphsForUser, @@ -17,7 +17,7 @@ beforeEach(() => }), ); -afterAll(() => pool.end()); +afterAll(() => closeDbPool()); it('can track a triumph where none was tracked before', async () => { await transaction(async (client) => { diff --git a/api/index.ts b/api/index.ts index f447d83..56d495f 100644 --- a/api/index.ts +++ b/api/index.ts @@ -6,7 +6,7 @@ import http from 'http'; import morgan from 'morgan'; import vhost from 'vhost'; import { refreshApps, stopAppsRefresh } from './apps/index.js'; -import { pool } from './db/index.js'; +import { closeDbPool } from './db/index.js'; import { app as dimGgApp } from './dim-gg/server.js'; import { metrics } from './metrics/index.js'; import { app as dimApiApp } from './server.js'; @@ -123,7 +123,7 @@ createTerminus(server, { onShutdown: async () => { console.log('Shutting down'); stopAppsRefresh(); - pool.end(); + closeDbPool(); }, }); diff --git a/api/migrations/20240521044000-add-search-type.js b/api/migrations/20240521044000-add-search-type.js new file mode 100644 index 0000000..97b7673 --- /dev/null +++ b/api/migrations/20240521044000-add-search-type.js @@ -0,0 +1,27 @@ +'use strict'; + +var dbm; +var type; +var seed; + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function (options, seedLink) { + dbm = options.dbmigrate; + type = dbm.dataType; + seed = seedLink; +}; + +exports.up = function (db, callback) { + db.runSql(`ALTER TABLE searches ADD COLUMN search_type smallint NOT NULL DEFAULT 1;`, callback); +}; + +exports.down = function (db, callback) { + db.runSql(`ALTER TABLE searches DROP COLUMN search_type;`, callback); +}; + +exports._meta = { + version: 1, +}; diff --git a/api/routes/import.ts b/api/routes/import.ts index fb7a4ae..c6b2b86 100644 --- a/api/routes/import.ts +++ b/api/routes/import.ts @@ -12,6 +12,7 @@ import { DestinyVersion } from '../shapes/general.js'; import { ImportResponse } from '../shapes/import.js'; import { ItemAnnotation } from '../shapes/item-annotations.js'; import { Loadout } from '../shapes/loadouts.js'; +import { SearchType } from '../shapes/search.js'; import { defaultSettings, Settings } from '../shapes/settings.js'; import { badRequest } from '../utils.js'; import { deleteAllData } from './delete-all-data.js'; @@ -123,6 +124,7 @@ export const importHandler = asyncHandler(async (req, res) => { search.search.saved, search.search.lastUsage, search.search.usageCount, + search.search.type ?? SearchType.Item, ); } }); diff --git a/api/routes/update.ts b/api/routes/update.ts index da3a830..111a4b0 100644 --- a/api/routes/update.ts +++ b/api/routes/update.ts @@ -22,6 +22,7 @@ import { DestinyVersion } from '../shapes/general.js'; import { ItemAnnotation } from '../shapes/item-annotations.js'; import { Loadout } from '../shapes/loadouts.js'; import { + DeleteSearchUpdate, ItemHashTagUpdate, ProfileUpdateRequest, ProfileUpdateResult, @@ -29,6 +30,7 @@ import { TrackTriumphUpdate, UsedSearchUpdate, } from '../shapes/profile.js'; +import { SearchType } from '../shapes/search.js'; import { Settings } from '../shapes/settings.js'; import { badRequest, @@ -146,12 +148,7 @@ export const updateHandler = asyncHandler(async (req, res) => { break; case 'delete_search': - result = await deleteSearch( - client, - bungieMembershipId, - destinyVersion, - update.payload.query, - ); + result = await deleteSearch(client, bungieMembershipId, destinyVersion, update.payload); break; default: @@ -428,7 +425,14 @@ async function recordSearch( } const start = new Date(); - await updateUsedSearch(client, appId, bungieMembershipId, destinyVersion, payload.query); + await updateUsedSearch( + client, + appId, + bungieMembershipId, + destinyVersion, + payload.query, + payload.type ?? SearchType.Item, + ); metrics.timing('update.recordSearch', start); return { status: 'Success' }; @@ -461,6 +465,7 @@ async function saveSearch( bungieMembershipId, destinyVersion, payload.query, + payload.type ?? SearchType.Item, payload.saved, ); metrics.timing('update.saveSearch', start); @@ -472,10 +477,16 @@ async function deleteSearch( client: ClientBase, bungieMembershipId: number, destinyVersion: DestinyVersion, - query: string, + payload: DeleteSearchUpdate['payload'], ): Promise { const start = new Date(); - await deleteSearchInDb(client, bungieMembershipId, destinyVersion, query); + await deleteSearchInDb( + client, + bungieMembershipId, + destinyVersion, + payload.query, + payload.type ?? SearchType.Item, + ); metrics.timing('update.deleteSearch', start); return { status: 'Success' }; diff --git a/api/server.test.ts b/api/server.test.ts index c96ba88..a97852b 100644 --- a/api/server.test.ts +++ b/api/server.test.ts @@ -5,13 +5,14 @@ import supertest from 'supertest'; import { promisify } from 'util'; import { v4 as uuid } from 'uuid'; import { refreshApps } from './apps/index.js'; -import { pool } from './db/index.js'; +import { closeDbPool } from './db/index.js'; import { app } from './server.js'; import { ExportResponse } from './shapes/export.js'; import { GlobalSettings } from './shapes/global-settings.js'; import { LoadoutShareRequest } from './shapes/loadout-share.js'; import { Loadout, LoadoutItem } from './shapes/loadouts.js'; import { ProfileResponse, ProfileUpdateRequest } from './shapes/profile.js'; +import { SearchType } from './shapes/search.js'; import { defaultSettings } from './shapes/settings.js'; const request = supertest(app); @@ -34,7 +35,7 @@ beforeAll(async () => { }); }); -afterAll(() => pool.end()); +afterAll(() => closeDbPool()); it('returns basic info from GET /', async () => { // Sends GET Request to / endpoint @@ -891,6 +892,7 @@ describe('searches', () => { action: 'search', payload: { query: 'tag:favorite', + type: SearchType.Item, }, }, ], @@ -920,12 +922,14 @@ describe('searches', () => { action: 'search', payload: { query: 'tag:favorite', + type: SearchType.Item, }, }, { action: 'save_search', payload: { query: 'tag:favorite', + type: SearchType.Item, saved: true, }, }, diff --git a/api/shapes/profile.ts b/api/shapes/profile.ts index ed42778..79e1d32 100644 --- a/api/shapes/profile.ts +++ b/api/shapes/profile.ts @@ -1,7 +1,7 @@ import { DestinyVersion } from './general.js'; import { ItemAnnotation, ItemHashTag } from './item-annotations.js'; import { Loadout } from './loadouts.js'; -import { Search } from './search.js'; +import { Search, SearchType } from './search.js'; import { Settings } from './settings.js'; export interface ProfileResponse { @@ -87,6 +87,7 @@ export interface UsedSearchUpdate { action: 'search'; payload: { query: string; + type: SearchType; }; } @@ -97,6 +98,7 @@ export interface SavedSearchUpdate { action: 'save_search'; payload: { query: string; + type: SearchType; /** * Whether the search should be saved */ @@ -111,6 +113,7 @@ export interface DeleteSearchUpdate { action: 'delete_search'; payload: { query: string; + type: SearchType; }; } diff --git a/api/shapes/search.ts b/api/shapes/search.ts index 279a7dd..452727e 100644 --- a/api/shapes/search.ts +++ b/api/shapes/search.ts @@ -1,3 +1,8 @@ +export const enum SearchType { + Item = 1, + Loadout = 2, +} + /** * A search query. This can either be from history (recent searches), pinned (saved searches), or suggested. */ @@ -15,4 +20,9 @@ export interface Search { * The last time this was used, as a unix millisecond timestamp. */ lastUsage: number; + /** + * Which kind of thing is this search for? Searches of different types are + * stored together and need to be filtered to the specific type. + */ + type: SearchType; }