diff --git a/src/db/migrations/0001_eager_living_lightning.sql b/src/db/migrations/0001_eager_living_lightning.sql new file mode 100644 index 0000000000000000000000000000000000000000..b7a7988a4b94d9aaa845d75e81f19cefc894f51a --- /dev/null +++ b/src/db/migrations/0001_eager_living_lightning.sql @@ -0,0 +1,336 @@ +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 "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_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" ( + "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_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_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 "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_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/0001_snapshot.json b/src/db/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000000000000000000000000000000000000..5a466332f3ddc1f5fd16a3173dac42ca55a7d193 --- /dev/null +++ b/src/db/migrations/meta/0001_snapshot.json @@ -0,0 +1,1499 @@ +{ + "id": "8c1d7047-0236-4058-81d4-b0f19d54c275", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.collection_likes": { + "name": "collection_likes", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "collection": { + "name": "collection", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "collection_likes_user_id_user_id_fk": { + "name": "collection_likes_user_id_user_id_fk", + "tableFrom": "collection_likes", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_likes_collection_collection_id_fk": { + "name": "collection_likes_collection_collection_id_fk", + "tableFrom": "collection_likes", + "tableTo": "collection", + "columnsFrom": [ + "collection" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.collection_resources": { + "name": "collection_resources", + "schema": "", + "columns": { + "collection_id": { + "name": "collection_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "collection_resources_collection_id_collection_id_fk": { + "name": "collection_resources_collection_id_collection_id_fk", + "tableFrom": "collection_resources", + "tableTo": "collection", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_resources_resource_id_resource_id_fk": { + "name": "collection_resources_resource_id_resource_id_fk", + "tableFrom": "collection_resources", + "tableTo": "resource", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.collection_stats": { + "name": "collection_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "collection_id": { + "name": "collection_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "downloads": { + "name": "downloads", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "likes": { + "name": "likes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "shares": { + "name": "shares", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "score": { + "name": "score", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "follows": { + "name": "follows", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "collection_stats_collection_id_collection_id_fk": { + "name": "collection_stats_collection_id_collection_id_fk", + "tableFrom": "collection_stats", + "tableTo": "collection", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collection_stats_id_unique": { + "name": "collection_stats_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + } + }, + "public.collection": { + "name": "collection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_private": { + "name": "is_private", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "thumbnail": { + "name": "thumbnail", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collection_id_unique": { + "name": "collection_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + } + }, + "public.complaint": { + "name": "complaint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evaluatedUser": { + "name": "evaluatedUser", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "collection_id": { + "name": "collection_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "evaluated_at": { + "name": "evaluated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "q1": { + "name": "q1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "q2": { + "name": "q2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "q3": { + "name": "q3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "q4": { + "name": "q4", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "complaint_evaluatedUser_user_id_fk": { + "name": "complaint_evaluatedUser_user_id_fk", + "tableFrom": "complaint", + "tableTo": "user", + "columnsFrom": [ + "evaluatedUser" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.educational_stages": { + "name": "educational_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "educational_stages_id_unique": { + "name": "educational_stages_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "educational_stages_name_unique": { + "name": "educational_stages_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.follows": { + "name": "follows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "follower_id": { + "name": "follower_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "follows_user_id_user_id_fk": { + "name": "follows_user_id_user_id_fk", + "tableFrom": "follows", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "follows_follower_id_user_id_fk": { + "name": "follows_follower_id_user_id_fk", + "tableFrom": "follows", + "tableTo": "user", + "columnsFrom": [ + "follower_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "follows_id_unique": { + "name": "follows_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + } + }, + "public.language": { + "name": "language", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "language_id_unique": { + "name": "language_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "language_name_unique": { + "name": "language_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "language_code_unique": { + "name": "language_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + } + }, + "public.licenses": { + "name": "licenses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "licenses_id_unique": { + "name": "licenses_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "licenses_name_unique": { + "name": "licenses_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.object_types": { + "name": "object_types", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "object_types_id_unique": { + "name": "object_types_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + } + }, + "public.reset_ticket": { + "name": "reset_ticket", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expiration_date": { + "name": "expiration_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token_used": { + "name": "token_used", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "valid_token": { + "name": "valid_token", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reset_ticket_user_id_user_id_fk": { + "name": "reset_ticket_user_id_user_id_fk", + "tableFrom": "reset_ticket", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reset_ticket_id_unique": { + "name": "reset_ticket_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + } + }, + "public.resource_languages": { + "name": "resource_languages", + "schema": "", + "columns": { + "resource_id": { + "name": "resource_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "language_id": { + "name": "language_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "resource_languages_resource_id_resource_id_fk": { + "name": "resource_languages_resource_id_resource_id_fk", + "tableFrom": "resource_languages", + "tableTo": "resource", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "resource_languages_language_id_language_id_fk": { + "name": "resource_languages_language_id_language_id_fk", + "tableFrom": "resource_languages", + "tableTo": "language", + "columnsFrom": [ + "language_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource_likes": { + "name": "resource_likes", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "resource_likes_user_id_user_id_fk": { + "name": "resource_likes_user_id_user_id_fk", + "tableFrom": "resource_likes", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "resource_likes_resource_id_resource_id_fk": { + "name": "resource_likes_resource_id_resource_id_fk", + "tableFrom": "resource_likes", + "tableTo": "resource", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource": { + "name": "resource", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "author": { + "name": "author", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "bucket_key": { + "name": "bucket_key", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "thumbnail": { + "name": "thumbnail", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "submited_at": { + "name": "submited_at", + "type": "timestamp", + "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": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "resource_id_unique": { + "name": "resource_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "resource_bucket_key_unique": { + "name": "resource_bucket_key_unique", + "nullsNotDistinct": false, + "columns": [ + "bucket_key" + ] + } + } + }, + "public.role": { + "name": "role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "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, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "role_id_unique": { + "name": "role_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "role_name_unique": { + "name": "role_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.stats_resources": { + "name": "stats_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "downloads": { + "name": "downloads", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "shares": { + "name": "shares", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "score": { + "name": "score", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "stats_resources_resource_id_resource_id_fk": { + "name": "stats_resources_resource_id_resource_id_fk", + "tableFrom": "stats_resources", + "tableTo": "resource", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "stats_resources_id_unique": { + "name": "stats_resources_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + } + }, + "public.subjects": { + "name": "subjects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subjects_id_unique": { + "name": "subjects_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "subjects_name_unique": { + "name": "subjects_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.submission": { + "name": "submission", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "is_accepted": { + "name": "is_accepted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "justification": { + "name": "justification", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_id": { + "name": "resource_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "submitter_id": { + "name": "submitter_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "curator_id": { + "name": "curator_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "answered_at": { + "name": "answered_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "q1": { + "name": "q1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "q2": { + "name": "q2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "q3": { + "name": "q3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "q4": { + "name": "q4", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "submission_resource_id_resource_id_fk": { + "name": "submission_resource_id_resource_id_fk", + "tableFrom": "submission", + "tableTo": "resource", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "submission_submitter_id_user_id_fk": { + "name": "submission_submitter_id_user_id_fk", + "tableFrom": "submission", + "tableTo": "user", + "columnsFrom": [ + "submitter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "submission_curator_id_user_id_fk": { + "name": "submission_curator_id_user_id_fk", + "tableFrom": "submission", + "tableTo": "user", + "columnsFrom": [ + "curator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user_role": { + "name": "user_role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_role_user_id_user_id_fk": { + "name": "user_role_user_id_user_id_fk", + "tableFrom": "user_role", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_role_role_id_role_id_fk": { + "name": "user_role_role_id_role_id_fk", + "tableFrom": "user_role", + "tableTo": "role", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_role_id_unique": { + "name": "user_role_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + } + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "likes": { + "name": "likes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "likes_received": { + "name": "likes_received", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "follows": { + "name": "follows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "followers": { + "name": "followers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "collections": { + "name": "collections", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "submitted_resources": { + "name": "submitted_resources", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "approved_resources": { + "name": "approved_resources", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reviewed_resources": { + "name": "reviewed_resources", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "comments": { + "name": "comments", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_id_unique": { + "name": "user_stats_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + } + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'sem descrição'" + }, + "institution": { + "name": "institution", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'sem instituição'" + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "cpf": { + "name": "cpf", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "confirmation_sent_at": { + "name": "confirmation_sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reactivated_at": { + "name": "reactivated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_id_unique": { + "name": "user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "user_username_unique": { + "name": "user_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json index 9d48a47674657854ea2780e911c617c632b0a850..cb7827014b9868c5d06f0dcad233d4368f21f13f 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1729264247365, "tag": "0000_lyrical_silver_samurai", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1729512790298, + "tag": "0001_eager_living_lightning", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/relations/resource-likes.relation.ts b/src/db/relations/resource-likes.relation.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b99ed3f18253aa9c6a7cc65ad1a9f407f7009b2 --- /dev/null +++ b/src/db/relations/resource-likes.relation.ts @@ -0,0 +1,40 @@ +import { integer, pgTable } from "drizzle-orm/pg-core"; +import { resourceTable, userTable } from "../schema"; +import { relations } from "drizzle-orm"; +import { createInsertSchema, createSelectSchema } from "drizzle-zod"; +import type { z } from "zod"; + +const resourceLikesTable = pgTable("resource_likes", { + user_id: integer("user_id").notNull().references(() => userTable.id, { onDelete: 'cascade' }), + resource_id: integer("resource_id").notNull().references(() => resourceTable.id, { onDelete: 'cascade' }) +}) + +export const resourceLikesRelations = relations(resourceLikesTable, ({ one }) => ({ + user: one(userTable, { + fields: [resourceLikesTable.user_id], + references: [userTable.id] + }), + resource: one(resourceTable, { + fields: [resourceLikesTable.resource_id], + references: [resourceTable.id] + }) +})) + +const resourceLikesModelSchema = createSelectSchema(resourceLikesTable) +const resourceLikesDtoSchema = resourceLikesModelSchema +const resourceLikesInputSchema = createInsertSchema(resourceLikesTable) +const resourceLikesUpdateSchema = resourceLikesInputSchema.partial().required({ user_id: true, resource_id: true }) + +export type ResourceLikesModel = z.infer<typeof resourceLikesModelSchema> +export type ResourceLikesDto = z.infer<typeof resourceLikesDtoSchema> +export type ResourceLikesInput = z.infer<typeof resourceLikesInputSchema> +export type ResourceLikesUpdate = z.infer<typeof resourceLikesUpdateSchema> + +export const resourceLikesSchemas = { + resourceLikesModelSchema, + resourceLikesDtoSchema, + resourceLikesInputSchema, + resourceLikesUpdateSchema, +} + +export default resourceLikesTable diff --git a/src/db/repo/collection-stats.repo.ts b/src/db/repo/collection-stats.repo.ts index 5d9039263ab9cbb52160d2f33e83f66e997f7193..976219d0aee47fa01001879c7f0009a10fbd9c6e 100644 --- a/src/db/repo/collection-stats.repo.ts +++ b/src/db/repo/collection-stats.repo.ts @@ -3,9 +3,9 @@ import type { CollectionStatsInput, CollectionStatsModel, CollectionStatsUpdate, -} from '../schema/collection-stats.model' +} from '../schema/collection-stats.schema' import db from ".."; -import collectionStatsTable, { collectionStatsSchemas } from "../schema/collection-stats.model"; +import collectionStatsTable, { collectionStatsSchemas } from "../schema/collection-stats.schema"; import { eq, sql } from 'drizzle-orm' diff --git a/src/db/repo/resource-likes.repo.ts b/src/db/repo/resource-likes.repo.ts new file mode 100644 index 0000000000000000000000000000000000000000..1ef620bfb7a2c0b3afe9004ac18964fd647b90f7 --- /dev/null +++ b/src/db/repo/resource-likes.repo.ts @@ -0,0 +1,122 @@ +import { Service } from "typedi"; +import type { ResourceModel } from "../schema/resource.schema"; +import type { UserModel } from "../schema/user.schema"; +import db from ".."; +import { resourceLikesTable, resourceTable, userTable } from "../schema"; +import { and, eq, inArray } from "drizzle-orm"; + +@Service() +export class resourceLikesRepo { + async associateResourceWithLikes(resourceId: ResourceModel['id'], userIds: UserModel['id'][]): Promise<void> { + try { + const existingAssociations = await db + .select({ + user_id: resourceLikesTable.user_id, + }) + .from(resourceLikesTable) + .where(eq(resourceLikesTable.resource_id, resourceId)) + .execute(); + + const existingUserIds = existingAssociations.map(row => row.user_id); + + const newUserIds = userIds.filter(userId => !existingUserIds.includes(userId)); + + if (newUserIds.length > 0) { + const valuesToInsert = newUserIds.map(userId => ({ + resource_id: resourceId, + user_id: userId, + })); + + await db + .insert(resourceLikesTable) + .values(valuesToInsert) + .onConflictDoNothing() + .execute(); + + console.log(`Resource ${resourceId} associated with new likes: ${newUserIds.join(', ')}`); + } else { + console.log(`No new likes to associate with resource ${resourceId}`); + } + } catch (error) { + console.error("Error associating resource with likes", error); + } + } + + async getLikesbyResource(resourceId: ResourceModel['id']): Promise<Partial<UserModel>[]> { + try { + const likes = await db + .select({ + user_id: userTable.id, + user_name: userTable.name, // Include only safe fields + // Other fields that are not sensitive + }) + .from(userTable) + .innerJoin(resourceLikesTable, eq(resourceLikesTable.user_id, userTable.id)) + .where(eq(resourceLikesTable.resource_id, resourceId)) + .execute(); + + return likes; + } catch (error) { + console.error("Error fetching likes by resource", error); + throw error; + } + } + + async removeLikesFromResource(resourceId: ResourceModel['id'], userIds: UserModel['id'][]): Promise<void> { + try { + await db + .delete(resourceLikesTable) + .where( + and( + eq(resourceLikesTable.resource_id, resourceId), + inArray(resourceLikesTable.user_id, userIds) + ) + ) + .execute(); + + console.log(`Likes removed from resource ${resourceId}: ${userIds.join(', ')}`); + } catch (error) { + console.error("Error removing likes from resource", error); + throw error; + } + } + + async isAssociationExists(resourceId: ResourceModel['id'], userId: UserModel['id']): Promise<boolean> { + try { + const result = await db + .select() + .from(resourceLikesTable) + .where( + and( + eq(resourceLikesTable.resource_id, resourceId), + eq(resourceLikesTable.user_id, userId) + ) + ) + .execute(); + return result.length > 0; + } catch (error) { + console.error("Error checking if association exists", error); + throw error; + } + } + + async getResourcesByLike(userId: UserModel['id']): Promise<ResourceModel[]> { + try { + const resources = await db + .select({ + resource_id: resourceTable.id, + resource_name: resourceTable.name, // Include only safe fields + // Other fields that are not sensitive + }) + .from(resourceLikesTable) + .innerJoin(resourceTable, eq(resourceLikesTable.resource_id, resourceTable.id)) + .where(eq(resourceLikesTable.user_id, userId)) + .execute(); + + return resources; + } catch (error) { + console.error("Error fetching resources by like", error); + throw error; + } + } +} \ No newline at end of file diff --git a/src/db/schema/collection-stats.model.ts b/src/db/schema/collection-stats.schema.ts similarity index 100% rename from src/db/schema/collection-stats.model.ts rename to src/db/schema/collection-stats.schema.ts diff --git a/src/db/schema/index.ts b/src/db/schema/index.ts index 1724352bb57267a8640dd6793a4f902345a8b201..8671809e2413b89789a817c2df894da7e9a40034 100644 --- a/src/db/schema/index.ts +++ b/src/db/schema/index.ts @@ -1,6 +1,6 @@ import resourceTable from './resource.schema' import statsResourcesTable from './stats-resources.schema' -import collectionStatsTable from './collection-stats.model' +import collectionStatsTable from './collection-stats.schema' import collectionTable from './collections.schema' import subjectsTable from './subjects.schema' import submissionTable from './submission.schema' @@ -24,6 +24,7 @@ import userRoleRelationTable from '../relations/user-role.relation' import collectionResourcesTable from '../relations/collection-resources.relation' import institutionTable from './institutions.schema' import userInstitutionRelationTable from '../relations/user-institution.relation' +import resourceLikesTable from '../relations/resource-likes.relation' export { userTable, @@ -50,6 +51,7 @@ export { resourceSubjectsTable, institutionTable, userInstitutionRelationTable, + resourceLikesTable, } export const tables = [ @@ -74,4 +76,5 @@ export const tables = [ collectionResourcesTable, institutionTable, userInstitutionRelationTable, + resourceLikesTable, ] diff --git a/src/db/seed.ts b/src/db/seed.ts index a8e6f3fc96fbfc819f75414d7123d270961fd247..d43389f7bb8733d4671879efe7491059ace5374d 100644 --- a/src/db/seed.ts +++ b/src/db/seed.ts @@ -42,5 +42,6 @@ await seeds.institutionSeed(db) await seeds.collectionResourcesSeed(db) await seeds.resourceSubjectsSeed(db) await seeds.userInstitutionSeed(db) +await seeds.resourceLikesSeed(db) await connection.end() diff --git a/src/db/seeds/collection-stats.seed.ts b/src/db/seeds/collection-stats.seed.ts index 6eec4b217a178667d4daeeeca44f742e89a1ba1e..2306cbe9c43a36eb170fe34066e8e6b3d7cad8bd 100644 --- a/src/db/seeds/collection-stats.seed.ts +++ b/src/db/seeds/collection-stats.seed.ts @@ -1,6 +1,6 @@ import type db from '..' -import type { CollectionStatsInput } from '../schema/collection-stats.model' -import collectionStatsTable from '../schema/collection-stats.model' +import type { CollectionStatsInput } from '../schema/collection-stats.schema' +import collectionStatsTable from '../schema/collection-stats.schema' export default async function seed(db:db) { await db.insert(collectionStatsTable).values(collectionStatsData) diff --git a/src/db/seeds/index.ts b/src/db/seeds/index.ts index 1238f59136b408bc17b950f4fa3eeb36a3b517b8..cf31993fec7a713f85858a78a571522effb254c5 100644 --- a/src/db/seeds/index.ts +++ b/src/db/seeds/index.ts @@ -19,3 +19,4 @@ export { default as collectionResourcesSeed } from './collection-resources.seed' export { default as resourceSubjectsSeed } from './resource-subjects.seed' export { default as institutionSeed } from './institutions.seed' export { default as userInstitutionSeed } from './user-institution.seed' +export { default as resourceLikesSeed } from './resource-likes.seed' diff --git a/src/db/seeds/resource-likes.seed.ts b/src/db/seeds/resource-likes.seed.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5d673b41b50145254e0e8fc1af4ae96a5677214 --- /dev/null +++ b/src/db/seeds/resource-likes.seed.ts @@ -0,0 +1,26 @@ +import type db from ".."; +import type { ResourceLikesInput } from "../relations/resource-likes.relation"; +import { resourceLikesTable } from "../schema"; + +export default async function seed(db: db) { + await db.insert(resourceLikesTable).values(resourceLikesData); +} + +const resourceLikesData: ResourceLikesInput[] = [ + { + resource_id: 1, + user_id: 1, + }, + { + resource_id: 1, + user_id: 2, + }, + { + resource_id: 2, + user_id: 1, + }, + { + resource_id: 2, + user_id: 3, + }, +] \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c9c90de9909c4a48fce38da8b54a6b1b137fb2ce..632a998df96151462afc47a905f1679440947904 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,8 +29,12 @@ import { userRoleRouter } from './routes/user-role.route' import { collectionLikesRoutes, publicCollectionLikesRoutes } from './routes/collection-likes.route' import { institutionRouter } from './routes/institution.route' import { collectionResourcesRoutes, publicCollectionResourcesRoutes } from './routes/collection-resources.route' +<<<<<<< HEAD import { publicResourceSubjectsRouter, resourceSubjectsRouter } from './routes/resource-subjects.route' import { userInstitutionRouter } from './routes/user-institution.route' +======= +import { publicResourceLikesRoutes, resourceLikesRoutes } from './routes/resource-likes.route' +>>>>>>> 04db82e (para não perder) const app = new Hono() @@ -85,6 +89,7 @@ app .route('/collectionResources', publicCollectionResourcesRoutes) .route('/resourceSubjects', publicResourceSubjectsRouter) + .route('/resourceLikes', publicResourceLikesRoutes) //rotas que precisam de token app .basePath('/api') @@ -111,6 +116,7 @@ app .route('/resourceSubjects', resourceSubjectsRouter) .route('/user-institutions', userInstitutionRouter) + .route('/resourceLikes', resourceLikesRoutes) export default app export type AppType = typeof app diff --git a/src/routes/collection-stats.route.ts b/src/routes/collection-stats.route.ts index 69608b0a2419a25dd9732440583f92fd53c6d2cd..7e84b1a667a49dda60c796fcea67bb0779603b7c 100644 --- a/src/routes/collection-stats.route.ts +++ b/src/routes/collection-stats.route.ts @@ -2,7 +2,7 @@ import { CollectionStatsService } from "@/services/collection-stats.service"; import Container from "typedi"; import { honoWithJwt } from ".."; import { zValidator } from "@hono/zod-validator"; -import { collectionStatsSchemas } from "@/db/schema/collection-stats.model"; +import { collectionStatsSchemas } from "@/db/schema/collection-stats.schema"; import { createApexError, HttpStatus } from "@/services/error.service"; import { Hono } from "hono"; diff --git a/src/routes/collections.route.ts b/src/routes/collections.route.ts index 99b8336b57c3219fbe855e1c7e452e62889a26e1..f39ae5d5820c53628f8c63812f7bb787aa1d1c14 100644 --- a/src/routes/collections.route.ts +++ b/src/routes/collections.route.ts @@ -5,7 +5,7 @@ import { zValidator } from "@hono/zod-validator"; import { collectionSchemas } from "@/db/schema/collections.schema"; import { createApexError, HttpStatus } from "@/services/error.service"; import { Hono } from "hono"; -import { collectionStatsSchemas } from "@/db/schema/collection-stats.model"; +import { collectionStatsSchemas } from "@/db/schema/collection-stats.schema"; import { CollectionStatsService } from "@/services/collection-stats.service"; const service = Container.get(CollectionsService); diff --git a/src/routes/resource-likes.route.ts b/src/routes/resource-likes.route.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2b817001b1e8ace3b092252b29a884f968f8218 --- /dev/null +++ b/src/routes/resource-likes.route.ts @@ -0,0 +1,120 @@ +import { ResourceLikesService } from "@/services/resource-likes.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({ + resourceId: z.number(), + userIds: z.array(z.number()), +}) + +const service = Container.get(ResourceLikesService); + +export const resourceLikesRoutes = honoWithJwt() + .post('/associate', zValidator('json', associateSchema), + async (c) => { + try { + const { resourceId, userIds } = await c.req.valid('json'); + await service.associateResourceWithLikes(resourceId, userIds); + return c.json({ message: 'Likes associated successfully' }, HttpStatus.OK); + } catch (e) { + return c.json( + createApexError({ + status: 'error', + message: 'Failed to associate likes', + code: HttpStatus.BAD_REQUEST, + path: c.req.routePath, + suggestion: 'Ensure the collection and user ids are valid', + }), + HttpStatus.BAD_REQUEST + ); + } + } + ) + .post('/:resourceId/delete/:userId', + async (c) => { + try { + const resourceId = +c.req.param('resourceId'); + const userId = +c.req.param('userId'); + await service.removeLikesFromResource(resourceId, [userId]); + return c.json({ message: 'Likes removed successfully' }, HttpStatus.OK); + } catch (e) { + return c.json( + createApexError({ + status: 'error', + message: 'Failed to remove likes', + code: HttpStatus.BAD_REQUEST, + path: c.req.routePath, + suggestion: 'Ensure the collection and user ids are valid', + }), + HttpStatus.BAD_REQUEST + ) + } + } + ) + +export const publicResourceLikesRoutes = new Hono() + .get('/:resourceId', + async (c) => { + try { + const resourceId = +c.req.param('resourceId'); + const likes = await service.getLikesByResource(resourceId); + return c.json(likes, HttpStatus.OK); + } catch (e) { + return c.json( + createApexError({ + status: 'error', + message: 'Failed to fetch likes', + code: HttpStatus.BAD_REQUEST, + path: c.req.routePath, + suggestion: 'Ensure the resource id is valid', + }), + HttpStatus.BAD_REQUEST + ) + } + } + ) + .get('/:resourceId/likes/:userId/exists', + async (c) => { + try { + const resourceId = +c.req.param('resourceId'); + const userId = +c.req.param('userId'); + const exists = await service.isAssociationExists(resourceId, userId); + return c.json({ exists }, HttpStatus.OK); + } catch (e) { + return c.json( + createApexError({ + status: 'error', + message: 'Failed to check association', + code: HttpStatus.BAD_REQUEST, + path: c.req.routePath, + suggestion: 'Check the input and try again', + }), + HttpStatus.BAD_REQUEST + ); + } + } + ) + .get('/user/:userId/resources', + async (c) => { + try { + const userId = +c.req.param('userId'); + const resources = await service.getResourcesByUser(userId); + return c.json({ resources }, HttpStatus.OK); + } catch (e) { + return c.json( + createApexError({ + status: 'error', + message: 'Failed to fetch resources', + 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/collection-stats.service.ts b/src/services/collection-stats.service.ts index 8b24328864b7ddbecfd6b0ecbfcc4f6b60413d82..0729d7b4a0e6ec6af9d1d463f9f650b56e3d6465 100644 --- a/src/services/collection-stats.service.ts +++ b/src/services/collection-stats.service.ts @@ -1,5 +1,5 @@ import { CollectionStatsRepo } from "@/db/repo/collection-stats.repo"; -import type { CollectionStatsInput, CollectionStatsModel, CollectionStatsUpdate } from "@/db/schema/collection-stats.model"; +import type { CollectionStatsInput, CollectionStatsModel, CollectionStatsUpdate } from "@/db/schema/collection-stats.schema"; import { Inject, Service } from "typedi"; @Service() diff --git a/src/services/resource-likes.service.ts b/src/services/resource-likes.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..4bfbeae1c8f93fde220e5d01af55c2071a6a6596 --- /dev/null +++ b/src/services/resource-likes.service.ts @@ -0,0 +1,30 @@ +import { resourceLikesRepo } from "@/db/repo/resource-likes.repo"; +import type { ResourceModel } from "@/db/schema/resource.schema"; +import type { UserModel } from "@/db/schema/user.schema"; +import { Inject, Service } from "typedi"; + +@Service() +export class ResourceLikesService { + @Inject() + private readonly repo: resourceLikesRepo; + + async associateResourceWithLikes(resourceId: ResourceModel['id'], userIds: UserModel['id'][]): Promise<void> { + await this.repo.associateResourceWithLikes(resourceId, userIds); + } + + async getLikesByResource(resourceId: ResourceModel['id']): Promise<UserModel[]> { + return this.repo.getLikesbyResource(resourceId); + } + + async removeLikesFromResource(resourceId: ResourceModel['id'], userIds: UserModel['id'][]): Promise<void> { + await this.repo.removeLikesFromResource(resourceId, userIds); + } + + async isAssociationExists(resourceId: ResourceModel['id'], userId: UserModel['id']): Promise<boolean> { + return this.repo.isAssociationExists(resourceId, userId); + } + + async getResourcesByUser(userId: UserModel['id']): Promise<ResourceModel[]> { + return this.repo.getResourcesByLike(userId); + } +} \ No newline at end of file