From c074bc18721dfceaeefe3d4f5b4e07aa42ae6322 Mon Sep 17 00:00:00 2001
From: Janaina <jsk22@inf.ufpr.br>
Date: Wed, 23 Oct 2024 11:24:57 -0300
Subject: [PATCH] Issue #26/CREATE intermediary table user-achievements

---
 src/db/migrations/0000_cold_old_lace.sql      | 431 ++++++++++++++++++
 src/db/migrations/meta/0000_snapshot.json     | 145 ++----
 src/db/migrations/meta/_journal.json          |  10 +-
 .../relations/user-achievements.relation.ts   |  40 ++
 src/db/repo/user-achievements.repo.ts         | 158 +++++++
 src/db/schema/index.ts                        |   5 +-
 src/db/schema/user.schema.ts                  |   4 +-
 src/db/seed.ts                                |   1 +
 src/db/seeds/index.ts                         |   1 +
 src/db/seeds/user-achievements.seed.ts        |  26 ++
 src/index.ts                                  |   3 +
 src/routes/user-achievements.route.ts         | 140 ++++++
 src/services/user-achievements.service.ts     |  34 ++
 13 files changed, 900 insertions(+), 98 deletions(-)
 create mode 100644 src/db/migrations/0000_cold_old_lace.sql
 create mode 100644 src/db/relations/user-achievements.relation.ts
 create mode 100644 src/db/repo/user-achievements.repo.ts
 create mode 100644 src/db/seeds/user-achievements.seed.ts
 create mode 100644 src/routes/user-achievements.route.ts
 create mode 100644 src/services/user-achievements.service.ts

diff --git a/src/db/migrations/0000_cold_old_lace.sql b/src/db/migrations/0000_cold_old_lace.sql
new file mode 100644
index 0000000..05cab79
--- /dev/null
+++ b/src/db/migrations/0000_cold_old_lace.sql
@@ -0,0 +1,431 @@
+CREATE TABLE IF NOT EXISTS "achievement" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar(255) NOT NULL,
+	"description" text,
+	"reward_experience" numeric,
+	"reward_points" numeric,
+	"state" integer DEFAULT 0 NOT NULL,
+	"repeatable" integer NOT NULL,
+	"is_resettable" boolean NOT NULL,
+	"created_at" timestamp DEFAULT now() NOT NULL,
+	"updated_at" timestamp DEFAULT now() NOT NULL,
+	CONSTRAINT "achievement_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "collection_likes" (
+	"user_id" integer NOT NULL,
+	"collection" integer NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "collection_resources" (
+	"collection_id" integer NOT NULL,
+	"resource_id" integer NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "collection_stats" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"collection_id" bigint NOT NULL,
+	"views" bigint DEFAULT 0,
+	"downloads" bigint DEFAULT 0,
+	"likes" bigint DEFAULT 0,
+	"shares" bigint DEFAULT 0,
+	"score" bigint DEFAULT 0,
+	"follows" bigint DEFAULT 0,
+	CONSTRAINT "collection_stats_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "collection" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar(255),
+	"description" text,
+	"is_private" boolean,
+	"is_active" boolean DEFAULT true,
+	"created_at" timestamp DEFAULT now() NOT NULL,
+	"updated_at" timestamp,
+	"deleted_at" timestamp,
+	"thumbnail" varchar(255),
+	CONSTRAINT "collection_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "complaint" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"state" integer DEFAULT 0 NOT NULL,
+	"description" text NOT NULL,
+	"evaluatedUser" integer NOT NULL,
+	"resource_id" integer,
+	"collection_id" integer,
+	"user_id" integer,
+	"evaluated_at" timestamp,
+	"created_at" timestamp DEFAULT now() NOT NULL,
+	"q1" boolean,
+	"q2" boolean,
+	"q3" boolean,
+	"q4" boolean
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "educational_stages" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar NOT NULL,
+	CONSTRAINT "educational_stages_id_unique" UNIQUE("id"),
+	CONSTRAINT "educational_stages_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "follows" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"user_id" integer NOT NULL,
+	"follower_id" integer NOT NULL,
+	CONSTRAINT "follows_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "institution" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar(255) NOT NULL,
+	"uf" varchar(2),
+	"city" varchar(255),
+	"cep" varchar(10),
+	"created_at" timestamp DEFAULT now() NOT NULL,
+	"updated_at" timestamp DEFAULT now() NOT NULL,
+	CONSTRAINT "institution_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "language" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar NOT NULL,
+	"code" varchar NOT NULL,
+	CONSTRAINT "language_id_unique" UNIQUE("id"),
+	CONSTRAINT "language_name_unique" UNIQUE("name"),
+	CONSTRAINT "language_code_unique" UNIQUE("code")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "licenses" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar NOT NULL,
+	"description" varchar NOT NULL,
+	"url" varchar NOT NULL,
+	CONSTRAINT "licenses_id_unique" UNIQUE("id"),
+	CONSTRAINT "licenses_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "object_types" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar(255) NOT NULL,
+	CONSTRAINT "object_types_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "reset_ticket" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"user_id" integer NOT NULL,
+	"token_hash" varchar(255) NOT NULL,
+	"expiration_date" timestamp NOT NULL,
+	"token_used" boolean DEFAULT false NOT NULL,
+	"valid_token" boolean DEFAULT true NOT NULL,
+	"created_at" timestamp DEFAULT now() NOT NULL,
+	"updated_at" timestamp DEFAULT now() NOT NULL,
+	CONSTRAINT "reset_ticket_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "resource_educational_stages" (
+	"resource_id" integer NOT NULL,
+	"educational_stage_id" integer NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "resource_languages" (
+	"resource_id" integer NOT NULL,
+	"language_id" integer NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "resource_likes" (
+	"user_id" integer NOT NULL,
+	"resource_id" integer NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "resource_subjects" (
+	"resource_id" integer NOT NULL,
+	"subject_id" integer NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "resource" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar(256) NOT NULL,
+	"author" varchar(256) NOT NULL,
+	"description" varchar(256),
+	"bucket_key" varchar(256),
+	"link" varchar(256),
+	"thumbnail" varchar(256) NOT NULL,
+	"active" boolean DEFAULT false NOT NULL,
+	"published_at" timestamp,
+	"submited_at" timestamp,
+	"created_at" timestamp DEFAULT now() NOT NULL,
+	"updated_at" timestamp,
+	"deleted_at" timestamp,
+	CONSTRAINT "resource_id_unique" UNIQUE("id"),
+	CONSTRAINT "resource_bucket_key_unique" UNIQUE("bucket_key")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "role" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar(255) NOT NULL,
+	"description" text,
+	"created_at" timestamp DEFAULT now() NOT NULL,
+	"updated_at" timestamp DEFAULT now() NOT NULL,
+	CONSTRAINT "role_id_unique" UNIQUE("id"),
+	CONSTRAINT "role_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "stats_resources" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"resource_id" bigint NOT NULL,
+	"views" bigint DEFAULT 0 NOT NULL,
+	"downloads" bigint DEFAULT 0 NOT NULL,
+	"shares" bigint DEFAULT 0 NOT NULL,
+	"score" bigint DEFAULT 0 NOT NULL,
+	CONSTRAINT "stats_resources_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "subjects" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar NOT NULL,
+	CONSTRAINT "subjects_id_unique" UNIQUE("id"),
+	CONSTRAINT "subjects_name_unique" UNIQUE("name")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "submission" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"is_accepted" boolean DEFAULT false NOT NULL,
+	"justification" text,
+	"resource_id" integer NOT NULL,
+	"submitter_id" integer NOT NULL,
+	"curator_id" integer NOT NULL,
+	"created_at" timestamp DEFAULT now() NOT NULL,
+	"updated_at" timestamp,
+	"answered_at" timestamp,
+	"q1" boolean,
+	"q2" boolean,
+	"q3" boolean,
+	"q4" boolean
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "user_achievements" (
+	"user_id" integer NOT NULL,
+	"achievement_id" integer NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "user_institution" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"user_id" integer NOT NULL,
+	"institution_id" integer NOT NULL,
+	CONSTRAINT "user_institution_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "user_role" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"user_id" integer NOT NULL,
+	"role_id" integer NOT NULL,
+	CONSTRAINT "user_role_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "user_stats" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"user_id" integer NOT NULL,
+	"score" integer DEFAULT 0 NOT NULL,
+	"likes" integer DEFAULT 0 NOT NULL,
+	"likes_received" integer DEFAULT 0 NOT NULL,
+	"follows" integer DEFAULT 0 NOT NULL,
+	"followers" integer DEFAULT 0 NOT NULL,
+	"collections" integer DEFAULT 0 NOT NULL,
+	"submitted_resources" integer DEFAULT 0 NOT NULL,
+	"approved_resources" integer DEFAULT 0 NOT NULL,
+	"reviewed_resources" integer DEFAULT 0 NOT NULL,
+	"comments" integer DEFAULT 0 NOT NULL,
+	CONSTRAINT "user_stats_id_unique" UNIQUE("id")
+);
+--> statement-breakpoint
+CREATE TABLE IF NOT EXISTS "user" (
+	"id" serial PRIMARY KEY NOT NULL,
+	"name" varchar(255) NOT NULL,
+	"username" varchar(255) NOT NULL,
+	"password" varchar(255) NOT NULL,
+	"email" varchar(255) NOT NULL,
+	"description" text DEFAULT 'sem descrição',
+	"institution" text DEFAULT 'sem instituição',
+	"birthday" timestamp NOT NULL,
+	"cpf" varchar(255) NOT NULL,
+	"created_at" timestamp DEFAULT now() NOT NULL,
+	"updated_at" timestamp DEFAULT now() NOT NULL,
+	"confirmed_at" timestamp,
+	"confirmation_sent_at" timestamp,
+	"deleted_at" timestamp,
+	"reactivated_at" timestamp,
+	"active" boolean DEFAULT true,
+	CONSTRAINT "user_id_unique" UNIQUE("id"),
+	CONSTRAINT "user_username_unique" UNIQUE("username"),
+	CONSTRAINT "user_email_unique" UNIQUE("email")
+);
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "collection_likes" ADD CONSTRAINT "collection_likes_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "collection_likes" ADD CONSTRAINT "collection_likes_collection_collection_id_fk" FOREIGN KEY ("collection") REFERENCES "public"."collection"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "collection_resources" ADD CONSTRAINT "collection_resources_collection_id_collection_id_fk" FOREIGN KEY ("collection_id") REFERENCES "public"."collection"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "collection_resources" ADD CONSTRAINT "collection_resources_resource_id_resource_id_fk" FOREIGN KEY ("resource_id") REFERENCES "public"."resource"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "collection_stats" ADD CONSTRAINT "collection_stats_collection_id_collection_id_fk" FOREIGN KEY ("collection_id") REFERENCES "public"."collection"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "complaint" ADD CONSTRAINT "complaint_evaluatedUser_user_id_fk" FOREIGN KEY ("evaluatedUser") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "follows" ADD CONSTRAINT "follows_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "follows" ADD CONSTRAINT "follows_follower_id_user_id_fk" FOREIGN KEY ("follower_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "reset_ticket" ADD CONSTRAINT "reset_ticket_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "resource_educational_stages" ADD CONSTRAINT "resource_educational_stages_resource_id_resource_id_fk" FOREIGN KEY ("resource_id") REFERENCES "public"."resource"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "resource_educational_stages" ADD CONSTRAINT "resource_educational_stages_educational_stage_id_educational_stages_id_fk" FOREIGN KEY ("educational_stage_id") REFERENCES "public"."educational_stages"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "resource_languages" ADD CONSTRAINT "resource_languages_resource_id_resource_id_fk" FOREIGN KEY ("resource_id") REFERENCES "public"."resource"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "resource_languages" ADD CONSTRAINT "resource_languages_language_id_language_id_fk" FOREIGN KEY ("language_id") REFERENCES "public"."language"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "resource_likes" ADD CONSTRAINT "resource_likes_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "resource_likes" ADD CONSTRAINT "resource_likes_resource_id_resource_id_fk" FOREIGN KEY ("resource_id") REFERENCES "public"."resource"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "resource_subjects" ADD CONSTRAINT "resource_subjects_resource_id_resource_id_fk" FOREIGN KEY ("resource_id") REFERENCES "public"."resource"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "resource_subjects" ADD CONSTRAINT "resource_subjects_subject_id_subjects_id_fk" FOREIGN KEY ("subject_id") REFERENCES "public"."subjects"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "stats_resources" ADD CONSTRAINT "stats_resources_resource_id_resource_id_fk" FOREIGN KEY ("resource_id") REFERENCES "public"."resource"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "submission" ADD CONSTRAINT "submission_resource_id_resource_id_fk" FOREIGN KEY ("resource_id") REFERENCES "public"."resource"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "submission" ADD CONSTRAINT "submission_submitter_id_user_id_fk" FOREIGN KEY ("submitter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "submission" ADD CONSTRAINT "submission_curator_id_user_id_fk" FOREIGN KEY ("curator_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "user_achievements" ADD CONSTRAINT "user_achievements_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "user_achievements" ADD CONSTRAINT "user_achievements_achievement_id_user_id_fk" FOREIGN KEY ("achievement_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "user_institution" ADD CONSTRAINT "user_institution_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "user_institution" ADD CONSTRAINT "user_institution_institution_id_institution_id_fk" FOREIGN KEY ("institution_id") REFERENCES "public"."institution"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "user_role" ADD CONSTRAINT "user_role_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "user_role" ADD CONSTRAINT "user_role_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."role"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
+--> statement-breakpoint
+DO $$ BEGIN
+ ALTER TABLE "user_stats" ADD CONSTRAINT "user_stats_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+EXCEPTION
+ WHEN duplicate_object THEN null;
+END $$;
diff --git a/src/db/migrations/meta/0000_snapshot.json b/src/db/migrations/meta/0000_snapshot.json
index 81c96f1..12f80d5 100644
--- a/src/db/migrations/meta/0000_snapshot.json
+++ b/src/db/migrations/meta/0000_snapshot.json
@@ -1,5 +1,5 @@
 {
-  "id": "a57a8a9a-72a9-43a1-8379-ae187d1f5d05",
+  "id": "859d43fc-3e36-405b-8a21-e76b61e071e6",
   "prevId": "00000000-0000-0000-0000-000000000000",
   "version": "7",
   "dialect": "postgresql",
@@ -609,100 +609,6 @@
         }
       }
     },
-    "public.items": {
-      "name": "items",
-      "schema": "",
-      "columns": {
-        "id": {
-          "name": "id",
-          "type": "serial",
-          "primaryKey": true,
-          "notNull": true
-        },
-        "name": {
-          "name": "name",
-          "type": "varchar(256)",
-          "primaryKey": false,
-          "notNull": true
-        },
-        "price": {
-          "name": "price",
-          "type": "numeric(10, 2)",
-          "primaryKey": false,
-          "notNull": false
-        },
-        "discount": {
-          "name": "discount",
-          "type": "numeric(10, 2)",
-          "primaryKey": false,
-          "notNull": false
-        },
-        "description": {
-          "name": "description",
-          "type": "varchar(256)",
-          "primaryKey": false,
-          "notNull": false
-        },
-        "is_active": {
-          "name": "is_active",
-          "type": "boolean",
-          "primaryKey": false,
-          "notNull": false,
-          "default": false
-        },
-        "item_type": {
-          "name": "item_type",
-          "type": "varchar(256)",
-          "primaryKey": false,
-          "notNull": false
-        },
-        "created_at": {
-          "name": "created_at",
-          "type": "timestamp",
-          "primaryKey": false,
-          "notNull": true,
-          "default": "now()"
-        },
-        "updated_at": {
-          "name": "updated_at",
-          "type": "timestamp",
-          "primaryKey": false,
-          "notNull": true
-        },
-        "achievement_id": {
-          "name": "achievement_id",
-          "type": "integer",
-          "primaryKey": false,
-          "notNull": false
-        }
-      },
-      "indexes": {},
-      "foreignKeys": {
-        "items_achievement_id_achievement_id_fk": {
-          "name": "items_achievement_id_achievement_id_fk",
-          "tableFrom": "items",
-          "tableTo": "achievement",
-          "columnsFrom": [
-            "achievement_id"
-          ],
-          "columnsTo": [
-            "id"
-          ],
-          "onDelete": "no action",
-          "onUpdate": "no action"
-        }
-      },
-      "compositePrimaryKeys": {},
-      "uniqueConstraints": {
-        "items_id_unique": {
-          "name": "items_id_unique",
-          "nullsNotDistinct": false,
-          "columns": [
-            "id"
-          ]
-        }
-      }
-    },
     "public.language": {
       "name": "language",
       "schema": "",
@@ -1513,6 +1419,55 @@
       "compositePrimaryKeys": {},
       "uniqueConstraints": {}
     },
+    "public.user_achievements": {
+      "name": "user_achievements",
+      "schema": "",
+      "columns": {
+        "user_id": {
+          "name": "user_id",
+          "type": "integer",
+          "primaryKey": false,
+          "notNull": true
+        },
+        "achievement_id": {
+          "name": "achievement_id",
+          "type": "integer",
+          "primaryKey": false,
+          "notNull": true
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {
+        "user_achievements_user_id_user_id_fk": {
+          "name": "user_achievements_user_id_user_id_fk",
+          "tableFrom": "user_achievements",
+          "tableTo": "user",
+          "columnsFrom": [
+            "user_id"
+          ],
+          "columnsTo": [
+            "id"
+          ],
+          "onDelete": "cascade",
+          "onUpdate": "no action"
+        },
+        "user_achievements_achievement_id_user_id_fk": {
+          "name": "user_achievements_achievement_id_user_id_fk",
+          "tableFrom": "user_achievements",
+          "tableTo": "user",
+          "columnsFrom": [
+            "achievement_id"
+          ],
+          "columnsTo": [
+            "id"
+          ],
+          "onDelete": "cascade",
+          "onUpdate": "no action"
+        }
+      },
+      "compositePrimaryKeys": {},
+      "uniqueConstraints": {}
+    },
     "public.user_institution": {
       "name": "user_institution",
       "schema": "",
diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json
index eaa8fcf..a7ac230 100644
--- a/src/db/migrations/meta/_journal.json
+++ b/src/db/migrations/meta/_journal.json
@@ -1,5 +1,13 @@
 {
   "version": "7",
   "dialect": "postgresql",
-  "entries": []
+  "entries": [
+    {
+      "idx": 0,
+      "version": "7",
+      "when": 1729690423126,
+      "tag": "0000_cold_old_lace",
+      "breakpoints": true
+    }
+  ]
 }
\ No newline at end of file
diff --git a/src/db/relations/user-achievements.relation.ts b/src/db/relations/user-achievements.relation.ts
new file mode 100644
index 0000000..fca23ef
--- /dev/null
+++ b/src/db/relations/user-achievements.relation.ts
@@ -0,0 +1,40 @@
+import { integer, pgTable } from "drizzle-orm/pg-core";
+import { achievementTable, userTable } from "../schema";
+import { relations } from "drizzle-orm";
+import { createInsertSchema, createSelectSchema } from "drizzle-zod";
+import type { z } from "zod";
+
+const userAchievementsTable = pgTable("user_achievements", {
+    user_id: integer("user_id").notNull().references(() => userTable.id, {onDelete: 'cascade'}),
+    achievement_id: integer("achievement_id").notNull().references(() => userTable.id, {onDelete: 'cascade'})
+})
+
+export const userAchievementRelations = relations(userAchievementsTable, ({one}) => ({
+    user: one (userTable, {
+        fields: [userAchievementsTable.user_id],
+        references: [userTable.id]
+    }),
+    achievement: one(achievementTable, {
+        fields: [userAchievementsTable.achievement_id],
+        references: [achievementTable.id]
+    })
+}))
+
+const userAchievementsModelSchema = createSelectSchema(userAchievementsTable)
+const userAchievementsRelationsDtoSchema = userAchievementsModelSchema
+const userAchievementsInputSchema = createInsertSchema(userAchievementsTable)
+const userAchievementsUpdateSchema = userAchievementsInputSchema.partial().required({ user_id: true, achievement_id: true })
+
+export type UserAchievementsModel = z.infer<typeof userAchievementsModelSchema>
+export type UserAchievementDto = z.infer<typeof userAchievementsRelationsDtoSchema>
+export type UserAchievementsInput = z.infer<typeof userAchievementsInputSchema>
+export type UserAchievementsUpdate = z.infer<typeof userAchievementsUpdateSchema>
+
+export const userAchievementsSchemas = {
+    userAchievementsModelSchema,
+    userAchievementsRelationsDtoSchema,
+    userAchievementsInputSchema,
+    userAchievementsUpdateSchema,
+}
+
+export default userAchievementsTable
\ No newline at end of file
diff --git a/src/db/repo/user-achievements.repo.ts b/src/db/repo/user-achievements.repo.ts
new file mode 100644
index 0000000..b9057c2
--- /dev/null
+++ b/src/db/repo/user-achievements.repo.ts
@@ -0,0 +1,158 @@
+import { Service } from "typedi";
+import type { UserModel } from "../schema/user.schema";
+import db from "..";
+import { achievementTable, userAchievementsTable, userTable } from "../schema";
+import { and, eq, inArray } from "drizzle-orm";
+import type { AchievementModel } from "../schema/achievements.schema";
+
+@Service()
+export class userAchievementsRepo {
+  async associateUserWithAchievements(userId: UserModel['id'], achievementIds: UserModel['id'][]): Promise<void> {
+    try {
+      const existingAssociations = await db
+        .select({
+          user_id: userAchievementsTable.user_id,
+        })
+        .from(userAchievementsTable)
+        .where(eq(userAchievementsTable.user_id, userId))
+        .execute();
+
+      const existingAchievementIds = existingAssociations.map(row => row.user_id);
+
+      const newAchievementIds = achievementIds.filter(achievementId => !existingAchievementIds.includes(achievementId));
+
+      if (newAchievementIds.length > 0) {
+        const valuesToInsert = newAchievementIds.map(achievementId => ({
+          user_id: userId,
+          achievement_id: achievementId,
+        }));
+
+        await db
+          .insert(userAchievementsTable)
+          .values(valuesToInsert)
+          .onConflictDoNothing()
+          .execute();
+
+        console.log(`User ${userId} associated with new achievements: ${newAchievementIds.join(', ')}`);
+      } else {
+        console.log(`No new achievements to associate with user ${userId}`);
+      }
+    } catch (error) {
+      console.error("Error associating user with achievements", error);
+    }
+  }
+
+  async getAchievementsByUser(userId: UserModel['id']): Promise<Partial<AchievementModel>[]> {
+    try {
+      const achievements = await db
+        .select({
+          id: achievementTable.id,
+          name: achievementTable.name,
+        })
+        .from(achievementTable)
+        .innerJoin(userAchievementsTable, eq(userAchievementsTable.achievement_id, achievementTable.id))
+        .where(eq(userAchievementsTable.user_id, userId))
+        .execute();
+
+      return achievements;
+    } catch (error) {
+      console.error("Error getting achievements by user", error);
+      throw error;
+    }
+  }
+
+  async removeAchievementsFromUser(userId: UserModel['id'], achievementIds: UserModel['id'][]): Promise<void> {
+    try {
+      await db
+        .delete(userAchievementsTable)
+        .where(
+          and(
+            eq(userAchievementsTable.user_id, userId),
+            inArray(userAchievementsTable.achievement_id, achievementIds)
+          )
+        )
+        .execute();
+
+      console.log(`Achievements ${achievementIds.join(', ')} removed from user ${userId}`);
+    } catch (error) {
+      console.error("Error removing achievements from user", error);
+      throw error;
+    }
+  }
+
+  async isAssociationExists(userId: UserModel['id'], achievementId: AchievementModel['id']): Promise<boolean> {
+    try {
+      const result = await db
+        .select()
+        .from(userAchievementsTable)
+        .where(
+          and(
+            eq(userAchievementsTable.user_id, userId),
+            eq(userAchievementsTable.achievement_id, achievementId)
+          )
+        )
+        .execute();
+
+      return result.length > 0;
+    } catch (error) {
+      console.error("Error checking if association exists", error);
+      throw error;
+    }
+  }
+
+  async getUsersByAchievement(achievementId: AchievementModel['id']): Promise<UserModel[]> {
+    try {
+      const users = await db
+        .select({
+          id: userTable.id,
+          name: userTable.name,
+        })
+        .from(userAchievementsTable)
+        .innerJoin(userTable, eq(userAchievementsTable.user_id, userTable.id))
+        .where(eq(userAchievementsTable.achievement_id, achievementId))
+        .execute();
+
+      return users;
+    } catch (error) {
+      console.error("Error getting users by achievement", error);
+      throw error;
+    }
+  }
+
+  async updateUserAchievements(userId: number, newAchievementIds: number[]): Promise<void> {
+    const existingAssociation = await db
+      .select({ achievementTable_id: userAchievementsTable.achievement_id })
+      .from(userAchievementsTable)
+      .where(eq(userAchievementsTable.user_id, userId))
+      .execute();
+
+    const existingAchievementIds = existingAssociation.map(row => row.achievementTable_id);
+
+    const achievementsIdsToAdd = newAchievementIds.filter(id => !existingAchievementIds.includes(id));
+    const achievementsIdsToRemove = existingAchievementIds.filter(id => !newAchievementIds.includes(id));
+
+    if (achievementsIdsToAdd.length > 0) {
+      const valuesToInsert = achievementsIdsToAdd.map(achievementId => ({
+        user_id: userId,
+        achievementId: achievementId,
+      }));
+
+      await db
+        .insert(userAchievementsTable)
+        .values(valuesToInsert)
+        .execute();
+    }
+
+    if (achievementsIdsToRemove.length > 0) {
+      await db
+        .delete(userAchievementsTable)
+        .where(
+          and(
+            eq(userAchievementsTable.user_id, userId),
+            inArray(userAchievementsTable.achievement_id, achievementsIdsToRemove)
+          )
+        )
+        .execute();
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/db/schema/index.ts b/src/db/schema/index.ts
index 1468616..da1a4fd 100644
--- a/src/db/schema/index.ts
+++ b/src/db/schema/index.ts
@@ -28,6 +28,7 @@ import userInstitutionRelationTable from '../relations/user-institution.relation
 import resourceLikesTable from '../relations/resource-likes.relation'
 import resourceEducationalStagesTable from './resource-educational-stages.schema'
 import itemsTable from './items.schema'
+import userAchievementsTable from '../relations/user-achievements.relation'
 
 export {
   userTable,
@@ -58,6 +59,7 @@ export {
   resourceEducationalStagesTable,
   achievementTable,
   itemsTable,
+  userAchievementsTable,
 }
 
 export const tables = [
@@ -84,5 +86,6 @@ export const tables = [
   userInstitutionRelationTable,
   resourceLikesTable,
   achievementTable,
-  itemsTable
+  itemsTable,
+  userAchievementsTable,
 ]
diff --git a/src/db/schema/user.schema.ts b/src/db/schema/user.schema.ts
index d369961..caac90e 100644
--- a/src/db/schema/user.schema.ts
+++ b/src/db/schema/user.schema.ts
@@ -4,6 +4,7 @@ import { createInsertSchema, createSelectSchema } from 'drizzle-zod'
 import { z } from 'zod'
 import userStatsTable from './user-stats.schema'
 import passwordRecoveryTable from './password-recovery.schema'
+import achievementTable from './achievements.schema'
 
 const userTable = pgTable('user', {
   id: serial('id').primaryKey()
@@ -49,7 +50,8 @@ const userTable = pgTable('user', {
 export const userTableRelation = relations(
   userTable, ({ one, many }) => ({
     userStats: one(userStatsTable),
-    passwordRecovery: many(passwordRecoveryTable)
+    passwordRecovery: many(passwordRecoveryTable),
+     achievements: many(userAchievementsTable)
   })
 )
 
diff --git a/src/db/seed.ts b/src/db/seed.ts
index e6faaab..cd53713 100644
--- a/src/db/seed.ts
+++ b/src/db/seed.ts
@@ -46,5 +46,6 @@ await seeds.resourceLikesSeed(db)
 await seeds.resourceEducationalStagesSeed(db)
 await seeds.achievementSeed(db)
 await seeds.itemsSeed(db)
+await seeds.userAchievementsSeed(db)
 
 await connection.end()
diff --git a/src/db/seeds/index.ts b/src/db/seeds/index.ts
index ba20da5..19c5bcf 100644
--- a/src/db/seeds/index.ts
+++ b/src/db/seeds/index.ts
@@ -23,3 +23,4 @@ export { default as resourceLikesSeed } from './resource-likes.seed'
 export { default as resourceEducationalStagesSeed } from './resource-educational-stages.seed'
 export { default as achievementSeed } from './achievement.seed'
 export { default as itemsSeed } from './items.seed'
+export { default as userAchievementsSeed } from './user-achievements.seed'
diff --git a/src/db/seeds/user-achievements.seed.ts b/src/db/seeds/user-achievements.seed.ts
new file mode 100644
index 0000000..523eae0
--- /dev/null
+++ b/src/db/seeds/user-achievements.seed.ts
@@ -0,0 +1,26 @@
+import type db from "..";
+import type { UserAchievementsInput } from "../relations/user-achievements.relation";
+import { userAchievementsTable } from "../schema";
+
+export default async function seed(db: db) {
+    await db.insert(userAchievementsTable).values(userAchievementsData)
+}
+
+const userAchievementsData: UserAchievementsInput[] = [
+    {
+        user_id: 1,
+        achievement_id: 1
+    },
+    {
+        user_id: 1,
+        achievement_id: 2
+    },
+    {
+        user_id: 2,
+        achievement_id: 1
+    },
+    {
+        user_id: 3,
+        achievement_id: 2
+    }
+]
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 76fac32..5612fc6 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -35,6 +35,7 @@ import { publicResourceLikesRoutes, resourceLikesRoutes } from './routes/resourc
 import { publicResourceEducationalStagesRouter, resourceEducationalStagesRouter } from './routes/resource-educational-stages.route'
 import { achievementRouter, publicAchievementRouter } from './routes/achievement.route'
 import { itemsRouter, publicItemsRouter } from './routes/items.route'
+import { publicUserAchievementsRoute, userAchievementsRouter } from './routes/user-achievements.route'
 
 const app = new Hono()
 
@@ -92,6 +93,7 @@ app
   .route('/items', publicItemsRouter)
   .route('/resourceLikes', publicResourceLikesRoutes)
   .route('/achievements', publicAchievementRouter)
+  .route('/userAchievements', publicUserAchievementsRoute)
 //rotas que precisam de token
 app
   .basePath('/api')
@@ -121,6 +123,7 @@ app
   .route('/items', itemsRouter)
   .route('/achievements', achievementRouter)
   .route('/resourceLikes', resourceLikesRoutes)
+  .route('/userAchievements', userAchievementsRouter)
 export default app
 export type AppType = typeof app
 
diff --git a/src/routes/user-achievements.route.ts b/src/routes/user-achievements.route.ts
new file mode 100644
index 0000000..526f0a3
--- /dev/null
+++ b/src/routes/user-achievements.route.ts
@@ -0,0 +1,140 @@
+import { UserAchievementsService } from "@/services/user-achievements.service";
+import Container from "typedi";
+import { z } from "zod";
+import { honoWithJwt } from "..";
+import { zValidator } from "@hono/zod-validator";
+import { createApexError, HttpStatus } from "@/services/error.service";
+import { Hono } from "hono";
+
+const associateSchema = z.object({
+  userId: z.number(),
+  achievementIds: z.array(z.number()),
+});
+
+const service = Container.get(UserAchievementsService);
+
+export const userAchievementsRouter = honoWithJwt()
+  .post('/associate', zValidator('json', associateSchema),
+    async (c) => {
+      try {
+        const { userId, achievementIds } = await c.req.valid('json');
+        await service.associateUserWithAchievements(userId, achievementIds);
+        return c.json({ message: 'Achievements associated successfully' });
+      } catch (e) {
+        return c.json(
+          createApexError({
+            status: 'error',
+            message: 'Failed to associate achievements',
+            code: HttpStatus.BAD_REQUEST,
+            path: c.req.routePath,
+            suggestion: 'Check the input and try again',
+          }),
+          HttpStatus.BAD_REQUEST
+        )
+      }
+    }
+  )
+  .post('/:userId/delete/achievement/:achievementId',
+    async (c) => {
+      try {
+        const userId = +c.req.param('userId');
+        const achievementId = +c.req.param('achievementId');
+        await service.removeAchievementsFromUser(userId, [achievementId]);
+        return c.json({ message: 'Achievement removed successfully' });
+      } catch (e) {
+        return c.json(
+          createApexError({
+            status: 'error',
+            message: 'Failed to remove achievement',
+            code: HttpStatus.BAD_REQUEST,
+            path: c.req.routePath,
+            suggestion: 'Check the input and try again',
+          }),
+          HttpStatus.BAD_REQUEST
+        )
+      }
+    }
+  )
+  .post('/update', zValidator('json', associateSchema),
+    async (c) => {
+      try {
+        const { userId, achievementIds } = await c.req.valid('json');
+        await service.updateUserAchievements(userId, achievementIds);
+        return c.json({ message: 'Achievements updated successfully' });
+      } catch (e) {
+        return c.json(
+          createApexError({
+            status: 'error',
+            message: 'Failed to update achievements',
+            code: HttpStatus.BAD_REQUEST,
+            path: c.req.routePath,
+            suggestion: 'Check the input and try again',
+          }),
+          HttpStatus.BAD_REQUEST
+        )
+      }
+    }
+  )
+
+export const publicUserAchievementsRoute = new Hono()
+  .get('/:userId/achievements',
+    async (c) => {
+      try {
+        const userId = +c.req.param('userId');
+        const achievements = await service.getAchievementsByUser(userId);
+        return c.json({ achievements });
+      } catch (e) {
+        return c.json(
+          createApexError({
+            status: 'error',
+            message: 'Failed to get user achievements',
+            code: HttpStatus.BAD_REQUEST,
+            path: c.req.routePath,
+            suggestion: 'Check the input and try again',
+          }),
+          HttpStatus.BAD_REQUEST
+        )
+      }
+    }
+  )
+  .get('/:userId/achievements/:achievementId/exists',
+    async (c) => {
+      try {
+        const userId = +c.req.param('userId');
+        const achievementId = +c.req.param('achievementId');
+        const exists = await service.isAssociationExists(userId, achievementId);
+        return c.json({ exists });
+      } catch (e) {
+        return c.json(
+          createApexError({
+            status: 'error',
+            message: 'Failed to check if association exists',
+            code: HttpStatus.BAD_REQUEST,
+            path: c.req.routePath,
+            suggestion: 'Check the input and try again',
+          }),
+          HttpStatus.BAD_REQUEST
+        )
+      }
+    }
+  )
+  .get('/achievements/:achievementId/users',
+    async (c) => {
+      try {
+        const achievementId = +c.req.param('achievementId');
+        const users = await service.getUsersByAchievement(achievementId);
+        return c.json({ users });
+      } catch (e) {
+        return c.json(
+          createApexError({
+            status: 'error',
+            message: 'Failed to get users by achievement',
+            code: HttpStatus.BAD_REQUEST,
+            path: c.req.routePath,
+            suggestion: 'Check the input and try again',
+          }),
+          HttpStatus.BAD_REQUEST
+        )
+      }
+    }
+  )
\ No newline at end of file
diff --git a/src/services/user-achievements.service.ts b/src/services/user-achievements.service.ts
new file mode 100644
index 0000000..c94c632
--- /dev/null
+++ b/src/services/user-achievements.service.ts
@@ -0,0 +1,34 @@
+import { userAchievementsRepo } from "@/db/repo/user-achievements.repo";
+import type { AchievementModel } from "@/db/schema/achievements.schema";
+import type { UserModel } from "@/db/schema/user.schema";
+import { Inject, Service } from "typedi";
+
+@Service()
+export class UserAchievementsService {
+    @Inject()
+    private readonly repo: userAchievementsRepo
+
+    async associateUserWithAchievements(userId: UserModel['id'], achievementIds: AchievementModel['id'][]): Promise<void> {
+        await this.repo.associateUserWithAchievements(userId, achievementIds);
+    }
+
+    async getAchievementsByUser(userId: UserModel['id']): Promise<AchievementModel[]> {
+        return this.repo.getAchievementsByUser(userId);
+    }
+
+    async removeAchievementsFromUser(userId: UserModel['id'], achievementIds: AchievementModel['id'][]): Promise<void> {
+        await this.repo.removeAchievementsFromUser(userId, achievementIds);
+    }
+
+    async isAssociationExists(userId: UserModel['id'], achievementId: AchievementModel['id']): Promise<boolean> {
+        return this.repo.isAssociationExists(userId, achievementId);
+    }
+
+    async getUsersByAchievement(achievementId: AchievementModel['id']): Promise<UserModel[]> {
+        return this.repo.getUsersByAchievement(achievementId);
+    }
+
+    async updateUserAchievements(userId: UserModel['id'], achievementIds: AchievementModel['id'][]): Promise<void> {
+        await this.repo.updateUserAchievements(userId, achievementIds);
+    }
+}
\ No newline at end of file
-- 
GitLab