Skip to content

Commit

Permalink
Merge pull request #7 from sombriks/develop
Browse files Browse the repository at this point in the history
basic spending planning support and other improvements
  • Loading branch information
sombriks authored Jan 28, 2024
2 parents 917fe31 + f2c9b5e commit 72d25c0
Show file tree
Hide file tree
Showing 19 changed files with 603 additions and 107 deletions.
23 changes: 0 additions & 23 deletions service-node-koa/app/config/main.spec.mjs

This file was deleted.

19 changes: 8 additions & 11 deletions service-node-koa/app/config/security/middleware.mjs
Original file line number Diff line number Diff line change
@@ -1,36 +1,33 @@
import {knex} from "../db/index.mjs";
import {verify} from "./encryption.mjs";

export const extractDetails = ctx => {
const extractDetails = ctx => {
const authHeader = ctx.request.header["authorization"];
if (!authHeader) ctx.throw(401, {message: "Missing auth header"});
const token = authHeader.replace("Bearer ", "");
return verify(token);
}

export const ifAdmin = async (ctx, next) => {
return await next();
};

export const ifOwner = async (ctx, next) => {
return await next();
const {admin} = extractDetails(ctx)
if (admin) return await next();
else return ctx.throw(403, "User is not an admin");
};

export const ifAuthenticated = async (ctx, next) => {
const details = extractDetails(ctx)
// console.log(details)
// console.log(new Date(details.exp * 1000))
const details = extractDetails(ctx) // id, nome, emil, admin, criacao, alteracao, iat, exp
if (!details.iat || !details.exp)
ctx.throw(401, {message: "Something strange with this token"})
if (new Date().getTime() > new Date(details.exp * 1000))
ctx.throw(401, {message: "Token expired"})
// ctx.throw(401, {message:"test"})
return await next()
};

export const contaOwnedBy = async (ctx, next) => {
const {id} = extractDetails(ctx)
const {usuario_id, conta_id} = ctx.request.params;
if (id != usuario_id) return ctx.throw(403, `Usuário logado não é o usuário informado`);
const [count] = await knex("conta").where({usuario_id, id: conta_id}).count();
if (count[Object.keys(count)[0]]) await next();
else throw new Error(`Conta ${conta_id} não pertence ao usuário ${usuario_id}`);
else ctx.throw(403, `Conta ${conta_id} não pertence ao usuário ${usuario_id}`);
};
80 changes: 80 additions & 0 deletions service-node-koa/app/config/security/middleware.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import chai from "chai"

import * as middleware from "./middleware.mjs"
import sinon from "sinon";
import {sign} from "./encryption.mjs";
import {getAdmin, resetConta} from "../../services/index.mjs";

chai.should();

describe("Middleware tests", () => {

it("should check if it's admin", async () => {
const adm = await getAdmin()
const {token} = sign(adm)
const authorization = `Bearer ${token}`
const ctx = {request: {header: {authorization}}, throw: sinon.fake()}
const next = sinon.mock()
next.once()

await middleware.ifAdmin(ctx, next)

next.verify()
})

it("should check if it's authenticated", async () => {
const adm = await getAdmin()
const {token} = sign(adm)
const authorization = `Bearer ${token}`
const ctx = {request: {header: {authorization}}, throw: sinon.fake()}
const next = sinon.mock()
next.once()

await middleware.ifAuthenticated(ctx, next)

next.verify()

})

it("should check if it owns the resource", async () => {
// given
const adm = await getAdmin()
const contasIds = await resetConta({usuario_id: adm.id})
const {token} = sign(adm)
const authorization = `Bearer ${token}`
const params = {usuario_id: adm.id, conta_id: contasIds[0].id}
const ctx = {request: {header: {authorization}, params}, throw: sinon.fake()}
const next = sinon.mock()
next.once()

// when
await middleware.contaOwnedBy(ctx, next)

// then
next.verify()
})

it("Should FAIL due missing auth header", async () => {
// given
const authorization = `Bearer`
const ctx = {request: {header: {authorization}}, throw: sinon.mock()}
const next = sinon.mock()
next.never()
ctx.throw.never()

// when
const spyable = { ifAuthenticated: middleware.ifAuthenticated}
const spy = sinon.spy(spyable, "ifAuthenticated")
try {
await spyable.ifAuthenticated(ctx, next)
} catch(e) {
chai.expect(spy.exceptions).length(1)
}

// then
chai.expect(spy.called)
chai.expect(spy.threw())
ctx.throw.verify()
next.verify()
})
})
27 changes: 22 additions & 5 deletions service-node-koa/app/controllers/planejamento.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
import { listPlanejamento } from "../services/index.mjs";
import { delPlanejamento, insertPlanejamento, listPlanejamento, updatePlanejamento } from '../services/index.mjs'

export const listPlanejamentoRequest = async ctx => {
const { user_id } = ctx.request.params;
const { q, limit = 10, offset = 0 } = ctx.request.query;
return await listPlanejamento({ user_id, q, limit, offset });
};
const { usuario_id } = ctx.request.params
const { q, limit = 10, offset = 0 } = ctx.request.query
ctx.body = await listPlanejamento({ usuario_id, q, limit, offset })
}

export const insertPlanejamentoRequest = async ctx => {
const { usuario_id } = ctx.request.params
const planejamento = ctx.request.body
ctx.body = await insertPlanejamento({ usuario_id, planejamento })
}

export const updatePlanejamentoRequest = async ctx => {
const { id, usuario_id } = ctx.request.params
const planejamento = ctx.request.body
ctx.body = await updatePlanejamento({ id, usuario_id, planejamento })
}

export const delPlanejamentoRequest = async ctx => {
const { usuario_id, id } = ctx.request.params
ctx.body = await delPlanejamento({ usuario_id, id })
}
16 changes: 12 additions & 4 deletions service-node-koa/app/controllers/usuario.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import chai, { expect } from "chai";
import chai, {expect} from "chai";
import chaiHttp from "chai-http";

import { app } from "../main.mjs";
import { verify } from "../config/security/index.mjs";
import {app} from "../main.mjs";
import {verify} from "../config/security/index.mjs";

chai.should();
chai.use(chaiHttp);

describe("User API test", async () => {

it("Should login", async () => {
const testUser = { email: "[email protected]", senha: "e1e2e3e4" };
const testUser = {email: "[email protected]", senha: "e1e2e3e4"};

const res = await chai.request(app.callback()).post("/login").send(testUser);

Expand All @@ -21,4 +21,12 @@ describe("User API test", async () => {
data.email.should.be.eq(testUser.email);
expect(data.senha).to.be.undefined;
});

it("Should NOT login wrong auth info", async () => {
const testUser = {email: "[email protected]", senha: "crap"};

const res = await chai.request(app.callback()).post("/login").send(testUser);

res.should.have.status(404);
});
});
56 changes: 30 additions & 26 deletions service-node-koa/app/main.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,43 @@ import Router from "@koa/router";
import bodyParser from "koa-bodyparser";

import {
listCategoriasRequest,
findCategoriaRequest,
insertCategoriaRequest,
updateCategoriaRequest,
delCategoriaRequest,
listContasRequest,
findContaRequest,
insertContaRequest,
updateContaRequest,
delContaRequest,
removeMovimentacaoRequest,
updateMovimentacaoRequest,
insertMovimentacaoRequest,
delPlanejamentoRequest,
delUsuarioRequest,
downloadMovimentacoesRequest,
findCategoriaRequest,
findContaRequest,
findMovimentacaoRequest,
insertCategoriaRequest,
insertContaRequest,
insertMovimentacaoRequest, insertPlanejamentoRequest,
listCategoriasRequest,
listContasRequest,
listMovimentacaoRequest,
listPlanejamentoRequest,
listRecorrenciaRequest,
removeMovimentacaoRequest,
updateCategoriaRequest,
updateContaRequest,
updateMovimentacaoRequest, updatePlanejamentoRequest,
uploadMovimentacoesRequest,
userLoginRequest,
userSignupRequest,
delUsuarioRequest,
uploadMovimentacoesRequest, downloadMovimentacoesRequest
} from "./controllers/index.mjs";
import {
listModelocategoria,
listTipoConta,
listTipoMovimentacao,
listTipoRecorrencia
} from "./services/index.mjs";
import { contaOwnedBy, ifAuthenticated } from "./config/security/index.mjs";
userSignupRequest
} from './controllers/index.mjs'
import {listModelocategoria, listTipoConta, listTipoMovimentacao, listTipoRecorrencia} from "./services/index.mjs";
import {contaOwnedBy, ifAuthenticated} from "./config/security/index.mjs";

import ApiBuilder from "koa-api-builder";
import { errHandler } from './config/default-error-handler.mjs'
import { cabin } from "./config/base-logging.mjs";
import {errHandler} from './config/default-error-handler.mjs'
import {cabin} from "./config/base-logging.mjs";

export const app = new Koa();
const router = new Router();

app.use(cabin.middleware).use(errHandler).use(cors()).use(bodyParser());

new ApiBuilder({ router }).path(b => {
new ApiBuilder({router}).path(b => {
b.get("/status", async ctx => ctx.body = "ONLINE");
b.get("/tipo-conta", async ctx => ctx.body = await listTipoConta());
b.get("/modelocategoria", async ctx => ctx.body = await listModelocategoria());
Expand Down Expand Up @@ -90,7 +87,14 @@ new ApiBuilder({ router }).path(b => {
});
});

b.path("/planejamento", b => b.get(listPlanejamentoRequest));
b.path("/planejamento", b => {
b.get(listPlanejamentoRequest)
b.post(insertPlanejamentoRequest)
b.path("/:id", b => {
b.del(delPlanejamentoRequest)
b.put(updatePlanejamentoRequest)
});
});

b.path("/recorrencia", b => b.get(listRecorrenciaRequest));
});
Expand Down
21 changes: 21 additions & 0 deletions service-node-koa/app/main.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import chai from "chai";
import chaiHttp from "chai-http";

import {app} from "./main.mjs";

chai.should();
chai.use(chaiHttp);

describe("Base API test", () => {

it("Should return status ONLINE", async () => {
const res = await chai.request(app.callback()).get("/status")
res.should.have.status(200);
res.text.should.be.eql("ONLINE");
});

it("Should answer with 404", async () => {
const res = await chai.request(app.callback()).get("/crap")
res.should.have.status(404);
})
});
41 changes: 31 additions & 10 deletions service-node-koa/app/services/planejamento.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import { knex } from "../config/db/index.mjs";

export const listPlanejamento = ({ user_id = -1, q = "", limit = 10, offset = 0 }) => {
return knex("planejamento")
.whereIn("categoria_id", knex("categoria")
.select("id")
.where({ user_id }))
.andWhereLike("descricao", `%${q}%`)
import { knex } from '../config/db/index.mjs'

export const listPlanejamento = ({ usuario_id = -1, q = '', limit = 10, offset = 0 }) => {
return knex('planejamento')
.whereIn('categoria_id', knex('categoria')
.select('id')
.where({ usuario_id }))
.andWhereLike('descricao', `%${q}%`)
.orderBy('criacao', 'desc')
.offset(offset)
.limit(limit);
};
.limit(limit)
}

export const insertPlanejamento = async ({ usuario_id, planejamento }) => {
const count = await knex('categoria').where({ usuario_id, id: planejamento.categoria_id }).count()
if (!count[Object.keys(count)[0]]) throw Error('Categoria não pertence ao usuário')
return knex('planejamento').insert(planejamento, ['id'])
}

export const updatePlanejamento = async ({ id, usuario_id, planejamento }) => {
const count = await knex('categoria').where({ usuario_id, id: planejamento.categoria_id }).count()
if (!count[Object.keys(count)[0]]) throw Error('Categoria não pertence ao usuário')
planejamento.id = id // sanity
return knex('planejamento').update(planejamento).where({ id })
}

export const delPlanejamento = ({ usuario_id = -1, id = -1 }) =>
knex('planejamento').del()
.whereIn('categoria_id', knex('categoria')
.select('id')
.where({ usuario_id }))
.andWhere({ id })
Loading

0 comments on commit 72d25c0

Please sign in to comment.