Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/di/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ import {
} from '~/modules/profile/application/profile'
import { createRecentFoodCrud } from '~/modules/recent-food/application/usecases/recentFoodCrud'
import { createRecentFoodRepository } from '~/modules/recent-food/infrastructure/recentFoodRepository'
import {
createTemplateSearchState,
type TemplateSearchState,
} from '~/modules/search/application/store/templateSearchState'
import {
type CachedSearchCrud,
createCachedSearchCrud,
} from '~/modules/search/application/usecases/cachedSearchCrud'
import { createCachedSearchRepository } from '~/modules/search/infrastructure/cachedSearchRepository'
import {
createTemplateSearchState,
type TemplateSearchState,
} from '~/modules/template-search/application/usecases/templateSearchState'
import {
createUserUseCases,
type UserUseCases,
Expand Down
9 changes: 4 additions & 5 deletions src/modules/diet/food/application/usecases/foodCrud.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { type Food } from '~/modules/diet/food/domain/food'
import { type FoodSearchParams } from '~/modules/diet/food/domain/foodRepository'
import { type FoodRepository } from '~/modules/diet/food/domain/foodRepository'
import {
type FoodRepository,
type FoodSearchParams,
} from '~/modules/diet/food/domain/foodRepository'
import { createApiFoodImportService } from '~/modules/diet/food/infrastructure/api/application/apiFood'
import { type CachedSearchCrud } from '~/modules/search/application/usecases/cachedSearchCrud'
import { showPromise } from '~/modules/toast/application/toastManager'
Expand Down Expand Up @@ -109,7 +111,4 @@ export function createFoodCrud(deps: {
}
}

/**
* Convenience type for the concrete use-cases returned by the factory.
*/
export type FoodCrud = ReturnType<typeof createFoodCrud>
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,10 @@ export function createApiFoodImportService(deps: {

logging.debug(`Found ${apiFoods.length} foods`)

const foodsToupsert = apiFoods.map(convertApi2Food)

const upsertPromises = foodsToupsert.map(foodRepository.upsertFood)

const foodsToUpsert = apiFoods.map(convertApi2Food)
const upsertPromises = foodsToUpsert.map(foodRepository.upsertFood)
const upsertionResults = await Promise.allSettled(upsertPromises)

logging.debug(
`upserted ${upsertionResults.length} foods. ${
upsertionResults.filter((result) => result.status === 'fulfilled')
Expand Down
Empty file.
52 changes: 52 additions & 0 deletions src/modules/search/application/store/cachedSearchCacheStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { createSignal } from 'solid-js'

import { type CachedSearch } from '~/modules/search/domain/cachedSearch'
import { logging } from '~/shared/utils/logging'

export function createCachedSearchCacheStore() {
const [cachedSearches, setCachedSearches] = createSignal<
readonly CachedSearch[]
>([])

return {
// Upsert to cache (from realtime events)
upsertToCache: (cachedSearch: CachedSearch) => {
const current = cachedSearches()
const existingIndex = current.findIndex(
(search) => search.search === cachedSearch.search,
)

if (existingIndex >= 0) {
// Update existing
const updated = [...current]
updated[existingIndex] = cachedSearch
setCachedSearches(updated)
logging.debug('Updated cached search in cache:', { cachedSearch })
} else {
// Add new
setCachedSearches([cachedSearch, ...current])
logging.debug('Added new cached search to cache:', { cachedSearch })
}
},

// Remove from cache (from realtime events)
removeFromCache: (selector: {
by: 'search'
value: CachedSearch['search']
}) => {
const current = cachedSearches()
const searchToRemove = current.find(
(search) => search.search === selector.value,
)

if (searchToRemove) {
const updated = current.filter(
(search) => search.search !== selector.value,
)
setCachedSearches(updated)

logging.debug('Removed cached search from cache:', { searchToRemove })
}
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { createResource, createRoot, createSignal } from 'solid-js'
import { type FoodCrud } from '~/modules/diet/food/application/usecases/foodCrud'
import { type RecipeCrud } from '~/modules/diet/recipe/application/usecases/recipeCrud'
import { type RecentFoodCrud } from '~/modules/recent-food/application/usecases/recentFoodCrud'
import { fetchTemplatesByTabLogic } from '~/modules/template-search/application/templateSearchLogic'
import { type TemplateSearchTab } from '~/sections/search/components/TemplateSearchTabs'
import { fetchTemplatesByTabLogic } from '~/modules/search/application/usecases/templateSearchLogic'
import { type TemplateSearchTab } from '~/modules/search/ui/TemplateSearchTabs'
import { createDebouncedSignal } from '~/shared/utils/createDebouncedSignal'

export function createTemplateSearchState(deps: {
Expand Down
29 changes: 8 additions & 21 deletions src/modules/search/application/usecases/cachedSearchCrud.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { type CachedSearchRepository } from '~/modules/search/domain/searchRepository'

export function createCachedSearchCrudUseCases(
cachedSearchRepository: CachedSearchRepository,
) {
return createCachedSearchCrud({
repository: cachedSearchRepository,
})
}

/**
* Factory that creates cached-search CRUD use-cases.
*
Expand All @@ -14,32 +22,14 @@ export function createCachedSearchCrud(deps: {
}) {
const repository = deps.repository

/**
* Checks whether a given search query result is already cached.
*
* @param query The search query to check.
* @returns A promise resolving to `true` if cached, otherwise `false`.
*/
async function isSearchCached(query: string): Promise<boolean> {
return await repository.isSearchCached(query)
}

/**
* Marks the given search query as cached.
*
* @param query The search query to mark as cached.
* @returns A promise that resolves when the operation completes.
*/
async function markSearchAsCached(query: string): Promise<void> {
await repository.markSearchAsCached(query)
}

/**
* Removes the cached mark for the given search query.
*
* @param query The search query to unmark.
* @returns A promise that resolves when the operation completes.
*/
async function unmarkSearchAsCached(query: string): Promise<void> {
await repository.unmarkSearchAsCached(query)
}
Expand All @@ -51,7 +41,4 @@ export function createCachedSearchCrud(deps: {
}
}

/**
* Type for DI/testing consumers.
*/
export type CachedSearchCrud = ReturnType<typeof createCachedSearchCrud>
65 changes: 65 additions & 0 deletions src/modules/search/application/usecases/searchUseCases.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { createRoot } from 'solid-js'

import { createCachedSearchCacheStore } from '~/modules/search/application/store/cachedSearchCacheStore'
import { createCachedSearchCrudUseCases } from '~/modules/search/application/usecases/cachedSearchCrud'
import { cachedSearchSchema } from '~/modules/search/domain/cachedSearch'
import { createCachedSearchRepository } from '~/modules/search/infrastructure/cachedSearchRepository'
import { initializeCachedSearchRealtime } from '~/modules/search/infrastructure/supabase/realtime'
import { parseWithStack } from '~/shared/utils/parseWithStack'

const { cachedSearchCacheStore, cachedSearchCrudUseCases } = createRoot(() => {
const cachedSearchCacheStore = createCachedSearchCacheStore()
const cachedSearchRepository = createCachedSearchRepository()
const cachedSearchCrudUseCases = createCachedSearchCrudUseCases(
cachedSearchRepository,
)

initializeCachedSearchRealtime({
onInsert: (newRecord) => {
cachedSearchCacheStore.upsertToCache(newRecord)
},
onUpdate: (newRecord) => {
cachedSearchCacheStore.upsertToCache(newRecord)
},
onDelete: (oldRecord) => {
cachedSearchCacheStore.removeFromCache({
by: 'search',
value: oldRecord.search,
})
},
})

return { cachedSearchCacheStore, cachedSearchCrudUseCases }
})

export const searchUseCases = {
isSearchCached: async (query: string): Promise<boolean> => {
const result = await cachedSearchCrudUseCases.isSearchCached(query)
if (result) {
cachedSearchCacheStore.upsertToCache(
parseWithStack(cachedSearchSchema, { search: query }),
)
} else {
cachedSearchCacheStore.removeFromCache({
by: 'search',
value: query,
})
}
return result
},
markSearchAsCached: async (query: string): Promise<void> => {
await cachedSearchCrudUseCases.markSearchAsCached(query)
cachedSearchCacheStore.upsertToCache(
parseWithStack(cachedSearchSchema, {
search: query,
}),
)
},
unmarkSearchAsCached: async (query: string): Promise<void> => {
await cachedSearchCrudUseCases.unmarkSearchAsCached(query)
cachedSearchCacheStore.removeFromCache({
by: 'search',
value: query,
})
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import type { Food } from '~/modules/diet/food/domain/food'
import type { FoodSearchParams } from '~/modules/diet/food/domain/foodRepository'
import type { Recipe } from '~/modules/diet/recipe/domain/recipe'
import type { Template } from '~/modules/diet/template/domain/template'
import { availableTabs } from '~/modules/search/ui/TemplateSearchTabs'
import { type User } from '~/modules/user/domain/user'
import { availableTabs } from '~/sections/search/components/TemplateSearchTabs'

/**
* Dependencies for fetchTemplatesByTabLogic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
import {
fetchTemplatesByTabLogic,
type FetchTemplatesDeps,
} from '~/modules/template-search/application/templateSearchLogic'
import { availableTabs } from '~/sections/search/components/TemplateSearchTabs'
} from '~/modules/search/application/usecases/templateSearchLogic'
import { availableTabs } from '~/modules/search/ui/TemplateSearchTabs'

describe('fetchTemplatesByTabLogic', () => {
const mockFood = promoteNewFoodToFood(
Expand Down
52 changes: 52 additions & 0 deletions src/modules/search/infrastructure/supabase/realtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
type CachedSearch,
cachedSearchSchema,
} from '~/modules/search/domain/cachedSearch'
import { registerSubapabaseRealtimeCallback } from '~/shared/supabase/supabase'
import { logging } from '~/shared/utils/logging'

const SUPABASE_TABLE_CACHED_SEARCHES = 'cached_searches'

let initialized = false

export function initializeCachedSearchRealtime(callbacks: {
onInsert: (newRecord: CachedSearch) => void
onUpdate: (newRecord: CachedSearch) => void
onDelete: (oldRecord: CachedSearch) => void
}): void {
if (initialized) {
return
}
logging.debug(`Cached search realtime initialized!`)
initialized = true
registerSubapabaseRealtimeCallback(
SUPABASE_TABLE_CACHED_SEARCHES,
cachedSearchSchema,
(event) => {
logging.debug(`Event:`, event)

switch (event.eventType) {
case 'INSERT': {
if (event.new !== undefined) {
callbacks.onInsert(event.new)
}
break
}

case 'UPDATE': {
if (event.new) {
callbacks.onUpdate(event.new)
}
break
}

case 'DELETE': {
if (event.old) {
callbacks.onDelete(event.old)
}
break
}
}
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import {
availableTabs,
type TemplateSearchTab,
} from '~/sections/search/components/TemplateSearchTabs'
} from '~/modules/search/ui/TemplateSearchTabs'

const STORAGE_KEY = 'macroflows:template-search-tab'

Expand Down
Loading
Loading