diff --git a/bun.lockb b/bun.lockb index 906ed9a589feee5e8d64813de80e168671ea1644..943161f7e0e32131ac8d906959ec40a46f7602e8 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 3f8ec9698c62e99db65e02d2af5f9629927f93e3..e3a493ffe8985d1b05cb7c9af3d0f50fce0294d8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "db:migrate": "cross-env DB_MIGRATING=true bun run src/db/migrate.ts", "db:seed": "cross-env DB_SEEDING=true bun run src/db/seed.ts", "db:studio": "drizzle-kit studio", - "route:test": "bun db:seed && bun test", + "route:test": "bun db:seed && bun test" }, "dependencies": { "@aws-sdk/client-s3": "^3.750.0", @@ -27,7 +27,7 @@ "jose": "^6.0.11", "jwt-simple": "^0.5.6", "nodemailer": "^6.10.0", - "openid-client": "^6.4.2", + "openid-client": "^6.5.0", "postgres": "^3.4.4", "reflect-metadata": "^0.2.2", "typedi": "^0.10.0", diff --git a/src/db/repo/search.repo.ts b/src/db/repo/search.repo.ts index a6e0a840757c3ed292f589ec4c278703d18967b6..1e2390b74cdc8eb071110c8c03c79afee74c426b 100644 --- a/src/db/repo/search.repo.ts +++ b/src/db/repo/search.repo.ts @@ -5,6 +5,7 @@ import { resourceIndexSchema, type ResourceIndex } from "../schema/search.schema import { userIndexSchema, type UserIndex } from "../schema/search.schema"; import { collectionIndexSchema, type CollectionIndex } from "../schema/search.schema"; + @Service() export class SearchRepo { async findResourcesForIndexing(id: number): Promise<ResourceIndex[]> { @@ -21,7 +22,7 @@ export class SearchRepo { subjects: sql`string_agg(distinct sj.name, ',')`, license: sql`lc.name`, object_type: sql`ot.name`, - state: sql`re.state`, + resource_state: sql`re.resource_state`, user: sql`us.name`, user_id: sql`re.user_id`, views: sql`rest.views`, @@ -30,18 +31,20 @@ export class SearchRepo { shares: sql`rest.shares`, score: sql`rest.score`, comments: sql`rest.comments`, + saves: sql`count(distinct cr.id)` }) .from(sql`resources re`) - .leftJoin(sql`resource_educational_stages resed`, sql`resed.resource_id = re.id`) - .leftJoin(sql`educational_stages es`, sql`resed.educational_stage_id = es.id`) + .leftJoin(sql`resource_educational_stages res`, sql`res.resource_id = re.id`) + .leftJoin(sql`educational_stages es`, sql`res.educational_stage_id = es.id`) .leftJoin(sql`resource_languages rel`, sql`rel.resource_id = re.id`) .leftJoin(sql`languages lg`, sql`rel.language_id = lg.id`) - .leftJoin(sql`resource_subjects res`, sql`res.resource_id = re.id`) - .leftJoin(sql`subjects sj`, sql`res.subject_id = sj.id`) + .leftJoin(sql`resource_subjects rs`, sql`rs.resource_id = re.id`) + .leftJoin(sql`subjects sj`, sql`rs.subject_id = sj.id`) .leftJoin(sql`licenses lc`, sql`re.license_id = lc.id`) .leftJoin(sql`object_types ot`, sql`re.object_type_id = ot.id`) .leftJoin(sql`users us`, sql`us.id = re.user_id`) .leftJoin(sql`resource_stats rest`, sql`rest.id = re.resource_stats_id`) + .leftJoin(sql`collection_resources`, sql`cr.resource_id = re.id`) .where(eq(sql`re.id`, id)) .groupBy( sql`re.id`, @@ -63,12 +66,25 @@ export class SearchRepo { sql`rest.comments` ); - let results = await query.execute(); + let results = await query.execute(); + + // O driver do postgres (pg) usado no drizzle retorna Numeric como uma string por padrão + // para evitar problemas com precisão numérica. Por isso precisamos parsear o score aqui + results = results.map(result => ({ ...result, score: Number(result.score) })); + // return results.map((result) => resourceIndexSchema.parse(result)); + // // Parse and validate each result using Zod schema + // return results.map((result) => resourceIndexSchema.parse(result)); - // O driver do postgres (pg) usado no drizzle retorna Numeric como uma string por padrão - // para evitar problemas com precisão numérica. Por isso precisamos parsear o score aqui - results = results.map(result => ({ ...result, score: Number(result.score) })); - return results.map((result) => resourceIndexSchema.parse(result)); + // converte strings separadas por vírgula em arrays + return results.map((result) => + resourceIndexSchema.parse({ + ...result, + author: (result.author as string | null)?.split(',').map((i) => i.trim()) ?? [], + educational_stages: (result.educational_stages as string | null)?.split(',').map((r) => r.trim()) ?? [], + languages: (result.languages as string | null)?.split(',').map((i) => i.trim()) ?? [], + subjects: (result.subjects as string | null)?.split(',').map((r) => r.trim()) ?? [], + }) + ); } async findUsersForIndexing(id: number): Promise<UserIndex[]> { @@ -76,7 +92,7 @@ export class SearchRepo { .select({ id: sql`users.id`, name: sql`users.name`, - username: sql`users.username`, + user: sql`users.username`, description: sql`users.description`, roles: sql`string_agg(distinct roles.name, ',')`, institutions: sql`string_agg(distinct inst.name, ',')`, @@ -110,8 +126,14 @@ export class SearchRepo { const results = await query.execute(); - // Parse and validate each result using Zod schema - return results.map((result) => userIndexSchema.parse(result)); + // converte strings separadas por vírgula em arrays + return results.map((result) => + userIndexSchema.parse({ + ...result, + roles: (result.roles as string | null)?.split(',').map((r) => r.trim()) ?? [], + institutions: (result.institutions as string | null)?.split(',').map((i) => i.trim()) ?? [], + }) + ); } // TODO fix the collections query @@ -120,7 +142,8 @@ export class SearchRepo { .select({ id: sql`col.id`, name: sql`col.name`, - author: sql`users.name`, + user: sql`users.name`, + user_id: sql `users.id`, description: sql`col.description`, created_at: sql`col.created_at`, score: sql`colst.score`, @@ -138,6 +161,7 @@ export class SearchRepo { sql`col.id`, sql`col.name`, sql`users.name`, + sql`users.id`, sql`col.description`, sql`col.created_at`, sql`colst.score`, diff --git a/src/db/schema/search-filters.schema.ts b/src/db/schema/search-filters.schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ea358c43bfdc75fa98cb5de89262c5853654d36 --- /dev/null +++ b/src/db/schema/search-filters.schema.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +export const exactFilterSchema = z.object({ + language: z.string().optional(), + object_type: z.string().optional(), + institution: z.string().optional(), + resource_state: z.string().optional(), + is_active: z.coerce.boolean().optional(), + is_private: z.coerce.boolean().optional(), +}); + +export type ExactFilterFields = z.infer<typeof exactFilterSchema>; \ No newline at end of file diff --git a/src/db/schema/search-sortable.schema.ts b/src/db/schema/search-sortable.schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..e29d475cc4ab29b20d2b097f4572239b57418123 --- /dev/null +++ b/src/db/schema/search-sortable.schema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; + +export const sortableFields = [ + 'created_at', + 'views', + 'downloads', + 'likes', + 'shares', + 'score', + 'comments', + 'followers', + 'approved_resources', + 'saves' +] as const; + +export const sortBySchema = z.enum(sortableFields); + +export type SortableField = typeof sortableFields[number]; \ No newline at end of file diff --git a/src/db/schema/search.schema.ts b/src/db/schema/search.schema.ts index f969933bf68d2ae1c1278295d01ab10dd97d473f..176a017e9f0269c2c66a8d29bde73029103a7d69 100644 --- a/src/db/schema/search.schema.ts +++ b/src/db/schema/search.schema.ts @@ -3,16 +3,17 @@ import { z } from "zod"; export const resourceIndexSchema = z.object({ id: z.number(), name: z.string(), - author: z.string().nullable(), + author: z.array(z.string()), description: z.string().nullable(), link: z.string().nullable(), - created_at: z.string().optional().nullable(), - educational_stages: z.string().nullable(), - languages: z.string(), - subjects: z.string().nullable(), + // created_at: z.string(), + created_at: z.coerce.date(), + educational_stages: z.array(z.string()).nullable(), + languages: z.array(z.string()), + subjects: z.array(z.string()), license: z.string(), object_type: z.string(), - state: z.enum(['draft', 'submitted', 'accepted', 'reported', 'deleted']), + resource_state: z.string(), user: z.string(), user_id: z.number(), views: z.number(), @@ -20,7 +21,8 @@ export const resourceIndexSchema = z.object({ likes: z.number(), shares: z.number(), score: z.number(), - comments: z.number() + comments: z.number(), + saves: z.number() }); export const userIndexSchema = z.object({ @@ -28,9 +30,10 @@ export const userIndexSchema = z.object({ name: z.string(), username: z.string(), description: z.string().nullable(), - roles: z.string().nullable(), - institutions: z.string().nullable(), - created_at: z.string(), + roles: z.array(z.string()), + institutions: z.array(z.string()), + // created_at: z.string(), + created_at: z.coerce.date(), is_active: z.boolean(), score: z.number(), likes: z.number(), @@ -41,9 +44,13 @@ export const userIndexSchema = z.object({ export const collectionIndexSchema = z.object({ id: z.number(), name: z.string(), - author: z.string().nullable(), + user: z.string().nullable(), + user_id: z.number(), description: z.string().nullable(), - created_at: z.string(), + // created_at: z.string(), + created_at: z.coerce.date(), + is_private: z.boolean(), + is_active: z.boolean(), score: z.number(), views: z.number(), downloads: z.number(), diff --git a/src/routes/resource.route.ts b/src/routes/resource.route.ts index 012551dec84278cd819614c3cf50b4e3b9dc5464..dbeca2888f6adcc0fb2aae4589741610356e253e 100644 --- a/src/routes/resource.route.ts +++ b/src/routes/resource.route.ts @@ -236,6 +236,8 @@ export const resourceRouter = honoWithJwt() //indexando await searchService.indexResource(resource.id) + await searchService.indexResource(resource.id) + // 5. Retorna o recurso com os arrays de IDs return c.json({ ...resourceSchema.return.parse(resource), diff --git a/src/routes/search.route.ts b/src/routes/search.route.ts index 2cf6112e9b25c2b9ef4fa72ffcfd6e1322273d73..8a16e16638762915319eff81e9523c36fce7dcaa 100644 --- a/src/routes/search.route.ts +++ b/src/routes/search.route.ts @@ -5,6 +5,8 @@ import { Hono } from "hono"; import { z } from "zod"; import { zValidator } from "@hono/zod-validator"; import { createApexError, HttpStatus } from "@/services/error.service"; +import { exactFilterSchema } from "@/db/schema/search-filters.schema"; +import { sortBySchema, type SortableField } from "@/db/schema/search-sortable.schema"; const searchService = Container.get(SearchService); @@ -16,29 +18,37 @@ export const searchRouter = new Hono() 'query', z.object({ query: z.string().optional(), - sortBy: z.enum(['created_at']).optional(), + filters: z.string().optional(), + sortBy: sortBySchema.optional(), sortOrder: z.enum(['asc', 'desc']).optional(), offset: z.coerce.number().int().nonnegative().optional(), page_size: z.coerce.number().int().positive().optional(), - - // filters - language: z.string().optional(), - objectType: z.string().optional(), - institution: z.string().optional(), + index: z.string().optional().transform((val) => (val ? val.split(',').map(s => s.trim()): undefined)) }) + .merge(exactFilterSchema) // ✅ merges in all filter fields from your schema ), async (c) => { try { - const { query } = c.req.valid('query'); + // const { query } = c.req.valid('query'); + + const { query, sortBy, sortOrder = 'desc', index, offset = 0, page_size = 1000, ...filters} = c.req.valid('query'); + + const results = await searchService.searchIndex( + query ?? '', + filters, + sortBy, + sortOrder, + index, // indexes to search + offset, + page_size + ); - const results = await searchService.searchIndex(query); + // const results = await searchService.searchIndex(query); - console.log("results", results) return c.json({ results }); } catch (e) { - console.log(e) return c.json( createApexError({ status: 'error', @@ -53,6 +63,225 @@ export const searchRouter = new Hono() } ) + .get('/home', async (c) => { + + + type SearchPreset = { + key: string; + query: string; + filters: Record<string, unknown>; + index: string[]; + sortBy: SortableField; + sortOrder: 'asc' | 'desc'; + page_size: number; + }; + + try { + // queries cujos resultados são mostrados na home + const queries: SearchPreset[] = [ + { + key: 're_mais_recentes', + query: '', + filters: {}, + index: ["resources"], + sortBy: 'created_at', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 'col_mais_recentes', + query: '', + filters: {}, + index: ["collections"], + sortBy: 'created_at', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 'usr_mais_recentes', + query: '', + filters: {}, + index: ["users"], + sortBy: 'created_at', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 're_mais_relevantes', + query: '', + filters: {}, + index: ["resources"], + sortBy: 'score', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 'col_mais_relevantes', + query: '', + filters: {}, + index: ["collections"], + sortBy: 'score', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 'usr_mais_relevantes', + query: '', + filters: {}, + index: ["users"], + sortBy: 'score', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 're_mais_comentados', + query: '', + filters: {}, + index: ["resources"], + sortBy: 'comments', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 're_mais_likes', + query: '', + filters: {}, + index: ["resources"], + sortBy: 'likes', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 'col_mais_likes', + query: '', + filters: {}, + index: ["collections"], + sortBy: 'likes', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 're_mais_downloads', + query: '', + filters: {}, + index: ["resources"], + sortBy: 'downloads', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 'col_mais_downloads', + query: '', + filters: {}, + index: ["collections"], + sortBy: 'downloads', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 're_mais_salvos', + query: '', + filters: {}, + index: ["resources"], + sortBy: 'saves', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 're_mais_shares', + query: '', + filters: {}, + index: ["resources"], + sortBy: 'shares', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 'col_mais_shares', + query: '', + filters: {}, + index: ["collections"], + sortBy: 'shares', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 'usr_mais_followers', + query: '', + filters: {}, + index: ["users"], + sortBy: 'followers', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 're_mais_views', + query: '', + filters: {}, + index: ["resources"], + sortBy: 'views', + sortOrder: 'desc', + page_size: 10, + }, + { + key: 'col_mais_views', + query: '', + filters: {}, + index: ["collections"], + sortBy: 'views', + sortOrder: 'desc', + page_size: 10, + }, + ]; + + // Executa todas as buscas em paralelo + const results = await Promise.all( + queries.map(async (query) => { + try { + const result = await searchService.searchIndex( + query.query, + query.filters, + query.sortBy, + query.sortOrder, + query.index, + 0, + query.page_size + ); + return { [query.key]: result }; + } catch (err) { + console.error('Error on preset', query.key, err); + return { [query.key]: { error: 'failed' } }; + } + }) + ) + + + // 🔗 Combina os resultados em um único objeto + // const combinedResults = results.reduce((acc, curr) => { + // return { ...acc, ...curr }; + // }, {}); + + const combinedResults = Object.assign({}, ...results) as { + [key: string]: unknown[] | { error: string }; + }; + + + return c.json(combinedResults); + } catch (e) { + console.error(e); + return c.json( + createApexError({ + status: 'error', + message: 'Preset search failed', + code: HttpStatus.BAD_REQUEST, + path: c.req.routePath, + suggestion: 'Check preset configurations', + }), + HttpStatus.BAD_REQUEST + ); + } + }) + .post( '/index/resource/:id', async (c) => { diff --git a/src/search/index.ts b/src/search/index.ts index 4d209a0d82e7f2b10fd400f4dbf3a2663445d85e..be78706900ee4aa00b64c54853c1f22ee0d86001 100644 --- a/src/search/index.ts +++ b/src/search/index.ts @@ -14,9 +14,103 @@ export const es = new Client({ }) // Register the Elasticsearch client in the DI container -console.log("Registering Elasticsearch client..."); Container.set('ElasticSearchClient', es) +const indexMappings = { + [env.ELASTIC_INDEX_USERS]: { + mappings: { + properties: { + name: { type: 'text' }, + username: { type: 'text' }, + description: { type: 'text' }, + roles: {type: 'text'}, + institutions: { + type: 'text', + fields: { raw: { type: 'keyword' } }, + }, + created_at: { + type: "date", + fields: { + text: { type: "text" } + } + }, + is_active: { type: 'boolean' }, + score: { type: 'float' }, + likes: { type: 'integer' }, + followers: { type: 'integer' }, + approved_resources: { type: 'integer' }, + }, + }, + }, + + [env.ELASTIC_INDEX_COLLECTIONS]: { + mappings: { + properties: { + name: { type: 'text' }, + user: { type: 'text' }, + description: { type: 'text' }, + created_at: { + type: "date", + fields: { + text: { type: "text" } + } + }, + is_private: { type: 'boolean' }, + is_active: { type: 'boolean' }, + score: { type: 'float' }, + views: { type: 'integer' }, + downloads: { type: 'integer' }, + likes: { type: 'integer' }, + shares: { type: 'integer' }, + followers: { type: 'integer' }, + }, + }, + }, + + [env.ELASTIC_INDEX_RESOURCES]: { + mappings: { + properties: { + name: { type: 'text' }, + author: { type: 'text' }, + description: { type: 'text' }, + link: { type: 'text' }, + created_at: { + type: "date", + fields: { + text: { type: "text" } + } + }, + educational_stages: { + type: 'text', + fields: { raw: { type: 'keyword' } }, + }, + languages: { + type: 'text', + fields: { raw: { type: 'keyword' } }, + }, + subjects: { + type: 'text', + fields: { raw: { type: 'keyword' } }, + }, + license: { type: 'text' }, + objectType: { + type: 'text', + fields: { raw: { type: 'keyword' } }, + }, + resource_state: { type: 'text' }, + user: { type: 'text' }, + views: { type: 'integer' }, + downloads: { type: 'integer' }, + likes: { type: 'integer' }, + shares: { type: 'integer' }, + score: { type: 'float' }, + comments: { type: 'integer' }, + saves: { type: 'integer' } + }, + }, + }, +} + export const checkElasticsearchConnection = async () => { try { const health = await es.cluster.health() @@ -27,7 +121,34 @@ export const checkElasticsearchConnection = async () => { } } -checkElasticsearchConnection() +export const ensureIndexesExist = async () => { + for (const [indexName, config] of Object.entries(indexMappings)) { + console.log("oiiiiiiiiiiiiii") + if (!indexName) { + console.error('❌ Invalid index name:', indexName) + continue + } + + console.log('🔍 Checking index:', indexName) + console.log("exists: ", await es.indices.exists({ index: indexName })) + const exists = await es.indices.exists({ index: indexName }) + console.log("exists: ", exists) + if (!exists) { + console.log(`Creating index: ${indexName}`) + await es.indices.create({ + index: indexName, + ...config, + }) + } else { + console.log(`Index already exists: ${indexName}`) + } + } +} + +;(async () => { + await checkElasticsearchConnection() + await ensureIndexesExist() +})() export type es = typeof es diff --git a/src/services/search.service.ts b/src/services/search.service.ts index 0504e8dc483bd3fd34c5a3d136cee649957dcbab..ce42ce058ecfdf99edd15865c316722c2a8f211f 100644 --- a/src/services/search.service.ts +++ b/src/services/search.service.ts @@ -2,25 +2,29 @@ import { Inject, Service } from "typedi" import es from '@/search' import env from '@/env' import { SearchRepo } from "@/db/repo/search.repo"; +import type { ExactFilterFields } from '@/db/schema/search-filters.schema'; +import type { SortableField } from "@/db/schema/search-sortable.schema"; -export type ExactFilterFields = { - language?: string; - objectType?: string; - institution?: string; - state?: number; -}; +// export type ExactFilterFields = { +// language?: string; +// object_type?: string; +// institution?: string; +// resource_state?: number; +// is_active?: boolean; +// is_private?: boolean; +// }; -export type SortableFilterFields = { - created_at?: string; - views?: number; - downloads?: number; - likes?: number; - shares?: number; - score?: number; - comments?: number; - followers?: number; - approved_resources?: number; -} +// export type SortableFilterFields = { +// created_at?: string; +// views?: number; +// downloads?: number; +// likes?: number; +// shares?: number; +// score?: number; +// comments?: number; +// followers?: number; +// approved_resources?: number; +// } @Service() export class SearchService { @@ -45,16 +49,16 @@ export class SearchService { id: document.id.toString(), body: { name: document.name, - author: document.author ?? "", + author: document.author ?? [], description: document.description ?? "", link: document.link ?? "", created_at: document.created_at, - educational_stages: document.educational_stages?.split(','), // Converte para array - languages: document.languages?.split(',') ?? [], // Converte para array - subjects: document.subjects?.split(',') ?? [], // Converte para array + educational_stages: document.educational_stages ?? [], + languages: document.languages ?? [], + subjects: document.subjects ?? [], license: document.license, - objectType: document.object_type, - state: document.state, + object_type: document.object_type, + resource_state: document.resource_state, user: document.user, user_id: document.user_id, views: document.views, @@ -62,7 +66,8 @@ export class SearchService { likes: document.likes, shares: document.shares, score: document.score, - comments: document.comments + comments: document.comments, + saves: document.saves }, }); @@ -87,9 +92,12 @@ export class SearchService { id: document.id.toString(), body: { name: document.name, - author: document.author ?? "", + user: document.user ?? "", + user_id: document.user_id, description: document.description ?? "", created_at: document.created_at, + is_private: document.is_private, + is_active: document.is_active, score: document.score, views: document.views, downloads: document.downloads, @@ -122,8 +130,8 @@ export class SearchService { name: document.name, username: document.username, description: document.description ?? "", - roles: document.roles?.split(',') ?? [], - institutions: document.institutions?.split(',') ?? [], // Converte para array + roles: document.roles ?? [], + institutions: document.institutions ?? [], created_at: document.created_at, is_active: document.is_active, score: document.score, @@ -146,12 +154,11 @@ export class SearchService { // offset: offset da paginação, número da página // page_size: tamanho da página async searchIndex( - query: string | undefined, - filters: Partial<ExactFilterFields> = {}, - sortBy: Partial<SortableFilterFields> = {}, - // sortBy?: 'created_at', + query: string, + filters: ExactFilterFields, + sortBy?: SortableField, sortOrder: 'asc' | 'desc' = 'desc', - index: string[] = [env.ELASTIC_INDEX_USERS, env.ELASTIC_INDEX_COLLECTIONS, env.ELASTIC_INDEX_RESOURCES], + index?: string[], offset: number = 0, page_size: number = 1000 ) { @@ -168,7 +175,7 @@ export class SearchService { let sort: Array<{ [key: string]: { order: 'asc' | 'desc' } }> = []; if (sortBy && Object.keys(sortBy).length > 0) { - const sortField = Object.keys(sortBy)[0]; + const sortField = sortBy sort = [ { [sortField]: { @@ -178,6 +185,26 @@ export class SearchService { ]; } + // lógica para mapear index na requisição para nome do index no back + const indexMap: Record<string, string> = { + users: env.ELASTIC_INDEX_USERS, + collections: env.ELASTIC_INDEX_COLLECTIONS, + resources: env.ELASTIC_INDEX_RESOURCES, + }; + + console.log("index: ", index) + + const resolvedIndexes = (index ?? []) + .map(i => indexMap[i]) + .filter((i): i is string => Boolean(i)); + + console.log("resolved: ", resolvedIndexes) + + const indexesToUse = resolvedIndexes.length > 0 + ? resolvedIndexes + : [env.ELASTIC_INDEX_USERS, env.ELASTIC_INDEX_COLLECTIONS, env.ELASTIC_INDEX_RESOURCES]; + + const searchQuery = query ? { multi_match: { @@ -188,23 +215,15 @@ export class SearchService { 'author', 'description', 'link', - 'fileFormats', 'educational_stages', 'languages', 'subjects', 'license', 'object_type', - 'state', 'user', + 'username', 'roles', - 'institution', - 'city', - 'views', - 'downloads', - 'likes', - 'shares', - 'score', - 'comments', + 'institutions', 'created_at' ], }, @@ -213,10 +232,12 @@ export class SearchService { match_all: {}, } + console.log(indexesToUse) + const result = await this.esClient.search({ - index: index, + index: indexesToUse, body: { - from: offset, + from: offset * page_size, size: page_size, query: { bool: {