diff --git a/eslint.config.js b/eslint.config.js index 25c48c7..3eea6c2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,10 +2,9 @@ import js from '@eslint/js'; import eslintConfigPrettier from 'eslint-config-prettier'; import { defineFlatConfig } from 'eslint-define-config'; import tsEslint from 'typescript-eslint'; -import arrayFuncPlugin from 'eslint-plugin-array-func'; import importPlugin from 'eslint-plugin-import'; import perfectionist from 'eslint-plugin-perfectionist'; -import unicornPlugin, { configs as unicornConfig } from 'eslint-plugin-unicorn'; +import unicornPlugin from 'eslint-plugin-unicorn'; import vitestPlugin from 'eslint-plugin-vitest'; import globals from 'globals'; @@ -42,7 +41,6 @@ export default defineFlatConfig([ }, }, plugins: { - 'array-func': arrayFuncPlugin, import: importPlugin, perfectionist, }, @@ -73,6 +71,7 @@ export default defineFlatConfig([ 'perfectionist/sort-objects': 'warn', 'perfectionist/sort-union-types': 'warn', + 'unicorn/no-null': 'off', 'unicorn/prevent-abbreviations': 'off', }, settings: { diff --git a/prisma/migrations/20240425225524_create_db/migration.sql b/prisma/migrations/20240425225524_create_db/migration.sql deleted file mode 100644 index 5eac95d..0000000 --- a/prisma/migrations/20240425225524_create_db/migration.sql +++ /dev/null @@ -1,59 +0,0 @@ --- CreateTable -CREATE TABLE "User" ( - "id" TEXT NOT NULL, - "email" TEXT NOT NULL, - "name" TEXT, - "password" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deletedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Account" ( - "id" TEXT NOT NULL, - "avatarUrl" TEXT NOT NULL, - "userId" TEXT, - "socialMediaId" INTEGER, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Account_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Token" ( - "id" SERIAL NOT NULL, - "authToken" TEXT NOT NULL, - "token" TEXT NOT NULL, - "issuedAt" TIMESTAMP(3) NOT NULL, - "expireIn" INTEGER NOT NULL, - "accountId" TEXT NOT NULL, - - CONSTRAINT "Token_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "SocialMedia" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - - CONSTRAINT "SocialMedia_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "Token_accountId_key" ON "Token"("accountId"); - --- AddForeignKey -ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Account" ADD CONSTRAINT "Account_socialMediaId_fkey" FOREIGN KEY ("socialMediaId") REFERENCES "SocialMedia"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Token" ADD CONSTRAINT "Token_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20240426013131_update/migration.sql b/prisma/migrations/20240426013131_update/migration.sql deleted file mode 100644 index 601c4b9..0000000 --- a/prisma/migrations/20240426013131_update/migration.sql +++ /dev/null @@ -1,35 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `createdAt` on the `User` table. All the data in the column will be lost. - - You are about to drop the column `deletedAt` on the `User` table. All the data in the column will be lost. - - You are about to drop the column `name` on the `User` table. All the data in the column will be lost. - - You are about to drop the column `updatedAt` on the `User` table. All the data in the column will be lost. - - You are about to drop the `Account` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `SocialMedia` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `Token` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropForeignKey -ALTER TABLE "Account" DROP CONSTRAINT "Account_socialMediaId_fkey"; - --- DropForeignKey -ALTER TABLE "Account" DROP CONSTRAINT "Account_userId_fkey"; - --- DropForeignKey -ALTER TABLE "Token" DROP CONSTRAINT "Token_accountId_fkey"; - --- AlterTable -ALTER TABLE "User" DROP COLUMN "createdAt", -DROP COLUMN "deletedAt", -DROP COLUMN "name", -DROP COLUMN "updatedAt"; - --- DropTable -DROP TABLE "Account"; - --- DropTable -DROP TABLE "SocialMedia"; - --- DropTable -DROP TABLE "Token"; diff --git a/prisma/migrations/20240501131741_init_db/migration.sql b/prisma/migrations/20240501131741_init_db/migration.sql new file mode 100644 index 0000000..f4ca32d --- /dev/null +++ b/prisma/migrations/20240501131741_init_db/migration.sql @@ -0,0 +1,64 @@ +-- CreateTable +CREATE TABLE "users" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "name" TEXT, + "username" TEXT NOT NULL, + "password" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted_at" TIMESTAMP(3), + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "accounts" ( + "id" TEXT NOT NULL, + "avatarUrl" TEXT NOT NULL, + "user_id" TEXT, + "social_media_id" INTEGER, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "accounts_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "tokens" ( + "id" SERIAL NOT NULL, + "auth_token" TEXT, + "token" TEXT, + "issued_at" TIMESTAMP(3), + "expire_in" INTEGER, + "account_id" TEXT NOT NULL, + + CONSTRAINT "tokens_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "social_media" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + + CONSTRAINT "social_media_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_username_key" ON "users"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "tokens_account_id_key" ON "tokens"("account_id"); + +-- AddForeignKey +ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "accounts" ADD CONSTRAINT "accounts_social_media_id_fkey" FOREIGN KEY ("social_media_id") REFERENCES "social_media"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tokens" ADD CONSTRAINT "tokens_account_id_fkey" FOREIGN KEY ("account_id") REFERENCES "accounts"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3d58862..82eea46 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -14,7 +14,53 @@ datasource db { } model User { - id String @id @default(uuid()) - email String @unique - password String + id String @id @default(uuid()) + email String @unique + name String? + username String @unique + password String + account Account[] + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @map("updated_at") + deletedAt DateTime? @map("deleted_at") + + @@map("users") +} + +model Account { + id String @id @default(uuid()) + avatarUrl String + userId String? @map("user_id") + socialMediaId Int? @map("social_media_id") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + user User? @relation(fields: [userId], references: [id]) + socialMedia SocialMedia? @relation(fields: [socialMediaId], references: [id]) + token Token? + + @@map("accounts") +} + +model Token { + id Int @id @default(autoincrement()) + authToken String? @map("auth_token") + token String? + issuedAt DateTime? @map("issued_at") + expireIn Int? @map("expire_in") + accountId String @unique @map("account_id") + + account Account? @relation(fields: [accountId], references: [id]) + + @@map("tokens") +} + +model SocialMedia { + id Int @id @default(autoincrement()) + name String + description String + + account Account[] + + @@map("social_media") } diff --git a/src/features/user/controllers/user-controller.test.ts b/src/features/user/controllers/user-controller.test.ts index 7a1ba0a..6e00406 100644 --- a/src/features/user/controllers/user-controller.test.ts +++ b/src/features/user/controllers/user-controller.test.ts @@ -61,7 +61,9 @@ describe('[Controllers] UserController', () => { const body = { email: 'valid_email@domain.com', + name: 'valid_name', password: 'valid_password', + username: 'valid_username', }; req.body = body; @@ -70,7 +72,9 @@ describe('[Controllers] UserController', () => { expect(serviceSpy).toHaveBeenCalledWith({ email: body.email, + name: body.name, password: body.password, + username: body.username, }); }); @@ -88,7 +92,7 @@ describe('[Controllers] UserController', () => { await userController.create(req, res, next); - expect(res.status).toHaveBeenCalledWith(204); + expect(res.status).toHaveBeenCalledWith(201); }); it('should call next when an error', async () => { diff --git a/src/features/user/controllers/user-controller.ts b/src/features/user/controllers/user-controller.ts index 56bbd96..1c9ecbe 100644 --- a/src/features/user/controllers/user-controller.ts +++ b/src/features/user/controllers/user-controller.ts @@ -15,10 +15,12 @@ export class UserController implements Controller { const response = await this.serviceCreate.execute({ email: req.body.email, + name: req.body.name, password: req.body.password, + username: req.body.username, }); - return res.status(HttpStatusCode.noContent).json(response); + return res.status(HttpStatusCode.created).json(response); } catch (error) { next(error); } diff --git a/src/features/user/models/user-create-model.ts b/src/features/user/models/user-create-model.ts index 231580a..fb2b302 100644 --- a/src/features/user/models/user-create-model.ts +++ b/src/features/user/models/user-create-model.ts @@ -1,7 +1,9 @@ export type UserModel = { email: string; id: string; + name: string; password: string; + username: string; }; export type UserCreateModel = Omit; diff --git a/src/features/user/repositories/user-repository/user-repository.ts b/src/features/user/repositories/user-repository/user-repository.ts index 7e725cb..0cffab1 100644 --- a/src/features/user/repositories/user-repository/user-repository.ts +++ b/src/features/user/repositories/user-repository/user-repository.ts @@ -4,14 +4,14 @@ import { database } from '@/shared/infra/database/database.js'; type CreateUserParams = Prisma.Args['data']; export class UserRepository { - async create({ email, password }: CreateUserParams) { - const user = await database.user.create({ + async create({ email, name, password, username }: CreateUserParams) { + return database.user.create({ data: { email, + name, password, + username, }, }); - - return user; } } diff --git a/src/features/user/services/user-create-service.test.ts b/src/features/user/services/user-create-service.test.ts index cb92644..b23364b 100644 --- a/src/features/user/services/user-create-service.test.ts +++ b/src/features/user/services/user-create-service.test.ts @@ -3,11 +3,16 @@ import { UserCreateService } from './user-create-service.js'; const makeSut = () => { class UserRepositoryStub implements UserRepository { - create({ email, password }: any) { + create({ email, name, password, username }: any) { return Promise.resolve({ + createdAt: new Date(2024, 5, 1), + deletedAt: null, email, id: 'valid_id', + name, password, + updatedAt: new Date(2024, 5, 1), + username, }); } } @@ -27,12 +32,16 @@ describe('UserCreateService', () => { await userCreateService.execute({ email: 'valid_email@email.com', + name: 'valid_name', password: 'valid_password', + username: 'valid_username', }); expect(repositorySpy).toHaveBeenCalledWith({ email: 'valid_email@email.com', + name: 'valid_name', password: 'valid_password', + username: 'valid_username', }); }); @@ -45,7 +54,9 @@ describe('UserCreateService', () => { const response = userCreateService.execute({ email: 'valid_email@email.com', + name: 'valid_name', password: 'valid_password', + username: 'valid_username', }); await expect(response).rejects.toThrowError(); diff --git a/src/features/user/services/user-create-service.ts b/src/features/user/services/user-create-service.ts index c1bed94..bd9151f 100644 --- a/src/features/user/services/user-create-service.ts +++ b/src/features/user/services/user-create-service.ts @@ -5,10 +5,12 @@ import type { Service } from '@/shared/protocols/service.js'; export class UserCreateService implements Service { constructor(private readonly userRepository: UserRepository) {} - async execute({ email, password }: UserCreateModel) { + async execute({ email, name, password, username }: UserCreateModel) { await this.userRepository.create({ email, + name, password, + username, }); } } diff --git a/src/features/user/validators/user-create-schema.ts b/src/features/user/validators/user-create-schema.ts index 493a1ae..b699037 100644 --- a/src/features/user/validators/user-create-schema.ts +++ b/src/features/user/validators/user-create-schema.ts @@ -2,7 +2,9 @@ import Joi from 'joi'; const userCreateBodySchema = Joi.object({ email: Joi.string().email().required(), + name: Joi.string().required(), password: Joi.string().required(), + username: Joi.string().required(), }); export const userCreateSchema = Joi.object({ diff --git a/src/shared/protocols/http-client.ts b/src/shared/protocols/http-client.ts index 14383cb..2ea5218 100644 --- a/src/shared/protocols/http-client.ts +++ b/src/shared/protocols/http-client.ts @@ -14,6 +14,7 @@ export type HttpMethod = 'delete' | 'get' | 'post' | 'put'; export enum HttpStatusCode { badRequest = 400, + created = 201, forbidden = 403, noContent = 204, notFound = 404, diff --git a/src/shared/test-helpers/mocks/user.mock.ts b/src/shared/test-helpers/mocks/user.mock.ts index ea5f270..f61b639 100644 --- a/src/shared/test-helpers/mocks/user.mock.ts +++ b/src/shared/test-helpers/mocks/user.mock.ts @@ -4,7 +4,9 @@ export class UserMock { public static create() { return { email: faker.internet.email(), + name: faker.person.firstName(), password: faker.internet.password(), + username: faker.internet.userName(), }; } }