Skip to content

Commit

Permalink
add users repo and errors middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
marcosparajua committed Apr 30, 2024
1 parent 604bbb2 commit 8ca4c61
Show file tree
Hide file tree
Showing 22 changed files with 619 additions and 2 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const config = {
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: [
'index.ts',
'repo.ts',
'entities',
'interface',
'tools',
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"type": "module",
"scripts": {
"start:prod": "cross-env NODE_ENV=prod node dist/index.js",
"start:dev": "cross-env DEBUG=W7E* NODE_ENV=dev PORT=3400 node --watch --env-file=.env dist/index.js",
"start:mon": "cross-env DEBUG=W7E* NODE_ENV=dev PORT=3400 nodemon dist/index.js",
"start:dev": "cross-env DEBUG=BOOKS* NODE_ENV=dev PORT=3400 node --watch --env-file=.env dist/index.js",
"start:mon": "cross-env DEBUG=BOOKS* NODE_ENV=dev PORT=3400 nodemon dist/index.js",
"build": "tsc",
"dev": "tsc -w",
"test": "jest",
Expand Down
53 changes: 53 additions & 0 deletions prisma/migrations/20240430083939_init/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
-- CreateEnum
CREATE TYPE "Role" AS ENUM ('ADMIN', 'USER', 'GUEST');

-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL,
"repeatPassword" TEXT NOT NULL,
"avatar" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"role" "Role" NOT NULL,

CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Book" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"author" TEXT NOT NULL,
"year" INTEGER NOT NULL,
"isbn" TEXT NOT NULL,
"coverUrl" TEXT NOT NULL,
"description" TEXT NOT NULL,

CONSTRAINT "Book_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Article" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"subtitle" TEXT,
"imageUrl" TEXT,
"authorId" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Article_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

-- CreateIndex
CREATE UNIQUE INDEX "Book_isbn_key" ON "Book"("isbn");

-- AddForeignKey
ALTER TABLE "Article" ADD CONSTRAINT "Article_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
14 changes: 14 additions & 0 deletions prisma/migrations/20240430090046_update/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
Warnings:
- The values [GUEST] on the enum `Role` will be removed. If these variants are still used in the database, this will fail.
*/
-- AlterEnum
BEGIN;
CREATE TYPE "Role_new" AS ENUM ('ADMIN', 'USER');
ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role_new" USING ("role"::text::"Role_new");
ALTER TYPE "Role" RENAME TO "Role_old";
ALTER TYPE "Role_new" RENAME TO "Role";
DROP TYPE "Role_old";
COMMIT;
14 changes: 14 additions & 0 deletions prisma/migrations/20240430094946_update/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
Warnings:
- The values [ADMIN,USER] on the enum `Role` will be removed. If these variants are still used in the database, this will fail.
*/
-- AlterEnum
BEGIN;
CREATE TYPE "Role_new" AS ENUM ('admin', 'user');
ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role_new" USING ("role"::text::"Role_new");
ALTER TYPE "Role" RENAME TO "Role_old";
ALTER TYPE "Role_new" RENAME TO "Role";
DROP TYPE "Role_old";
COMMIT;
2 changes: 2 additions & 0 deletions prisma/migrations/20240430113551_update/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ALTER COLUMN "avatar" DROP NOT NULL;
3 changes: 3 additions & 0 deletions prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
54 changes: 54 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
id String @id @default(cuid())
name String
email String @unique
password String
repeatPassword String
avatar String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
role Role
articles Article[]
}

enum Role {
admin
user
}

model Book {
id String @id @default(cuid())
title String
author String
year Int
isbn String @unique
coverUrl String
description String
}

model Article {
id String @id @default(cuid())
title String
subtitle String?
imageUrl String?
author User @relation(fields: [authorId], references: [id])
authorId String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
4 changes: 4 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ sonar.organization=isdi-coders-2023

# Encoding of the source code. Default is default system encoding
#sonar.sourceEncoding=UTF-8
sonar.sources=./src
sonar.test.inclusions=./src///.test., ./src///.spec.*
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.coverage.exclusions=**/src/**/*.spec.*, **/src/main.ts
8 changes: 8 additions & 0 deletions src/app.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createApp } from './app';

describe('Given the function createApp ', () => {
test('Then it should be call and return app', () => {
const app = createApp();
expect(app).toBeDefined();
});
});
6 changes: 6 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
import debug from 'debug';
import express from 'express';

export const createApp = () => {
debug('Creating app');
return express();
};
18 changes: 18 additions & 0 deletions src/entities/article.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Joi from 'joi';
import { type ArticleCreateDto, type ArticleUpdateDto } from './article.js';

export const articleCreateDtoSchema = Joi.object<ArticleCreateDto>({
title: Joi.string().required(),
subtitle: Joi.string().allow('', null),
imageUrl: Joi.string().allow('', null),
authorId: Joi.string().required(),
content: Joi.string().required(),
});

export const articleUpdateDtoSchema = Joi.object<ArticleUpdateDto>({
title: Joi.string(),
subtitle: Joi.string().allow('', null),
imageUrl: Joi.string().allow('', null),
authorId: Joi.string(),
content: Joi.string(),
});
15 changes: 15 additions & 0 deletions src/entities/article.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type User } from './user.js';

export type Article = {
id: string;
title: string;
subtitle: string | undefined;
imageUrl?: string | undefined;
author: User;
authorId: string;
content: string;
createdAt?: string;
updatedAt?: string;
};
export type ArticleCreateDto = Omit<Article, 'id' | 'createdAt' | 'updatedAt'>;
export type ArticleUpdateDto = Partial<ArticleCreateDto>;
20 changes: 20 additions & 0 deletions src/entities/book.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Joi from 'joi';
import { type BookCreateDto, type BookUpdateDto } from './book.js';

export const bookCreateDtoSchema = Joi.object<BookCreateDto>({
title: Joi.string().required(),
author: Joi.string().required(),
year: Joi.number().integer().min(0).required(),
isbn: Joi.string().required(),
coverUrl: Joi.string().uri().required(),
description: Joi.string().required(),
});

export const bookUpdateDtoSchema = Joi.object<BookUpdateDto>({
title: Joi.string(),
author: Joi.string(),
year: Joi.number().integer().min(0),
isbn: Joi.string(),
coverUrl: Joi.string().uri(),
description: Joi.string(),
});
11 changes: 11 additions & 0 deletions src/entities/book.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type Book = {
id: string;
title: string;
author: string;
year: number;
isbn: string;
coverUrl: string;
description: string;
};
export type BookCreateDto = Omit<Book, 'id'>;
export type BookUpdateDto = Partial<BookCreateDto>;
22 changes: 22 additions & 0 deletions src/entities/user.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Joi from 'joi';
import { type UserCreateDto, type UserUpdateDto } from './user';

export const userCreateDtoSchema = Joi.object<UserCreateDto>({
name: Joi.string().required(),
email: Joi.string().email().required(),
password: Joi.string().required(),
repeatPassword: Joi.string().required(),
avatar: Joi.string().allow('', null),

role: Joi.string().valid('admin', 'user').required(),
});

export const userUpdateDtoSchema = Joi.object<UserUpdateDto>({
name: Joi.string(),
email: Joi.string().email(),
password: Joi.string(),
repeatPassword: Joi.string(),
avatar: Joi.string().allow('', null),

role: Joi.string().valid('admin', 'user'),
});
30 changes: 30 additions & 0 deletions src/entities/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { type Article } from './article.js';

export type User = {
id: string;
name: string;
email: string;
password: string | undefined;
repeatPassword?: string | undefined;
avatar?: string | undefined;
createdAt: string;
updatedAt: string;
role: 'admin' | 'user';
articles: Partial<Article[]>;
};

export type UserCreateDto = Omit<
User,
'id' | 'createdAt' | 'updatedAt' | 'articles'
> & {
password: string;
repeatPassword: string;
avatar?: string | undefined;
};

export type UserUpdateDto = Partial<UserCreateDto>;

export type UserReadDto = Omit<
User,
'password' | 'repeatPassword' | 'createdAt' | 'updatedAt'
>;
45 changes: 45 additions & 0 deletions src/middleware/errors.middleware.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { type Request, type Response } from 'express';
import { ErrorsMiddleware, HttpError } from './errors.middleware';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';

const req = {} as unknown as Request;
const res = {
json: jest.fn(),
status: jest.fn(),
} as unknown as Response;
const next = jest.fn();

describe('Given a instance of the class ErrorsMiddleware', () => {
const middleware = new ErrorsMiddleware();
test('Then it should be instance of the class', () => {
expect(middleware).toBeInstanceOf(ErrorsMiddleware);
});
describe('When we use the method handle with a HttpError', () => {
test('Then it should call res.status 404', () => {
const error = new HttpError(404, 'Not Found', 'Article not found');
middleware.handle(error, req, res, next);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalled();
});
});
describe('When we use the method handle with a PrismaClientKnownRequestError', () => {
test('Then it should call res.status 404', () => {
const error = new PrismaClientKnownRequestError('error', {
code: 'P2025',
clientVersion: '3.0.0',
});
middleware.handle(error, req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(res.json).toHaveBeenCalled();
});
});

describe('When we use the method handle with a Error', () => {
test('Then it should call res.status with 500', () => {
const error = new Error('Something went wrong');
middleware.handle(error, req, res, next);
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalled();
});
});
});
Loading

0 comments on commit 8ca4c61

Please sign in to comment.