diff --git a/src/db/repo/collection-likes.repo.ts b/src/db/repo/collection-likes.repo.ts index 8063c07a0ecdc69e141dc0392d53f7a090943b22..e3b48586f4688ed141e2fdcb133b2f7753612ea6 100644 --- a/src/db/repo/collection-likes.repo.ts +++ b/src/db/repo/collection-likes.repo.ts @@ -42,6 +42,7 @@ export class collectionLikesRepo { } } catch (error) { console.error("Error associating collection with likes", error); + throw error; } } diff --git a/src/db/repo/collection-resources.repo.ts b/src/db/repo/collection-resources.repo.ts index b179ca3251d4fbfe0e5620037dd829e59c0b2f4f..d0cea1915bd57c918f52768b7e407d0226a4e662 100644 --- a/src/db/repo/collection-resources.repo.ts +++ b/src/db/repo/collection-resources.repo.ts @@ -41,6 +41,7 @@ export class collectionResourcesRepo { } } catch (error) { console.error("Error associating collection with resources", error); + throw error; } } diff --git a/src/db/repo/resource-educational-stages.repo.ts b/src/db/repo/resource-educational-stages.repo.ts index f4de4fedef00e1ed4cf25e686f6c0e452b449222..e895df750aa15fcacceb65d27de0a0c36f062261 100644 --- a/src/db/repo/resource-educational-stages.repo.ts +++ b/src/db/repo/resource-educational-stages.repo.ts @@ -43,6 +43,7 @@ export class resourceEducationalStagesRepo { } } catch (error) { console.error("Error associating resource with educational stages:", error); + throw error; } } @@ -79,6 +80,8 @@ export class resourceEducationalStagesRepo { console.log(`Educational stage ${educationalStageId} removed from resource ${resourceId}`); } catch (error) { console.error("Error removing educational stage from resource:", error); + throw error; + } } diff --git a/src/db/repo/resource-language.repo.ts b/src/db/repo/resource-language.repo.ts index 389730455072cd133a99b32f206fa0f7e74d6628..95be6dbe4e8fe083dcae6a78e651852b754ababc 100644 --- a/src/db/repo/resource-language.repo.ts +++ b/src/db/repo/resource-language.repo.ts @@ -48,6 +48,8 @@ export class resourceLanguagesRepo { } } catch (error) { console.error("Error associating resource with languages:", error); + throw error; + } } diff --git a/src/db/repo/resource-likes.repo.ts b/src/db/repo/resource-likes.repo.ts index e9dbba8cad92a4928f82aad71e9e7c353350c791..534357e2cf5c7e14a6b7ae5c0325e6e1d59bb17a 100644 --- a/src/db/repo/resource-likes.repo.ts +++ b/src/db/repo/resource-likes.repo.ts @@ -41,6 +41,7 @@ export class resourceLikesRepo { } } catch (error) { console.error("Erro ao alternar like no recurso", error); + throw error; } } diff --git a/src/db/repo/resource-subjects.repo.ts b/src/db/repo/resource-subjects.repo.ts index 665a22d1ea34f6497ccbb735fcc17e36443f27c0..b1100f32a9a1c28a8af552f1c721b04a75fca453 100644 --- a/src/db/repo/resource-subjects.repo.ts +++ b/src/db/repo/resource-subjects.repo.ts @@ -47,6 +47,7 @@ export class resourceSubjectsRepo { } } catch (error) { console.error("Error associating resource with subjects:", error); + throw error; } } @@ -84,6 +85,7 @@ export class resourceSubjectsRepo { console.log(`Resource ${resourceId} removed from subjects: ${subjectIds.join(", ")}`); } catch (error) { console.error("Error removing resource from subjects:", error); + throw error; } } diff --git a/src/db/repo/search.repo.ts b/src/db/repo/search.repo.ts index 1e2390b74cdc8eb071110c8c03c79afee74c426b..8fdb59706e4dc11d16ae16be505dec3fdbf7de4f 100644 --- a/src/db/repo/search.repo.ts +++ b/src/db/repo/search.repo.ts @@ -92,7 +92,7 @@ export class SearchRepo { .select({ id: sql`users.id`, name: sql`users.name`, - user: sql`users.username`, + username: sql`users.username`, description: sql`users.description`, roles: sql`string_agg(distinct roles.name, ',')`, institutions: sql`string_agg(distinct inst.name, ',')`, @@ -124,7 +124,9 @@ export class SearchRepo { ); - const results = await query.execute(); + let results = await query.execute(); + + results = results.map(result => ({ ...result, score: Number(result.score) })); // converte strings separadas por vírgula em arrays return results.map((result) => @@ -172,7 +174,9 @@ export class SearchRepo { sql`colst.follows`, ); - const results = await query.execute(); + let results = await query.execute(); + + results = results.map(result => ({ ...result, score: Number(result.score) })); // Parse and validate each result using Zod schema return results.map((result) => collectionIndexSchema.parse(result)); diff --git a/src/db/repo/user-achievements.repo.ts b/src/db/repo/user-achievements.repo.ts index b9057c28b80540c7c75d7d6954233dc0ea9ab871..df1be436154d334754e0e73d7b3dc4166dfb870d 100644 --- a/src/db/repo/user-achievements.repo.ts +++ b/src/db/repo/user-achievements.repo.ts @@ -39,6 +39,7 @@ export class userAchievementsRepo { } } catch (error) { console.error("Error associating user with achievements", error); + throw error; } } diff --git a/src/db/repo/user-collection.repo.ts b/src/db/repo/user-collection.repo.ts index 3963f06b58785dbec3d203a33b7998b2a0f3b8ec..18ec700895f73db6954fbeb5a3dfe10b207f29d6 100644 --- a/src/db/repo/user-collection.repo.ts +++ b/src/db/repo/user-collection.repo.ts @@ -41,6 +41,7 @@ export class userCollectionsRepo { } } catch (error) { console.error("Error associating user with collection", error); + throw error; } } diff --git a/src/db/schema/search.schema.ts b/src/db/schema/search.schema.ts index 176a017e9f0269c2c66a8d29bde73029103a7d69..7fac6e8a6a931dc429d342c748a12b631d226e23 100644 --- a/src/db/schema/search.schema.ts +++ b/src/db/schema/search.schema.ts @@ -28,7 +28,7 @@ export const resourceIndexSchema = z.object({ export const userIndexSchema = z.object({ id: z.number(), name: z.string(), - username: z.string(), + user: z.string(), description: z.string().nullable(), roles: z.array(z.string()), institutions: z.array(z.string()), @@ -44,7 +44,7 @@ export const userIndexSchema = z.object({ export const collectionIndexSchema = z.object({ id: z.number(), name: z.string(), - user: z.string().nullable(), + username: z.string().nullable(), user_id: z.number(), description: z.string().nullable(), // created_at: z.string(), diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 722e64c15c429c0458c3343378ae724aeacfcadc..0799ee44c9fcdcda0fe4650cfcb1b1274a344030 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -184,35 +184,69 @@ export const authRouter = new Hono().post( // Redirecionamos para página do front que vai salvar o token return c.redirect(env.FRONT_END_RETURN_URL + '?' + params.toString()); - } - ) - .post('/signup', signupRoute, - zValidator('json', userSchemas.userInputSchema), - async (c) => { - try { - const input = await c.req.valid('json'); - console.log("Dados recebidos:", input); - - // Criando userStats - console.log("Criando userStats..."); - const userStats = userStatsSchemas.dto.parse(await userStatsService.create()); - console.log("UserStats criado:", userStats); + }) + .post('/signup', signupRoute, zValidator('json', userSchemas.userInputSchema), async (c) => { + const input = await c.req.valid('json'); + console.log("Dados recebidos:", input); - input.user_stats_id = userStats.id; + let user; + let userStats; + try { + // 1. Criar userStats + console.log("Criando userStats..."); + userStats = userStatsSchemas.dto.parse(await userStatsService.create()); + console.log("UserStats criado:", userStats); - const user = userSchemas.userDtoSchema.parse(await userService.create(input)); + // 2. Criar usuário com userStats + input.user_stats_id = userStats.id; + user = userSchemas.userDtoSchema.parse(await userService.create(input)); + console.log("Usuário criado:", user); - await authService.sendConfirmationEmail(user.email, "Confirmação de email", user.name, user.email); + // 3. Indexar usuário + try { await searchService.indexUser(user.id); + console.log("Usuário indexado com sucesso."); + } catch (indexErr) { + console.error("Erro ao indexar usuário:", indexErr); + await userService.systemDelete(user.id); + await userStatsService.delete(userStats.id); + console.warn("Usuário deletado após falha de indexação."); + return c.text('could not create', 500); + } + } catch (dbErr) { + console.error("Erro ao criar usuário ou userStats:", dbErr); - return c.json({ user, userStats }); - } catch (e) { - console.error("Erro no cadastro:", e); - return c.text('could not create', 500); + // Best effort cleanup + if (userStats?.id) { + try { + await userStatsService.delete(userStats.id); + } catch (err) { + console.warn("Erro ao deletar userStats no cleanup:", err); + } + } + + if (input?.id) { + try { + await userService.systemDelete(input.id); + } catch (err) { + console.warn("Erro ao deletar usuário no cleanup:", err); + } } + + return c.text('could not create', 500); } - ) + + // 4. Enviar email de confirmação (sem rollback em caso de erro) + try { + await authService.sendConfirmationEmail(user.email, "Confirmação de email", user.name, user.email); + console.log("Email de confirmação enviado."); + } catch (emailErr) { + console.error("Erro ao enviar email de confirmação:", emailErr); + } + + return c.json({ user, userStats }); + }) .post('/recoveryPassword/:id', requestPasswordResetRoute, async (c) => { try { diff --git a/src/routes/collections.route.ts b/src/routes/collections.route.ts index 29fd74d2b9a5dea60791048fd6e290f1f1f31b48..16a709aede843ced5f26db6b0e2dfdc80803cb83 100644 --- a/src/routes/collections.route.ts +++ b/src/routes/collections.route.ts @@ -53,38 +53,56 @@ async function addOwnerToCollections(collections: CollectionModel[]): Promise<Co } export const collectionsRouter = honoWithJwt() - .post( - '/create', createCollectionRoute, + .post('/create', + createCollectionRoute, zValidator('json', collectionSchemas.collectionInputSchema), async (c) => { - try { - const input = await c.req.valid('json') + const input = await c.req.valid('json'); + console.log("Dados recebidos:", input); - const collectionStats = collectionStatsSchemas.collectionStatsDtoSchema.parse( - await serviceStats.create() - ) + let collection; + let collectionStats; - input.collection_stats_id = collectionStats.id + try { + // 1. Criar collectionStats + console.log("Criando collectionStats..."); + collectionStats = collectionStatsSchemas.collectionStatsDtoSchema.parse( + await serviceStats.create() + ); + console.log("collectionStats criado:", collectionStats); - const collection = collectionSchemas.collectionDtoSchema.parse( + // 2. Criar a collection com referência ao collectionStats + input.collection_stats_id = collectionStats.id; + collection = collectionSchemas.collectionDtoSchema.parse( await service.create(input) - ) - - await searchService.indexCollection(collection.id) - - return c.json({ collection, collectionStats }) - } catch (e) { - return c.json( - createApexError({ - status: 'error', - message: 'could not create collection', - code: HttpStatus.BAD_REQUEST, - path: c.req.routePath, - suggestion: 'check the input and try again', - }), - HttpStatus.BAD_REQUEST - ) + ); + console.log("Collection criada:", collection); + + // 3. Tentar indexar a collection + try { + await searchService.indexCollection(collection.id); + console.log("Collection indexada com sucesso."); + } catch (indexErr) { + console.error("Erro ao indexar collection:", indexErr); + await service.deletePermanently(collection.id); + await serviceStats.delete(collectionStats.id); + console.warn("Collection deletada após falha de indexação."); + return c.text('could not create', 500); + } + + } catch (dbErr) { + console.error("Erro ao criar collection ou collectionStats:", dbErr); + // Best effort cleanup + if (collectionStats?.id) { + await serviceStats.delete(collectionStats.id).catch(() => {}); + } + if (input.id) { + await service.deletePermanently(input.id).catch(() => {}); + } + return c.text('could not create', 500); } + + return c.json({ collection, collectionStats }); } ) .post( diff --git a/src/routes/resource.route.ts b/src/routes/resource.route.ts index dbeca2888f6adcc0fb2aae4589741610356e253e..c06ed87344f3e078dc3d41c50d85088a9b65dbe7 100644 --- a/src/routes/resource.route.ts +++ b/src/routes/resource.route.ts @@ -45,11 +45,6 @@ const serviceHomologation = Container.get(HomologationService); type ResourceWithObjectTypeName = ResourceModel & { objectTypeName: string; }; - - - - - export const resourceSchemaJson = { update: z.object({ id: z.number().int(), // O id é obrigatório para atualizar o recurso @@ -121,79 +116,111 @@ export const resourceRouter = honoWithJwt() // create a resource, nao precisa mandar o id do usuario dono .post('/create', createResourceRoute, zValidator('json', resourceSchemaJson.input), - async (c) => { const user_id = c.get('jwtPayload').id; - try { - let input = c.req.valid('json') - input = { - ...input, - user_id: user_id, - state: 'draft', - } - - //cria o stats do recurso correspondente - const stats = resourceStatsSchema.dto.parse( - await serviceStats.create() - ) - - input.resource_stats_id = stats.id - - //cria o recurso principal - const resource = resourceSchema.dto.parse( - await service.create(input) - ) - - // associa tudo em paralelo - await Promise.all([ - resourceSubjectsService.associateResourceWithSubjects(resource.id, input.subjects), - resourceLanguagesService.associateResourceWithLanguages(resource.id, input.language), - resourceEducationalStagesService.associateResourceWithEducationalStages(resource.id, input.educational_stages), - ]); - - // 3. Busca os dados relacionados - const [subjects, languages, educationalStages] = await Promise.all([ - resourceSubjectsService.getSubjectsByResource(resource.id), - resourceLanguagesService.getLanguagesByResource(resource.id), - resourceEducationalStagesService.getEducationalStagesByResource(resource.id), - ]); + let input = await c.req.valid('json'); + let resource; + let stats; + try { + // Enriquecendo input + input = { + ...input, + user_id, + state: 'draft', + }; + + // 1. Criar stats do recurso + stats = resourceStatsSchema.dto.parse(await serviceStats.create()); + input.resource_stats_id = stats.id; + + // 2. Criar recurso principal + resource = resourceSchema.dto.parse(await service.create(input)); + + // 3. Associar dados relacionados + await Promise.all([ + resourceSubjectsService.associateResourceWithSubjects(resource.id, input.subjects), + resourceLanguagesService.associateResourceWithLanguages(resource.id, input.language), + resourceEducationalStagesService.associateResourceWithEducationalStages(resource.id, input.educational_stages), + ]); + + // 4. Indexar o recurso + try { + await searchService.indexResource(resource.id); + } catch (indexErr) { + console.error("Erro ao indexar recurso:", indexErr); + // Cleanup em caso de falha de indexação + if (resource?.id) { + try { + await service.deleteData(resource.id); + } catch (delErr) { + console.warn("Erro ao deletar recurso após falha de indexação:", delErr); + } + } + if (stats?.id) { + try { + await serviceStats.delete(stats.id); + } catch (delErr) { + console.warn("Erro ao deletar stats após falha de indexação:", delErr); + } + } + return c.text('could not create', 500); + } - // 4. Extrai apenas os IDs - const subjectIds = subjects.map((s) => s.id); - const languageIds = languages.map((l) => l.id); - const educationalStageIds = educationalStages.map((e) => e.id); + // 5. Buscar os dados relacionados + const [subjects, languages, educationalStages] = await Promise.all([ + resourceSubjectsService.getSubjectsByResource(resource.id), + resourceLanguagesService.getLanguagesByResource(resource.id), + resourceEducationalStagesService.getEducationalStagesByResource(resource.id), + ]); - //indexando - await searchService.indexResource(resource.id) + // 6. Extrair IDs + const subjectIds = subjects.map((s) => s.id); + const languageIds = languages.map((l) => l.id); + const educationalStageIds = educationalStages.map((e) => e.id); + // 7. Retornar o recurso finalizado + return c.json({ + ...resourceSchema.return.parse(resource), + subjects: subjectIds, + language: languageIds, + educational_stages: educationalStageIds, + }); - // 5. Retorna o recurso com os arrays de IDs - return c.json({ - ...resourceSchema.return.parse(resource), - subjects: subjectIds, - language: languageIds, - educational_stages: educationalStageIds, - }); + } catch (e) { + console.error("Erro ao criar recurso:", e); + + // Best effort cleanup + if (resource?.id) { + try { + await service.deleteData(resource.id); + } catch (err) { + console.warn("Erro ao deletar recurso durante cleanup:", err); + } + } + if (stats?.id) { + try { + await serviceStats.delete(stats.id); + } catch (err) { + console.warn("Erro ao deletar stats durante cleanup:", err); + } + } - } catch (e) { return c.json( createApexError({ - status: 'error', - message: 'could not create the resource', - code: HttpStatus.BAD_REQUEST, - path: c.req.routePath, - suggestion: 'check the json input and try again', + status: 'error', + message: 'could not create the resource', + code: HttpStatus.BAD_REQUEST, + path: c.req.routePath, + suggestion: 'check the json input and try again', }), HttpStatus.BAD_REQUEST - ) + ); } - } ) - // update a resource .post('/update', updateResourceRoute, zValidator('json', resourceSchemaJson.update), diff --git a/src/services/search.service.ts b/src/services/search.service.ts index ce42ce058ecfdf99edd15865c316722c2a8f211f..e0dc0962a71cb159fc031142bf027d1fa8b3348e 100644 --- a/src/services/search.service.ts +++ b/src/services/search.service.ts @@ -142,9 +142,10 @@ export class SearchService { }); console.log(`Document ${document.id} indexed successfully`); - } catch (error) { + } catch (error) { console.error(`Error indexing document with ID ${id}:`, error); - } + throw error; + } } // filters: filtros para valores exatos (ex: language: "Português")