Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions infra/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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;
9 changes: 6 additions & 3 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 query(queryObject) {
let client;
Expand All @@ -7,9 +8,11 @@ async function query(queryObject) {
const result = await client.query(queryObject);
return result;
} catch (error) {
console.log("\n Erro dentro do catch do database.js:");
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
43 changes: 41 additions & 2 deletions infra/errors.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,50 @@
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
26 changes: 26 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.4",
"dotenv-expand": "11.0.6",
"next": "13.1.6",
"next-connect": "1.0.0",
"node-pg-migrate": "6.2.2",
"pg": "8.11.3",
"react": "18.2.0",
Expand Down
72 changes: 40 additions & 32 deletions pages/api/v1/migrations/index.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,57 @@
import { createRouter } from "next-connect";
import migrationRunner from "node-pg-migrate";
import { resolve } from "node:path";
import database from "infra/database.js";
import controller from "infra/controller.js";

export default async function migrations(request, response) {
const allowedMethods = ["GET", "POST"];
if (!allowedMethods.includes(request.method)) {
return response.status(405).json({
error: `Method "${request.method}" not allowed`,
});
}
const router = createRouter();

router.get(getHandler);
router.post(postHandler);

export default router.handler(controller.errorHandlers);

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

async function getHandler(request, response) {
let dbClient;

try {
dbClient = await database.getNewClient();

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

if (request.method === "GET") {
const pendingMigrations = await migrationRunner(defaultMigrationOptions);
return response.status(200).json(pendingMigrations);
}
const pendingMigrations = await migrationRunner({
...defaultMigrationOptions,
dbClient,
});
return response.status(200).json(pendingMigrations);
} finally {
await dbClient.end();
}
}

async function postHandler(request, response) {
let dbClient;

if (request.method === "POST") {
const migratedMigrations = await migrationRunner({
...defaultMigrationOptions,
dryRun: false,
});
try {
dbClient = await database.getNewClient();

if (migratedMigrations.length > 0) {
return response.status(201).json(migratedMigrations);
}
const migratedMigrations = await migrationRunner({
...defaultMigrationOptions,
dbClient,
dryRun: false,
});

return response.status(200).json(migratedMigrations);
if (migratedMigrations.length > 0) {
return response.status(201).json(migratedMigrations);
}
} catch (error) {
console.error(error);
throw error;

return response.status(200).json(migratedMigrations);
} finally {
await dbClient.end();
}
Expand Down
80 changes: 37 additions & 43 deletions pages/api/v1/status/index.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,41 @@
import { createRouter } from "next-connect";
import database from "infra/database";
import { InternalServerError } from "infra/errors";

async function status(request, response) {
try {
const updatedAt = new Date().toISOString();

const databaseVersionResult = await database.query("SHOW server_version;");
const databaseVersionValue = databaseVersionResult.rows[0].server_version;

const databaseMaxConnectionsResult = await database.query(
"SHOW max_connections;"
);
const databaseMaxConnectionsValue =
databaseMaxConnectionsResult.rows[0].max_connections;

const databaseName = process.env.POSTGRES_DB;
const databaseOpenedConnectionsResult = await database.query({
text: "SELECT count(*)::int FROM pg_stat_activity WHERE datname = $1;",
values: [databaseName],
});
const databaseOpenedConnectionsValue =
databaseOpenedConnectionsResult.rows[0].count;

response.status(200).json({
updated_at: updatedAt,
dependencies: {
database: {
version: databaseVersionValue,
max_connections: parseInt(databaseMaxConnectionsValue),
opened_connections: databaseOpenedConnectionsValue,
},
},
});
} catch (error) {
const publicErrorObject = new InternalServerError({
cause: error,
});
import controller from "infra/controller.js";

console.log("\n Erro dentro do catch do controller:");
console.error(publicErrorObject);
const router = createRouter();

response.status(500).json(publicErrorObject);
}
}
router.get(getHandler);

export default router.handler(controller.errorHandlers);

async function getHandler(request, response) {
const updatedAt = new Date().toISOString();

const databaseVersionResult = await database.query("SHOW server_version;");
const databaseVersionValue = databaseVersionResult.rows[0].server_version;

export default status;
const databaseMaxConnectionsResult = await database.query(
"SHOW max_connections;"
);
const databaseMaxConnectionsValue =
databaseMaxConnectionsResult.rows[0].max_connections;

const databaseName = process.env.POSTGRES_DB;
const databaseOpenedConnectionsResult = await database.query({
text: "SELECT count(*)::int FROM pg_stat_activity WHERE datname = $1;",
values: [databaseName],
});
const databaseOpenedConnectionsValue =
databaseOpenedConnectionsResult.rows[0].count;

response.status(200).json({
updated_at: updatedAt,
dependencies: {
database: {
version: databaseVersionValue,
max_connections: parseInt(databaseMaxConnectionsValue),
opened_connections: databaseOpenedConnectionsValue,
},
},
});
}
26 changes: 26 additions & 0 deletions tests/integration/api/v1/status/post.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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,
});
});
});
});