Skip to content

Commit

Permalink
Merge pull request #32 from anthonyleier/custom-errors
Browse files Browse the repository at this point in the history
Padronização dos Controllers
  • Loading branch information
anthonyleier authored Feb 21, 2025
2 parents a2f43be + e71caf9 commit 826f462
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 84 deletions.
24 changes: 24 additions & 0 deletions infra/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { InternalServerError, MethodNotAllowedError } from "infra/errors";

function onNoMatchHandler(request, response) {
const publicErrorObject = new MethodNotAllowedError();
response.status(publicErrorObject.statusCode).json(publicErrorObject);
}

function onErrorHandler(error, request, response) {
const publicErrorObject = new InternalServerError({
statusCode: error.statusCode,
cause: error,
});
console.error(publicErrorObject);
response.status(publicErrorObject.statusCode).json(publicErrorObject);
}

const controller = {
errorHandlers: {
onNoMatch: onNoMatchHandler,
onError: onErrorHandler,
},
};

export default controller;
8 changes: 6 additions & 2 deletions infra/database.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Client } from "pg";
import { ServiceError } from "./errors.js";

async function getNewClient() {
const client = new Client({
Expand All @@ -21,8 +22,11 @@ async function query(queryObject) {
const result = await client.query(queryObject);
return result;
} catch (error) {
console.error(error);
throw error;
const serviceErrorObject = new ServiceError({
message: "Erro na conexão com Banco ou na Query.",
cause: error,
});
throw serviceErrorObject;
} finally {
await client?.end();
}
Expand Down
41 changes: 39 additions & 2 deletions infra/errors.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,46 @@
export class InternalServerError extends Error {
constructor({ cause }) {
constructor({ cause, statusCode }) {
super("Um erro interno não esperado aconteceu.", { cause });
this.name = "InternalServerError";
this.action = "Entre em contato com o suporte.";
this.statusCode = 500;
this.statusCode = statusCode || 500;
}

toJSON() {
return {
name: this.name,
message: this.message,
action: this.action,
status_code: this.statusCode,
};
}
}

export class ServiceError extends Error {
constructor({ cause, message }) {
super(message || "Serviço indisponível no momento.", { cause });
this.name = "ServiceError";
this.action = "Verifique se o serviço está disponível.";
this.statusCode = 503;
}

toJSON() {
return {
name: this.name,
message: this.message,
action: this.action,
status_code: this.statusCode,
};
}
}

export class MethodNotAllowedError extends Error {
constructor() {
super("Método não permitido para este endpoint.");
this.name = "MethodNotAllowedError";
this.action =
"Verifique se o método HTTP enviado é válido para este endpoint";
this.statusCode = 405;
}

toJSON() {
Expand Down
29 changes: 29 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"dotenv": "16.4.5",
"dotenv-expand": "11.0.6",
"next": "14.2.5",
"next-connect": "1.0.0",
"node-pg-migrate": "7.6.1",
"pg": "8.12.0",
"react": "18.3.1",
Expand Down
82 changes: 37 additions & 45 deletions pages/api/v1/migrations/index.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,44 @@
import { resolve } from "node:path";
import migrationRunner from "node-pg-migrate";
import database from "infra/database.js";
import { createRouter } from "next-connect";
import controller from "infra/controller.js";

const router = createRouter();
router.get(getHandler);
router.post(postHandler);
export default router.handler(controller.errorHandlers);

const defaultMigrationsOptions = {
dryRun: true,
dir: resolve("infra", "migrations"),
direction: "up",
verbose: true,
migrationsTable: "pgmigrations",
};

async function getHandler(request, response) {
const dbClient = await database.getNewClient();
const pendingMigrations = await migrationRunner({
...defaultMigrationsOptions,
dbClient,
});
await dbClient.end();
return response.status(200).json(pendingMigrations);
}

export default async function migrations(request, response) {
let dbClient;
const allowedMethods = ["GET", "POST"];

if (!allowedMethods.includes(request.method)) {
return response.status(405).json({
error: `Method "${request.method}" not allowed`,
});
async function postHandler(request, response) {
const dbClient = await database.getNewClient();
const migratedMigrations = await migrationRunner({
...defaultMigrationsOptions,
dbClient,
dryRun: false,
});
await dbClient.end();

if (migratedMigrations.length > 0) {
return response.status(201).json(migratedMigrations);
}

try {
dbClient = await database.getNewClient();

const defaultMigrationsOptions = {
dbClient: dbClient,
dryRun: true,
dir: resolve("infra", "migrations"),
direction: "up",
verbose: true,
migrationsTable: "pgmigrations",
};

if (request.method === "GET") {
const pendingMigrations = await migrationRunner(defaultMigrationsOptions);
return response.status(200).json(pendingMigrations);
}

if (request.method === "POST") {
const migratedMigrations = await migrationRunner({
...defaultMigrationsOptions,
dryRun: false,
});

if (migratedMigrations.length > 0) {
return response.status(201).json(migratedMigrations);
}

return response.status(200).json(migratedMigrations);
}

return response.status(405).end();
} catch (error) {
console.error(error);
throw error;
} finally {
await dbClient.end();
}
return response.status(200).json(migratedMigrations);
}
62 changes: 27 additions & 35 deletions pages/api/v1/status/index.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,35 @@
import { createRouter } from "next-connect";
import database from "infra/database.js";
import { InternalServerError } from "infra/errors";
async function status(request, response) {
try {
const updatedAt = new Date().toISOString();
import controller from "infra/controller";

const postgresVersionResult = await database.query("SHOW server_version;");
const postgresVersion = postgresVersionResult.rows[0].server_version;
const router = createRouter();
router.get(getHandler);
export default router.handler(controller.errorHandlers);

const maxConnectionsResult = await database.query("SHOW max_connections;");
const maxConnections = parseInt(
maxConnectionsResult.rows[0].max_connections,
);
async function getHandler(request, response) {
const updatedAt = new Date().toISOString();

const databaseName = process.env.POSTGRES_DB;
const openedConnectionsResult = await database.query({
text: `SELECT COUNT(*)::INTEGER AS opened_connections FROM pg_stat_activity WHERE datname = $1;`,
values: [databaseName],
});
const openedConnections =
openedConnectionsResult.rows[0].opened_connections;
const postgresVersionResult = await database.query("SHOW server_version;");
const postgresVersion = postgresVersionResult.rows[0].server_version;

response.status(200).json({
updated_at: updatedAt,
dependencies: {
database: {
version: postgresVersion,
max_connections: maxConnections,
opened_connections: openedConnections,
},
},
});
} catch (error) {
const publicErrorObject = new InternalServerError({ cause: error });
const maxConnectionsResult = await database.query("SHOW max_connections;");
const maxConnections = parseInt(maxConnectionsResult.rows[0].max_connections);

console.log("\n Erro dentro do catch do controller:");
console.error(publicErrorObject);
const databaseName = process.env.POSTGRES_DB;
const openedConnectionsResult = await database.query({
text: `SELECT COUNT(*)::INTEGER AS opened_connections FROM pg_stat_activity WHERE datname = $1;`,
values: [databaseName],
});
const openedConnections = openedConnectionsResult.rows[0].opened_connections;

response.status(500).json(publicErrorObject);
}
response.status(200).json({
updated_at: updatedAt,
dependencies: {
database: {
version: postgresVersion,
max_connections: maxConnections,
opened_connections: openedConnections,
},
},
});
}

export default status;
25 changes: 25 additions & 0 deletions tests/integration/api/v1/status/post.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import orchestrator from "tests/orchestrator.js";

beforeAll(async () => {
await orchestrator.waitForAllServices();
});

describe("POST /api/v1/status", () => {
describe("Anonymous user", () => {
test("Retrieving current system status", async () => {
const response = await fetch("http://localhost:3000/api/v1/status", {
method: "POST",
});
expect(response.status).toBe(405);

const responseBody = await response.json();
expect(responseBody).toEqual({
name: "MethodNotAllowedError",
message: "Método não permitido para este endpoint.",
action:
"Verifique se o método HTTP enviado é válido para este endpoint",
status_code: 405,
});
});
});
});

0 comments on commit 826f462

Please sign in to comment.