Added transactions and Accounts

feature/TransactionUI
William Lewis 5 months ago
parent bd35017f4a
commit 744bfa8114

@ -0,0 +1,33 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/active-allocator/bin/Debug/net7.0/linux-x64/active-allocator.dll",
"args": [],
"cwd": "${workspaceFolder}/active-allocator",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

76
.vscode/tasks.json vendored

@ -0,0 +1,76 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "dotnet build",
"command": "dotnet",
"type": "shell",
"args": [
"build",
// Ask dotnet build to generate full paths for file names.
"/property:GenerateFullPaths=true",
// Do not generate summary otherwise it leads to duplicate errors in Problems panel
"/consoleloggerparameters:NoSummary"
],
"group": "build",
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile",
"options": {
"cwd": "./active-allocator"
}
},
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/active-allocator/active-allocator.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/active-allocator/active-allocator.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"${workspaceFolder}/active-allocator/active-allocator.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "run",
"command": "dotnet",
"type": "process",
"args": [
"run",
"${workspaceFolder}/active-allocator/active-allocator.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}

@ -0,0 +1,22 @@
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Attach (console)",
"preLaunchTask": "dotnet build",
"type": "coreclr",
"processId": "${command:pickProcess}",
"request": "attach",
"console": "integratedTerminal",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart",
"pipeTransport": {
"pipeCwd": "${workspaceFolder}/active-allocator",
"pipeProgram": "bash",
"pipeArgs": ["-c"],
"debuggerPath": "/home/william/.vscode-oss/extensions/muhammad-sammy.csharp-1.25.0/.debugger/netcoredbg/netcoredbg",
"quoteArgs": true
}
}
]
}

@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

@ -1,7 +1,11 @@
import { Counter } from "./components/Counter";
import { FetchData } from "./components/FetchData";
import { Home } from "./components/Home";
import Login from "./components/auth/Login";
import CreateAccount from "./components/accounts/CreateAccount";
import DeleteAccount from "./components/accounts/DeleteAccount";
import UpdateAccount from "./components/accounts/UpdateAccount";
import Accounts from "./components/accounts/Accounts";
import { Login } from "./components/auth/Login";
import { Logout } from "./components/auth/Logout";
import Register from "./components/auth/Register";
import CreateUser from "./components/users/CreateUser";
@ -54,6 +58,22 @@ const AppRoutes = [
path: '/admin/user/delete',
element: <DeleteUser />
},
{
path: '/admin/accounts',
element: <Accounts />
},
{
path: '/admin/account/create',
element: <CreateAccount />
},
{
path: '/admin/account/edit',
element: <UpdateAccount />
},
{
path: '/admin/account/delete',
element: <DeleteAccount />
},
];
export default AppRoutes;

@ -48,6 +48,9 @@ export class NavMenu extends Component {
<NavItem>
<NavLink tag={Link} className="text-dark" to="/admin/users">Users</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/admin/accounts">Accounts</NavLink>
</NavItem>
{/*<NavItem>
<NavLink tag={Link} className="text-dark" to="/login">{sessionStorage.getItem('firstName')}</NavLink>
</NavItem>*/}

@ -0,0 +1,94 @@
import { getData } from "../services/AccessAPI";
import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
import { useState } from 'react';
export default function Accounts() {
useEffect(() => {
getAllAccountsData();
}, [])
let navigate = useNavigate();
const [state, setState] = useState({
accounts: [],
loading: false
});
function onAccountCreate() {
navigate('/admin/account/create');
}
function onAccountEdit(id){
let path = "/admin/account/edit"
let query = "?id=" + id
navigate(path + query);
}
function onAccountDelete(id){
let path = "/admin/account/delete"
let query = "?id=" + id
navigate(path + query);
}
function getAllAccountsData() {
getData('Accounts/').then(
(result) => {
if (result) {
console.log(result);
setState({
accounts: result,
loading: false
});
}
}
);
}
function renderAllAccountsTable(accounts) {
return (
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Owner</th>
<th>Balance</th>
<th>External Account Number</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{
accounts.map(account => (
<tr key={account.id}>
<td>{account.name}</td>
<td>{account.owner}</td>
<td>{account.balance}</td>
<td>{account.externalAccountNumber}</td>
<td><button onClick={() => onAccountEdit(account.id)} className="btn btn-success">Edit</button> ||
<button onClick={() => onAccountDelete(account.id)} className="btn btn-danger">Delete</button></td>
</tr>
))
}
</tbody>
</table>
);
}
let content = state.loading ? (
<p>
<em>Loading...</em>
</p>
) : (
renderAllAccountsTable(state.accounts)
)
return (
<div>
<h3>List of Accounts</h3>
<button onClick={() => onAccountCreate()} className="btn btn-primary">Create new account</button>
{content}
</div>
);
}

@ -0,0 +1,106 @@
import SessionManager from "../auth/SessionManager";
import { postData } from "../services/AccessAPI";
import { useNavigate } from "react-router-dom";
import { useState } from 'react';
export default function CreateAccount() {
let navigate = useNavigate();
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [accountname, setAccountname] = useState("");
const [password, setPassword] = useState("");
const [confirmationPassword, setConfirmationPassword] = useState("");
const [role, setRole] = useState("");
const [loading, setLoading] = useState(true);
function onSubmit(e) {
e.preventDefault();
if (password !== confirmationPassword) {
alert("Password and confirm password are not same");
return;
}
let accountObj = {
firstName: firstName,
lastName: lastName,
email: email,
accountname: accountname,
password: password,
role: role,
}
postData('Accounts/register', accountObj).then((result) => {
setLoading(false);
let responseJson = result;
if (responseJson) {
navigate('/admin/accounts');
}
});
}
function onClickBack(e){
e.preventDefault();
if(SessionManager.getToken()){
navigate('/admin/accounts');
}else{
navigate('/login');
}
}
/*function onChange(e) {
//setState({ [e.target.name]: e.target.value });
}*/
return (
<div className="row">
<div className="col-md-4">
<h3>Create new account</h3>
<form onSubmit={onSubmit}>
<div className="form-group">
<label className="control-label">First Name: </label>
<input className="form-control" type="text" name="firstName" value={firstName} onChange={(e) => setFirstName(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Last Name: </label>
<input className="form-control" type="text" name="lastName" value={lastName} onChange={(e) => setLastName(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Email: </label>
<input className="form-control" type="text" name="email" value={email} onChange={(e) => setEmail(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Account Name: </label>
<input className="form-control" type="text" name="accountname" value={accountname} onChange={(e) => setAccountname(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Role: </label>
<input className="form-control" type="text" name="roles" value={role} onChange={(e) => setRole(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Password: </label>
<input className="form-control" type="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Confirm Password: </label>
<input className="form-control" type="password" name="confirmationPassword" value={confirmationPassword} onChange={(e) => setConfirmationPassword(e.target.value)}></input>
</div>
<div className="form-group">
<input type="submit" value="Create Account" className="btn btn-primary"></input> &nbsp; &nbsp;
<input type="button" value="Back" onClick={onClickBack} className="btn btn-primary"></input>
</div>
</form>
</div>
</div>
);
}

@ -0,0 +1,110 @@
import { useNavigate } from "react-router-dom";
import { deleteData, getData } from "../services/AccessAPI";
import { useEffect } from "react";
import { useState } from 'react';
import { useLocation } from 'react-router-dom';
export default function DeleteAccount() {
const search = useLocation().search;
const id = new URLSearchParams(search).get('id')
useEffect(() => {
//const { id } = this.props.match.params;
getData('Accounts/' + id).then(
(result) => {
console.log("Role for edit: ");
console.log(result);
if (result) {
setState({
firstName: result.firstName,
lastName: result.lastName,
accountname: result.accountname,
email: result.email,
roles: result.roles,
loading: false
});
}
}
);
}, [id])
let navigate = useNavigate();
const [state, setState] = useState({
firstName: '',
lastName: '',
accountname: '',
email: '',
roles: [],
loading: true
});
function onCancel() {
navigate('/admin/accounts');
}
function onConfirmation(e) {
e.preventDefault();
deleteData('Accounts/' + id).then((result) => {
let responseJson = result;
if (responseJson) {
navigate('/admin/accounts');
}
}
);
}
return (
<div>
<h2>::Delete account::</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Account Information</h4>
<dl className="row">
<dt className="col-sm-2">
First Name:
</dt>
<dd className="col-sm-10">
{state.firstName}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
Last Name:
</dt>
<dd className="col-sm-10">
{state.lastName}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
Account Name:
</dt>
<dd className="col-sm-10">
{state.accountname}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
Email:
</dt>
<dd className="col-sm-10">
{state.email}
</dd>
</dl>
<form onSubmit={onConfirmation}>
<input type="hidden" asp-for="Id" />
<button type="submit" className="btn btn-danger">Delete</button> |
<button onClick={onCancel} className="btn btn-primary">Back to List</button>
</form>
</div>
</div>
)
}

@ -0,0 +1,114 @@
import { getData, putData } from "../services/AccessAPI";
import { useNavigate, useLocation } from "react-router-dom";
import { useEffect } from "react";
import { useState } from 'react';
export default function UpdateAccount() {
let navigate = useNavigate();
const search = useLocation().search;
const id = new URLSearchParams(search).get('id')
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [accountname, setAccountname] = useState("");
const [role, setRole] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
//const id = location.pathName.slice(location.pathName.lastIndexOf("/") , location.pathName.length);
getData('Accounts/' + id).then(
(result) => {
//let responseJson = result;
console.log("account for edit: ");
console.log(result);
console.log("Accountname: " + result.accountname)
if (result) {
setFirstName(result.firstName);
setLastName(result.lastName);
setEmail(result.email);
setAccountname(result.accountname);
setRole(result.roles);
setLoading(false);
}
}
);
}, [id])
function onKeyDown(e) {
if (e.key === 'Enter') {
this.update(false);
}
}
function onUpdateCancel() {
navigate('/admin/accounts');
}
function onSubmit(e){
e.preventDefault();
//const id = location.pathName.slice(location.pathName.lastIndexOf("/") , location.pathName.length);
let accountProfile = {
id: id,
Accountname: accountname,
FirstName: firstName,
LastName: lastName,
Email: email,
}
putData('Accounts/' + id, accountProfile).then((result) => {
console.log("Put Request body: ");
console.log(accountProfile);
let responseJson = result;
console.log("update response: ");
if(responseJson){
console.log(responseJson);
navigate('/admin/accounts');
}
}
);
}
return(
<div className="row">
<div className="col-md-4">
<h3>Edit Account</h3>
<form onSubmit={onSubmit}>
<div className="form-group">
<label className="control-label">First Name: </label>
<input className="form-control" type="text" value={firstName} onChange={(e) => setFirstName(e.target.value)} name="firstName"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">
<label className="control-label">Last Name: </label>
<input className="form-control" type="text" value={lastName} onChange={(e) => setLastName(e.target.value)} name="lastName"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">
<label className="control-label">Account Name: </label>
<input className="form-control" type="text" value={accountname} onChange={(e) => setAccountname(e.target.value)} name="accountname"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">
<label className="control-label">Email: </label>
<input className="form-control" type="text" value={email} onChange={(e) => setEmail(e.target.value)} name="email"
onKeyDown={onKeyDown}></input>
</div>
<div className="form-group">
<button onClick={onUpdateCancel} className="btn btn-default">Cancel</button>
<input type="submit" value="Edit" className="btn btn-primary"></input>
</div>
</form>
</div>
</div>
);
}

@ -2,10 +2,14 @@ import { useNavigate } from "react-router-dom";
import { deleteData, getData } from "../services/AccessAPI";
import { useEffect } from "react";
import { useState } from 'react';
import { useLocation } from 'react-router-dom';
export default function DeleteUser() {
const search = useLocation().search;
const id = new URLSearchParams(search).get('id')
useEffect(() => {
const { id } = this.props.match.params;
//const { id } = this.props.match.params;
getData('Users/' + id).then(
(result) => {
@ -23,7 +27,7 @@ export default function DeleteUser() {
}
}
);
}, [])
}, [id])
let navigate = useNavigate();
@ -43,9 +47,7 @@ export default function DeleteUser() {
function onConfirmation(e) {
e.preventDefault();
const { id } = this.props.match.params;
deleteData('/Users/' + id).then((result) => {
deleteData('Users/' + id).then((result) => {
let responseJson = result;
if (responseJson) {
navigate('/admin/users');
@ -61,45 +63,45 @@ export default function DeleteUser() {
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>User Information</h4>
<dl class="row">
<dt class="col-sm-2">
<dl className="row">
<dt className="col-sm-2">
First Name:
</dt>
<dd class="col-sm-10">
<dd className="col-sm-10">
{state.firstName}
</dd>
</dl>
<dl class="row">
<dt class="col-sm-2">
<dl className="row">
<dt className="col-sm-2">
Last Name:
</dt>
<dd class="col-sm-10">
<dd className="col-sm-10">
{state.lastName}
</dd>
</dl>
<dl class="row">
<dt class="col-sm-2">
<dl className="row">
<dt className="col-sm-2">
User Name:
</dt>
<dd class="col-sm-10">
<dd className="col-sm-10">
{state.username}
</dd>
</dl>
<dl class="row">
<dt class="col-sm-2">
<dl className="row">
<dt className="col-sm-2">
Email:
</dt>
<dd class="col-sm-10">
<dd className="col-sm-10">
{state.email}
</dd>
</dl>
<form onSubmit={onConfirmation}>
<input type="hidden" asp-for="Id" />
<button type="submit" class="btn btn-danger">Delete</button> |
<button type="submit" className="btn btn-danger">Delete</button> |
<button onClick={onCancel} className="btn btn-primary">Back to List</button>
</form>
</div>

@ -4,45 +4,37 @@ import { useEffect } from "react";
import { useState } from 'react';
export default function UpdateUser() {
const location = useLocation();
let navigate = useNavigate();
const search = useLocation().search;
const id = new URLSearchParams(search).get('id')
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [username, setUsername] = useState("");
const [role, setRole] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
const id = location.pathName.slice(location.pathName.lastIndexOf("/") , location.pathName.length);
//const id = location.pathName.slice(location.pathName.lastIndexOf("/") , location.pathName.length);
getData('Users/' + id).then(
(result) => {
//let responseJson = result;
console.log("user for edit: ");
console.log(result);
console.log("Username: " + result.username)
if (result) {
setState({
//users: result,
id: result.id,
firstName: result.firstName,
lastName: result.lastName,
userName: result.userName,
email: result.email,
loading: false
});
setFirstName(result.firstName);
setLastName(result.lastName);
setEmail(result.email);
setUsername(result.username);
setRole(result.roles);
setLoading(false);
}
}
);
}, [location])
let navigate = useNavigate();
const [state, setState] = useState({
id: '',
firstName: '',
lastName: '',
userName: '',
email: '',
roles: []
});
function onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
}, [id])
function onKeyDown(e) {
if (e.key === 'Enter') {
@ -57,26 +49,28 @@ export default function UpdateUser() {
function onSubmit(e){
e.preventDefault();
const id = location.pathName.slice(location.pathName.lastIndexOf("/") , location.pathName.length);
//const id = location.pathName.slice(location.pathName.lastIndexOf("/") , location.pathName.length);
let userProfile = {
id: state.id,
firstName: state.firstName,
lastName: state.lastName,
email: state.email,
roles: state.roles
id: id,
Username: username,
FirstName: firstName,
LastName: lastName,
Email: email,
}
putData('Users/' + id, userProfile).then((result) => {
let responseJson = result;
console.log("update response: ");
if(responseJson){
console.log(responseJson);
navigate('/admin/users');
console.log("Put Request body: ");
console.log(userProfile);
let responseJson = result;
console.log("update response: ");
if(responseJson){
console.log(responseJson);
navigate('/admin/users');
}
}
}
);
}
@ -87,24 +81,25 @@ export default function UpdateUser() {
<form onSubmit={onSubmit}>
<div className="form-group">
<label className="control-label">First Name: </label>
<input className="form-control" type="text" value={state.firstName} onChange={onChange} name="firstName"
<input className="form-control" type="text" value={firstName} onChange={(e) => setFirstName(e.target.value)} name="firstName"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">
<label className="control-label">Last Name: </label>
<input className="form-control" type="text" value={state.lastName} onChange={onChange} name="lastName"
<input className="form-control" type="text" value={lastName} onChange={(e) => setLastName(e.target.value)} name="lastName"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">
<label className="control-label">User Name: </label>
<input className="form-control" type="text" value={state.userName} disabled = {true} readOnly = {true}></input>
<input className="form-control" type="text" value={username} onChange={(e) => setUsername(e.target.value)} name="username"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">
<label className="control-label">Email: </label>
<input className="form-control" type="text" value={state.email} onChange={onChange} name="email"
<input className="form-control" type="text" value={email} onChange={(e) => setEmail(e.target.value)} name="email"
onKeyDown={onKeyDown}></input>
</div>

@ -20,11 +20,15 @@ export default function Users() {
}
function onUserEdit(id){
navigate('/admin/user/edit/' + id);
let path = "/admin/user/edit"
let query = "?id=" + id
navigate(path + query);
}
function onUserDelete(id){
navigate('/admin/user/delete/' + id);
let path = "/admin/user/delete"
let query = "?id=" + id
navigate(path + query);
}
function getAllUsersData() {

@ -0,0 +1,70 @@
namespace active_allocator.Controllers;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using active_allocator.Models.Accounts;
using active_allocator.Services;
using active_allocator.Authorization;
using active_allocator.Helpers;
using System.Collections.Generic;
using active_allocator.Entities;
[Authorize]
[ApiController]
[Route("[controller]")]
public class AccountsController : ControllerBase
{
private IAccountService _accountService;
private IMapper _mapper;
private readonly AppSettings _appSettings;
public AccountsController(
IAccountService accountService,
IMapper mapper,
IOptions<AppSettings> appSettings)
{
_accountService = accountService;
_mapper = mapper;
_appSettings = appSettings.Value;
}
[HttpGet]
public IActionResult GetAll()
{
List<AccountDTO> accountDtos = new List<AccountDTO>();
foreach (Account acc in _accountService.GetAll())
accountDtos.Add(_mapper.Map<Account, AccountDTO>(acc));
return Ok(accountDtos);
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
Account account = _accountService.GetById(id);
return Ok(_mapper.Map<Account, AccountDTO>(account));
}
[HttpPost]
public IActionResult Create([FromBody]AccountCreateRequest model)
{
_accountService.Create(model);
return Ok(new { message = "account created" });
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody]AccountUpdateRequest model)
{
_accountService.Update(id, model);
return Ok(new { message = "account updated" });
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_accountService.Delete(id);
return Ok(new { message = "account deleted" });
}
}

@ -0,0 +1,64 @@
namespace active_allocator.Controllers;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using active_allocator.Models.CurrencyType;
using active_allocator.Services;
using active_allocator.Authorization;
using active_allocator.Helpers;
[Authorize]
[ApiController]
[Route("[controller]")]
public class CurrencyTypesController : ControllerBase
{
private ICurrencyTypeService _currencyTypeService;
private IMapper _mapper;
private readonly AppSettings _appSettings;
public CurrencyTypesController(
ICurrencyTypeService currencyTypeService,
IMapper mapper,
IOptions<AppSettings> appSettings)
{
_currencyTypeService = currencyTypeService;
_mapper = mapper;
_appSettings = appSettings.Value;
}
[HttpGet]
public IActionResult GetAll()
{
var currencyTypes = _currencyTypeService.GetAll();
return Ok(currencyTypes);
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
var currencyType = _currencyTypeService.GetById(id);
return Ok(currencyType);
}
[HttpPost]
public IActionResult Create([FromBody]CurrencyTypeCreateRequest model)
{
_currencyTypeService.Create(model);
return Ok(new { message = "currencyType created" });
}
/*[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody]AccountUpdateRequest model)
{
_accountService.Update(id, model);
return Ok(new { message = "account updated" });
}*/
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_currencyTypeService.Delete(id);
return Ok(new { message = "currencyType deleted" });
}
}

@ -0,0 +1,64 @@
namespace active_allocator.Controllers;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using active_allocator.Models.Accounts;
using active_allocator.Services;
using active_allocator.Authorization;
using active_allocator.Helpers;
[Authorize]
[ApiController]
[Route("[controller]")]
public class FireFlyController : ControllerBase
{
private IFireFlyService _fireFlyService;
private IMapper _mapper;
private readonly AppSettings _appSettings;
public FireFlyController(
IFireFlyService fireFlyService,
IMapper mapper,
IOptions<AppSettings> appSettings)
{
_fireFlyService = fireFlyService;
_mapper = mapper;
_appSettings = appSettings.Value;
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
var transactions = await _fireFlyService.GetAll();
return Ok(transactions);
}
/*
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
var account = _accountService.GetById(id);
return Ok(account);
}
[HttpPost]
public IActionResult Create([FromBody]AccountCreateRequest model)
{
_accountService.Create(model);
return Ok(new { message = "account created" });
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody]AccountUpdateRequest model)
{
_accountService.Update(id, model);
return Ok(new { message = "account updated" });
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_accountService.Delete(id);
return Ok(new { message = "account deleted" });
}*/
}

@ -0,0 +1,70 @@
namespace active_allocator.Controllers;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using active_allocator.Models.Transactions;
using active_allocator.Services;
using active_allocator.Authorization;
using active_allocator.Helpers;
using System.Runtime.InteropServices;
using active_allocator.Entities;
[Authorize]
[ApiController]
[Route("[controller]")]
public class TransactionsController : ControllerBase
{
private ITransactionService _transactionService;
private IMapper _mapper;
private readonly AppSettings _appSettings;
public TransactionsController(
ITransactionService transactionService,
IMapper mapper,
IOptions<AppSettings> appSettings)
{
_transactionService = transactionService;
_mapper = mapper;
_appSettings = appSettings.Value;
}
[HttpGet]
public IActionResult GetAll()
{
List<TransactionDto> transactionDtos = new List<TransactionDto>();
foreach (Transaction tran in _transactionService.GetAll())
transactionDtos.Add(_mapper.Map<Transaction, TransactionDto>(tran));
return Ok(transactionDtos);
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
Transaction tran = _transactionService.GetById(id);
return Ok(_mapper.Map<Transaction, TransactionDto>(tran));
}
[HttpPost]
public IActionResult Create([FromBody]TransactionCreate model)
{
_transactionService.Create(model);
return Ok(new { message = "transaction created" });
}
/*[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody]TransactionUpdateRequest model)
{
_transactionService.Update(id, model);
return Ok(new { message = "transaction updated" });
}*/
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_transactionService.Delete(id);
return Ok(new { message = "transaction deleted" });
}
}

@ -7,6 +7,7 @@ using active_allocator.Models.Users;
using active_allocator.Services;
using active_allocator.Authorization;
using active_allocator.Helpers;
using System;
[Authorize]
[ApiController]
@ -58,11 +59,27 @@ public class UsersController : ControllerBase
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody]UpdateRequest model)
public IActionResult Update(int id, [FromBody]UserUpdateRequest model)
{
_userService.Update(id, model);
return Ok(new { message = "User updated" });
}
/*
[HttpPut("{id}")]
public IActionResult Update(int id, string Email = null, string FirstName = null, string LastName = null, string Username = null, string Role = null, string Password = null)
{
UserUpdateRequest model = new UserUpdateRequest() {
Email = Email,
FirstName = FirstName,
LastName = LastName,
Username = Username,
Role = Role,
Password = Password
};
_userService.Update(id, model);
return Ok(new { message = "User updated" });
}*/
[HttpDelete("{id}")]
public IActionResult Delete(int id)

@ -6,13 +6,16 @@ public class Account
{
public int Id { get; set; }
public string Name { get; set; }
public int OwnerId { get; set; }
public User Owner { get; set; }
public DateTime LastActivity { get; set; }
public decimal Balance { get; set; }
public DateTime CreatedOn { get; set; }
public decimal InitialBalance { get; set; }
public int CurrencyId { get; set; }
public CurrencyType Currency { get; set; }
public string ExternalAccountNumber { get; set; }
public ICollection<Alloc> Allocs { get; set; }
//public ICollection<Transaction> Transactions { get; set; }
//public Institution institution { get; set; }
}

@ -0,0 +1,21 @@
using System.Collections.Generic;
using active_allocator.Services;
namespace active_allocator.Entities;
public class Transaction
{
public int Id { get; set; }
public User Owner { get; set; }
public DateTime Date { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
public string ExternalId { get; set; }
public string Description { get; set; }
public Account DebitAccount { get; set; }
public Account CreditAccount { get; set; }
public decimal Amount { get; set; }
public CurrencyType CurrencyType { get; set; }
public string Notes { get; set; }
public bool IsPending { get; set; }
}

@ -3,9 +3,15 @@ namespace active_allocator.Helpers;
using AutoMapper;
using active_allocator.Entities;
using active_allocator.Models.Users;
using active_allocator.Models.Accounts;
using active_allocator.Models.CurrencyType;
using active_allocator.Services;
using System.Runtime.Serialization;
using active_allocator.Models.Transactions;
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
// User -> AuthenticateResponse
@ -14,8 +20,8 @@ public class AutoMapperProfile : Profile
// RegisterRequest -> User
CreateMap<RegisterRequest, User>();
// UpdateRequest -> User
CreateMap<UpdateRequest, User>()
// UserUpdateRequest -> User
CreateMap<UserUpdateRequest, User>()
.ForAllMembers(x => x.Condition(
(src, dest, prop) =>
{
@ -26,8 +32,83 @@ public class AutoMapperProfile : Profile
// ignore null role
if (x.DestinationMember.Name == "Role" && src.Role == null) return false;
// ignore null password
if (x.DestinationMember.Name == "Password" && src.Password == null) return false;
return true;
}
));
// AccountUpdateRequest -> Account
CreateMap<AccountUpdateRequest, Account>()
.ForAllMembers(x => x.Condition(
(src, dest, prop) =>
{
// ignore both null & empty string properties
if (prop == null) return false;
if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false;
return true;
}
));
// AccountCreateRequest -> Account
CreateMap<AccountCreateRequest, Account>()
.ForMember(
dest => dest.OwnerId,
opt => opt.MapFrom(src => src.Owner)
);
/*.ForAllMembers(x => x.Condition(
(src, dest, prop) =>
{
// ignore both null & empty string properties
if (prop == null) return false;
if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false;
return true;
}
))*/
// Account -> AccountGet
CreateMap<Account, AccountDTO>()
.ForAllMembers(x => x.Condition(
(src, dest, prop) =>
{
// ignore both null & empty string properties
if (prop == null) return false;
if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false;
return true;
}
));
// Transaction -> TransactionDto
CreateMap<Transaction, TransactionDto>()
.ForMember(dest => dest.DebitAccountId, opt => opt.MapFrom(src => src.DebitAccount.Id))
.ForMember(dest => dest.CreditAccountId, opt => opt.MapFrom(src => src.CreditAccount.Id))
.ForAllMembers(x => x.Condition(
(src, dest, prop) =>
{
// ignore both null & empty string properties
if (prop == null) return false;
if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false;
return true;
}
));
// CurrencyTypeCreateRequest -> CurrencyType
CreateMap<CurrencyTypeCreateRequest, CurrencyType>()
.ForAllMembers(x => x.Condition(
(src, dest, prop) =>
{
// ignore both null & empty string properties
if (prop == null) return false;
if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false;
return true;
}
));
}
}

@ -2,6 +2,7 @@ namespace active_allocator.Helpers;
using Microsoft.EntityFrameworkCore;
using active_allocator.Entities;
using System.Diagnostics;
public class DataContext : DbContext
{
@ -23,4 +24,5 @@ public class DataContext : DbContext
public DbSet<Alloc> Allocs { get; set; }
public DbSet<CurrencyType> CurrencyTypes { get; set; }
public DbSet<Operation> Operations { get; set; }
public DbSet<Transaction> Transactions { get; set; }
}

@ -0,0 +1,420 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using active_allocator.Helpers;
#nullable disable
namespace active_allocator.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20231219215509_AddedTransactionEntity")]
partial class AddedTransactionEntity
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("active_allocator.Entities.Account", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Balance")
.HasColumnType("numeric");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<int>("CurrencyId")
.HasColumnType("integer");
b.Property<string>("ExternalAccountNumber")
.IsRequired()
.HasColumnType("text");
b.Property<decimal>("InitialBalance")
.HasColumnType("numeric");
b.Property<DateTime>("LastActivity")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("CurrencyId");
b.HasIndex("OwnerId");
b.ToTable("Accounts");
});
modelBuilder.Entity("active_allocator.Entities.Alloc", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AccountId")
.HasColumnType("integer");
b.Property<decimal>("CurrentBalance")
.HasColumnType("numeric");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<decimal>("LastBalanceAdded")
.HasColumnType("numeric");
b.Property<DateTime>("LastTriggeredOn")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<TimeSpan>("Period")
.HasColumnType("interval");
b.Property<bool>("PersistanceEnabled")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("AccountId");
b.ToTable("Allocs");
});
modelBuilder.Entity("active_allocator.Entities.CurrencyType", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasColumnType("text");
b.Property<int>("DecimalPlaces")
.HasColumnType("integer");
b.Property<string>("Symbol")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("CurrencyTypes");
});
modelBuilder.Entity("active_allocator.Entities.InnerTransaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Amount")
.HasColumnType("numeric");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<int>("CreditAccountId")
.HasColumnType("integer");
b.Property<int>("CurrencyTypeId")
.HasColumnType("integer");
b.Property<int>("DebitAccountId")
.HasColumnType("integer");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("InnerTransactionId")
.HasColumnType("integer");
b.Property<bool>("IsPending")
.HasColumnType("boolean");
b.Property<string>("Notes")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("OuterTransactionId")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CreditAccountId");
b.HasIndex("CurrencyTypeId");
b.HasIndex("DebitAccountId");
b.HasIndex("InnerTransactionId");
b.HasIndex("OuterTransactionId");
b.ToTable("InnerTransactions");
});
modelBuilder.Entity("active_allocator.Entities.Operation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("AbsoluteValue")
.HasColumnType("numeric");
b.Property<int?>("AllocId")
.HasColumnType("integer");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<int>("HistoricLookbackDepth")
.HasColumnType("integer");
b.Property<TimeSpan>("HistoricPeriod")
.HasColumnType("interval");
b.Property<int>("HistoricStatistic")
.HasColumnType("integer");
b.Property<int>("Mode")
.HasColumnType("integer");
b.Property<bool>("Negative")
.HasColumnType("boolean");
b.Property<int>("Order")
.HasColumnType("integer");
b.Property<decimal>("Percentage")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("AllocId");
b.ToTable("Operations");
});
modelBuilder.Entity("active_allocator.Entities.OuterTransaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("text");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.Property<string>("SplitDescription")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("OuterTransactions");
});
modelBuilder.Entity("active_allocator.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Role")
.HasColumnType("integer");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("active_allocator.Entities.Account", b =>
{
b.HasOne("active_allocator.Entities.CurrencyType", "Currency")
.WithMany()
.HasForeignKey("CurrencyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.User", "Owner")
.WithMany("Accounts")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Currency");
b.Navigation("Owner");
});
modelBuilder.Entity("active_allocator.Entities.Alloc", b =>
{
b.HasOne("active_allocator.Entities.Account", "Account")
.WithMany("Allocs")
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Account");
});
modelBuilder.Entity("active_allocator.Entities.InnerTransaction", b =>
{
b.HasOne("active_allocator.Entities.Account", "CreditAccount")
.WithMany()
.HasForeignKey("CreditAccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.CurrencyType", "CurrencyType")
.WithMany()
.HasForeignKey("CurrencyTypeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.Account", "DebitAccount")
.WithMany()
.HasForeignKey("DebitAccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.InnerTransaction", null)
.WithMany("Transactions")
.HasForeignKey("InnerTransactionId");
b.HasOne("active_allocator.Entities.OuterTransaction", null)
.WithMany("Transactions")
.HasForeignKey("OuterTransactionId");
b.Navigation("CreditAccount");
b.Navigation("CurrencyType");
b.Navigation("DebitAccount");
});
modelBuilder.Entity("active_allocator.Entities.Operation", b =>
{
b.HasOne("active_allocator.Entities.Alloc", null)
.WithMany("Operations")
.HasForeignKey("AllocId");
});
modelBuilder.Entity("active_allocator.Entities.OuterTransaction", b =>
{
b.HasOne("active_allocator.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("active_allocator.Entities.Account", b =>
{
b.Navigation("Allocs");
});
modelBuilder.Entity("active_allocator.Entities.Alloc", b =>
{
b.Navigation("Operations");
});
modelBuilder.Entity("active_allocator.Entities.InnerTransaction", b =>
{
b.Navigation("Transactions");
});
modelBuilder.Entity("active_allocator.Entities.OuterTransaction", b =>
{
b.Navigation("Transactions");
});
modelBuilder.Entity("active_allocator.Entities.User", b =>
{
b.Navigation("Accounts");
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,131 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace active_allocator.Migrations
{
/// <inheritdoc />
public partial class AddedTransactionEntity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "OuterTransactions",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
OwnerId = table.Column<int>(type: "integer", nullable: false),
SplitDescription = table.Column<string>(type: "text", nullable: false),
CreatedOn = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedOn = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ExternalId = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_OuterTransactions", x => x.Id);
table.ForeignKey(
name: "FK_OuterTransactions_Users_OwnerId",
column: x => x.OwnerId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "InnerTransactions",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Description = table.Column<string>(type: "text", nullable: false),
DebitAccountId = table.Column<int>(type: "integer", nullable: false),
CreditAccountId = table.Column<int>(type: "integer", nullable: false),
Amount = table.Column<decimal>(type: "numeric", nullable: false),
CreatedOn = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedOn = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ExternalId = table.Column<string>(type: "text", nullable: false),
CurrencyTypeId = table.Column<int>(type: "integer", nullable: false),
Notes = table.Column<string>(type: "text", nullable: false),
IsPending = table.Column<bool>(type: "boolean", nullable: false),
InnerTransactionId = table.Column<int>(type: "integer", nullable: true),
OuterTransactionId = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_InnerTransactions", x => x.Id);
table.ForeignKey(
name: "FK_InnerTransactions_Accounts_CreditAccountId",
column: x => x.CreditAccountId,
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_InnerTransactions_Accounts_DebitAccountId",
column: x => x.DebitAccountId,
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_InnerTransactions_CurrencyTypes_CurrencyTypeId",
column: x => x.CurrencyTypeId,
principalTable: "CurrencyTypes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_InnerTransactions_InnerTransactions_InnerTransactionId",
column: x => x.InnerTransactionId,
principalTable: "InnerTransactions",
principalColumn: "Id");
table.ForeignKey(
name: "FK_InnerTransactions_OuterTransactions_OuterTransactionId",
column: x => x.OuterTransactionId,
principalTable: "OuterTransactions",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_InnerTransactions_CreditAccountId",
table: "InnerTransactions",
column: "CreditAccountId");
migrationBuilder.CreateIndex(
name: "IX_InnerTransactions_CurrencyTypeId",
table: "InnerTransactions",
column: "CurrencyTypeId");
migrationBuilder.CreateIndex(
name: "IX_InnerTransactions_DebitAccountId",
table: "InnerTransactions",
column: "DebitAccountId");
migrationBuilder.CreateIndex(
name: "IX_InnerTransactions_InnerTransactionId",
table: "InnerTransactions",
column: "InnerTransactionId");
migrationBuilder.CreateIndex(
name: "IX_InnerTransactions_OuterTransactionId",
table: "InnerTransactions",
column: "OuterTransactionId");
migrationBuilder.CreateIndex(
name: "IX_OuterTransactions_OwnerId",
table: "OuterTransactions",
column: "OwnerId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "InnerTransactions");
migrationBuilder.DropTable(
name: "OuterTransactions");
}
}
}

@ -0,0 +1,399 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using active_allocator.Helpers;
#nullable disable
namespace active_allocator.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20231221130142_ModifiedTranscationEntity")]
partial class ModifiedTranscationEntity
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("active_allocator.Entities.Account", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Balance")
.HasColumnType("numeric");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<int>("CurrencyId")
.HasColumnType("integer");
b.Property<string>("ExternalAccountNumber")
.IsRequired()
.HasColumnType("text");
b.Property<decimal>("InitialBalance")
.HasColumnType("numeric");
b.Property<DateTime>("LastActivity")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("CurrencyId");
b.HasIndex("OwnerId");
b.ToTable("Accounts");
});
modelBuilder.Entity("active_allocator.Entities.Alloc", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AccountId")
.HasColumnType("integer");
b.Property<decimal>("CurrentBalance")
.HasColumnType("numeric");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<decimal>("LastBalanceAdded")
.HasColumnType("numeric");
b.Property<DateTime>("LastTriggeredOn")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<TimeSpan>("Period")
.HasColumnType("interval");
b.Property<bool>("PersistanceEnabled")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("AccountId");
b.ToTable("Allocs");
});
modelBuilder.Entity("active_allocator.Entities.CurrencyType", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasColumnType("text");
b.Property<int>("DecimalPlaces")
.HasColumnType("integer");
b.Property<string>("Symbol")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("CurrencyTypes");
});
modelBuilder.Entity("active_allocator.Entities.InnerTransaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Amount")
.HasColumnType("numeric");
b.Property<int>("CreditAccountId")
.HasColumnType("integer");
b.Property<int>("CurrencyTypeId")
.HasColumnType("integer");
b.Property<int>("DebitAccountId")
.HasColumnType("integer");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsPending")
.HasColumnType("boolean");
b.Property<string>("Notes")
.IsRequired()
.HasColumnType("text");
b.Property<int?>("OuterTransactionId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("CreditAccountId");
b.HasIndex("CurrencyTypeId");
b.HasIndex("DebitAccountId");
b.HasIndex("OuterTransactionId");
b.ToTable("InnerTransactions");
});
modelBuilder.Entity("active_allocator.Entities.Operation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("AbsoluteValue")
.HasColumnType("numeric");
b.Property<int?>("AllocId")
.HasColumnType("integer");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<int>("HistoricLookbackDepth")
.HasColumnType("integer");
b.Property<TimeSpan>("HistoricPeriod")
.HasColumnType("interval");
b.Property<int>("HistoricStatistic")
.HasColumnType("integer");
b.Property<int>("Mode")
.HasColumnType("integer");
b.Property<bool>("Negative")
.HasColumnType("boolean");
b.Property<int>("Order")
.HasColumnType("integer");
b.Property<decimal>("Percentage")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("AllocId");
b.ToTable("Operations");
});
modelBuilder.Entity("active_allocator.Entities.OuterTransaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone");
b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("text");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.Property<string>("SplitDescription")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("OuterTransactions");
});
modelBuilder.Entity("active_allocator.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Role")
.HasColumnType("integer");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("active_allocator.Entities.Account", b =>
{
b.HasOne("active_allocator.Entities.CurrencyType", "Currency")
.WithMany()
.HasForeignKey("CurrencyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.User", "Owner")
.WithMany("Accounts")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Currency");
b.Navigation("Owner");
});
modelBuilder.Entity("active_allocator.Entities.Alloc", b =>
{
b.HasOne("active_allocator.Entities.Account", "Account")
.WithMany("Allocs")
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Account");
});
modelBuilder.Entity("active_allocator.Entities.InnerTransaction", b =>
{
b.HasOne("active_allocator.Entities.Account", "CreditAccount")
.WithMany()
.HasForeignKey("CreditAccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.CurrencyType", "CurrencyType")
.WithMany()
.HasForeignKey("CurrencyTypeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.Account", "DebitAccount")
.WithMany()
.HasForeignKey("DebitAccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.OuterTransaction", null)
.WithMany("Transactions")
.HasForeignKey("OuterTransactionId");
b.Navigation("CreditAccount");
b.Navigation("CurrencyType");
b.Navigation("DebitAccount");
});
modelBuilder.Entity("active_allocator.Entities.Operation", b =>
{
b.HasOne("active_allocator.Entities.Alloc", null)
.WithMany("Operations")
.HasForeignKey("AllocId");
});
modelBuilder.Entity("active_allocator.Entities.OuterTransaction", b =>
{
b.HasOne("active_allocator.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("active_allocator.Entities.Account", b =>
{
b.Navigation("Allocs");
});
modelBuilder.Entity("active_allocator.Entities.Alloc", b =>
{
b.Navigation("Operations");
});
modelBuilder.Entity("active_allocator.Entities.OuterTransaction", b =>
{
b.Navigation("Transactions");
});
modelBuilder.Entity("active_allocator.Entities.User", b =>
{
b.Navigation("Accounts");
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,93 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace active_allocator.Migrations
{
/// <inheritdoc />
public partial class ModifiedTranscationEntity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_InnerTransactions_InnerTransactions_InnerTransactionId",
table: "InnerTransactions");
migrationBuilder.DropIndex(
name: "IX_InnerTransactions_InnerTransactionId",
table: "InnerTransactions");
migrationBuilder.DropColumn(
name: "CreatedOn",
table: "InnerTransactions");
migrationBuilder.DropColumn(
name: "ExternalId",
table: "InnerTransactions");
migrationBuilder.DropColumn(
name: "InnerTransactionId",
table: "InnerTransactions");
migrationBuilder.DropColumn(
name: "UpdatedOn",
table: "InnerTransactions");
migrationBuilder.AddColumn<DateTime>(
name: "Date",
table: "OuterTransactions",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Date",
table: "OuterTransactions");
migrationBuilder.AddColumn<DateTime>(
name: "CreatedOn",
table: "InnerTransactions",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<string>(
name: "ExternalId",
table: "InnerTransactions",
type: "text",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<int>(
name: "InnerTransactionId",
table: "InnerTransactions",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedOn",
table: "InnerTransactions",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.CreateIndex(
name: "IX_InnerTransactions_InnerTransactionId",
table: "InnerTransactions",
column: "InnerTransactionId");
migrationBuilder.AddForeignKey(
name: "FK_InnerTransactions_InnerTransactions_InnerTransactionId",
table: "InnerTransactions",
column: "InnerTransactionId",
principalTable: "InnerTransactions",
principalColumn: "Id");
}
}
}

@ -0,0 +1,365 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using active_allocator.Helpers;
#nullable disable
namespace active_allocator.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20231221150634_SimplifiedTransaction")]
partial class SimplifiedTransaction
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("active_allocator.Entities.Account", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Balance")
.HasColumnType("numeric");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<int>("CurrencyId")
.HasColumnType("integer");
b.Property<string>("ExternalAccountNumber")
.IsRequired()
.HasColumnType("text");
b.Property<decimal>("InitialBalance")
.HasColumnType("numeric");
b.Property<DateTime>("LastActivity")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("CurrencyId");
b.HasIndex("OwnerId");
b.ToTable("Accounts");
});
modelBuilder.Entity("active_allocator.Entities.Alloc", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("AccountId")
.HasColumnType("integer");
b.Property<decimal>("CurrentBalance")
.HasColumnType("numeric");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<decimal>("LastBalanceAdded")
.HasColumnType("numeric");
b.Property<DateTime>("LastTriggeredOn")
.HasColumnType("timestamp with time zone");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<TimeSpan>("Period")
.HasColumnType("interval");
b.Property<bool>("PersistanceEnabled")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("AccountId");
b.ToTable("Allocs");
});
modelBuilder.Entity("active_allocator.Entities.CurrencyType", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasColumnType("text");
b.Property<int>("DecimalPlaces")
.HasColumnType("integer");
b.Property<string>("Symbol")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("CurrencyTypes");
});
modelBuilder.Entity("active_allocator.Entities.Operation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("AbsoluteValue")
.HasColumnType("numeric");
b.Property<int?>("AllocId")
.HasColumnType("integer");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<int>("HistoricLookbackDepth")
.HasColumnType("integer");
b.Property<TimeSpan>("HistoricPeriod")
.HasColumnType("interval");
b.Property<int>("HistoricStatistic")
.HasColumnType("integer");
b.Property<int>("Mode")
.HasColumnType("integer");
b.Property<bool>("Negative")
.HasColumnType("boolean");
b.Property<int>("Order")
.HasColumnType("integer");
b.Property<decimal>("Percentage")
.HasColumnType("numeric");
b.HasKey("Id");
b.HasIndex("AllocId");
b.ToTable("Operations");
});
modelBuilder.Entity("active_allocator.Entities.Transaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Amount")
.HasColumnType("numeric");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<int>("CreditAccountId")
.HasColumnType("integer");
b.Property<int>("CurrencyTypeId")
.HasColumnType("integer");
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone");
b.Property<int>("DebitAccountId")
.HasColumnType("integer");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsPending")
.HasColumnType("boolean");
b.Property<string>("Notes")
.IsRequired()
.HasColumnType("text");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CreditAccountId");
b.HasIndex("CurrencyTypeId");
b.HasIndex("DebitAccountId");
b.HasIndex("OwnerId");
b.ToTable("Transactions");
});
modelBuilder.Entity("active_allocator.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Role")
.HasColumnType("integer");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("active_allocator.Entities.Account", b =>
{
b.HasOne("active_allocator.Entities.CurrencyType", "Currency")
.WithMany()
.HasForeignKey("CurrencyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.User", "Owner")
.WithMany("Accounts")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Currency");
b.Navigation("Owner");
});
modelBuilder.Entity("active_allocator.Entities.Alloc", b =>
{
b.HasOne("active_allocator.Entities.Account", "Account")
.WithMany("Allocs")
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Account");
});
modelBuilder.Entity("active_allocator.Entities.Operation", b =>
{
b.HasOne("active_allocator.Entities.Alloc", null)
.WithMany("Operations")
.HasForeignKey("AllocId");
});
modelBuilder.Entity("active_allocator.Entities.Transaction", b =>
{
b.HasOne("active_allocator.Entities.Account", "CreditAccount")
.WithMany()
.HasForeignKey("CreditAccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.CurrencyType", "CurrencyType")
.WithMany()
.HasForeignKey("CurrencyTypeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.Account", "DebitAccount")
.WithMany()
.HasForeignKey("DebitAccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CreditAccount");
b.Navigation("CurrencyType");
b.Navigation("DebitAccount");
b.Navigation("Owner");
});
modelBuilder.Entity("active_allocator.Entities.Account", b =>
{
b.Navigation("Allocs");
});
modelBuilder.Entity("active_allocator.Entities.Alloc", b =>
{
b.Navigation("Operations");
});
modelBuilder.Entity("active_allocator.Entities.User", b =>
{
b.Navigation("Accounts");
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,189 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace active_allocator.Migrations
{
/// <inheritdoc />
public partial class SimplifiedTransaction : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "InnerTransactions");
migrationBuilder.DropTable(
name: "OuterTransactions");
migrationBuilder.CreateTable(
name: "Transactions",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
OwnerId = table.Column<int>(type: "integer", nullable: false),
Date = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CreatedOn = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedOn = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ExternalId = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: false),
DebitAccountId = table.Column<int>(type: "integer", nullable: false),
CreditAccountId = table.Column<int>(type: "integer", nullable: false),
Amount = table.Column<decimal>(type: "numeric", nullable: false),
CurrencyTypeId = table.Column<int>(type: "integer", nullable: false),
Notes = table.Column<string>(type: "text", nullable: false),
IsPending = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Transactions", x => x.Id);
table.ForeignKey(
name: "FK_Transactions_Accounts_CreditAccountId",
column: x => x.CreditAccountId,
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Transactions_Accounts_DebitAccountId",
column: x => x.DebitAccountId,
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Transactions_CurrencyTypes_CurrencyTypeId",
column: x => x.CurrencyTypeId,
principalTable: "CurrencyTypes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Transactions_Users_OwnerId",
column: x => x.OwnerId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Transactions_CreditAccountId",
table: "Transactions",
column: "CreditAccountId");
migrationBuilder.CreateIndex(
name: "IX_Transactions_CurrencyTypeId",
table: "Transactions",
column: "CurrencyTypeId");
migrationBuilder.CreateIndex(
name: "IX_Transactions_DebitAccountId",
table: "Transactions",
column: "DebitAccountId");
migrationBuilder.CreateIndex(
name: "IX_Transactions_OwnerId",
table: "Transactions",
column: "OwnerId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Transactions");
migrationBuilder.CreateTable(
name: "OuterTransactions",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
OwnerId = table.Column<int>(type: "integer", nullable: false),
CreatedOn = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Date = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ExternalId = table.Column<string>(type: "text", nullable: false),
SplitDescription = table.Column<string>(type: "text", nullable: false),
UpdatedOn = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_OuterTransactions", x => x.Id);
table.ForeignKey(
name: "FK_OuterTransactions_Users_OwnerId",
column: x => x.OwnerId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "InnerTransactions",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
CreditAccountId = table.Column<int>(type: "integer", nullable: false),
CurrencyTypeId = table.Column<int>(type: "integer", nullable: false),
DebitAccountId = table.Column<int>(type: "integer", nullable: false),
Amount = table.Column<decimal>(type: "numeric", nullable: false),
Description = table.Column<string>(type: "text", nullable: false),
IsPending = table.Column<bool>(type: "boolean", nullable: false),
Notes = table.Column<string>(type: "text", nullable: false),
OuterTransactionId = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_InnerTransactions", x => x.Id);
table.ForeignKey(
name: "FK_InnerTransactions_Accounts_CreditAccountId",
column: x => x.CreditAccountId,
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_InnerTransactions_Accounts_DebitAccountId",
column: x => x.DebitAccountId,
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_InnerTransactions_CurrencyTypes_CurrencyTypeId",
column: x => x.CurrencyTypeId,
principalTable: "CurrencyTypes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_InnerTransactions_OuterTransactions_OuterTransactionId",
column: x => x.OuterTransactionId,
principalTable: "OuterTransactions",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_InnerTransactions_CreditAccountId",
table: "InnerTransactions",
column: "CreditAccountId");
migrationBuilder.CreateIndex(
name: "IX_InnerTransactions_CurrencyTypeId",
table: "InnerTransactions",
column: "CurrencyTypeId");
migrationBuilder.CreateIndex(
name: "IX_InnerTransactions_DebitAccountId",
table: "InnerTransactions",
column: "DebitAccountId");
migrationBuilder.CreateIndex(
name: "IX_InnerTransactions_OuterTransactionId",
table: "InnerTransactions",
column: "OuterTransactionId");
migrationBuilder.CreateIndex(
name: "IX_OuterTransactions_OwnerId",
table: "OuterTransactions",
column: "OwnerId");
}
}
}

@ -174,6 +174,66 @@ namespace active_allocator.Migrations
b.ToTable("Operations");
});
modelBuilder.Entity("active_allocator.Entities.Transaction", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Amount")
.HasColumnType("numeric");
b.Property<DateTime>("CreatedOn")
.HasColumnType("timestamp with time zone");
b.Property<int>("CreditAccountId")
.HasColumnType("integer");
b.Property<int>("CurrencyTypeId")
.HasColumnType("integer");
b.Property<DateTime>("Date")
.HasColumnType("timestamp with time zone");
b.Property<int>("DebitAccountId")
.HasColumnType("integer");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsPending")
.HasColumnType("boolean");
b.Property<string>("Notes")
.IsRequired()
.HasColumnType("text");
b.Property<int>("OwnerId")
.HasColumnType("integer");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CreditAccountId");
b.HasIndex("CurrencyTypeId");
b.HasIndex("DebitAccountId");
b.HasIndex("OwnerId");
b.ToTable("Transactions");
});
modelBuilder.Entity("active_allocator.Entities.User", b =>
{
b.Property<int>("Id")
@ -247,6 +307,41 @@ namespace active_allocator.Migrations
.HasForeignKey("AllocId");
});
modelBuilder.Entity("active_allocator.Entities.Transaction", b =>
{
b.HasOne("active_allocator.Entities.Account", "CreditAccount")
.WithMany()
.HasForeignKey("CreditAccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.CurrencyType", "CurrencyType")
.WithMany()
.HasForeignKey("CurrencyTypeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.Account", "DebitAccount")
.WithMany()
.HasForeignKey("DebitAccountId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("active_allocator.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CreditAccount");
b.Navigation("CurrencyType");
b.Navigation("DebitAccount");
b.Navigation("Owner");
});
modelBuilder.Entity("active_allocator.Entities.Account", b =>
{
b.Navigation("Allocs");

@ -0,0 +1,14 @@
namespace active_allocator.Models.Accounts;
using System.ComponentModel.DataAnnotations;
using System.Runtime.InteropServices;
using active_allocator.Entities;
public class AccountCreateRequest
{
public string Name { get; set; }
public int Owner { get; set; }
public string InitialBalance { get; set; }
public int Currency { get; set; }
public string ExternalAccountNumber { get; set; }
}

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
namespace active_allocator.Models.Accounts;
public class AccountDTO
{
public int Id { get; set; }
public string Name { get; set; }
public int OwnerId { get; set; }
public DateTime LastActivity { get; set; }
public decimal Balance { get; set; }
public DateTime CreatedOn { get; set; }
public decimal InitialBalance { get; set; }
public int CurrencyId { get; set; }
public string ExternalAccountNumber { get; set; }
}

@ -0,0 +1,15 @@
namespace active_allocator.Models.Accounts;
using System.ComponentModel.DataAnnotations;
using active_allocator.Entities;
public class AccountUpdateRequest
{
public string Name { get; set; }
public int OwnerId { get; set; }
public string InitialBalance { get; set; }
public int CurrencyId { get; set; }
public string ExternalAccountNumber { get; set; }
public ICollection<int> Allocs { get; set; }
}

@ -0,0 +1,12 @@
namespace active_allocator.Models.CurrencyType;
using System.ComponentModel.DataAnnotations;
using System.Runtime.InteropServices;
using active_allocator.Entities;
public class CurrencyTypeCreateRequest
{
public string Code { get; set; } // USD
public string Symbol { get; set; } // $
public int DecimalPlaces { get; set; } // 2
}

@ -0,0 +1,10 @@
namespace active_allocator.Models.FireFlyTransactions;
public class Attributes
{
public DateTime created_at { get; set; }
public DateTime updated_at { get; set; }
public string user { get; set; }
public string group_title { get; set; }
public List<Transaction> transactions { get; set; }
}

@ -0,0 +1,9 @@
namespace active_allocator.Models.FireFlyTransactions;
public class Datum
{
public string type { get; set; }
public string id { get; set; }
public Attributes attributes { get; set; }
public Links links { get; set; }
}

@ -0,0 +1,14 @@
namespace active_allocator.Models.FireFlyTransactions;
using Newtonsoft.Json;
public class Links
{
[JsonProperty("0")]
public _0 _0 { get; set; }
public string self { get; set; }
public string first { get; set; }
public string next { get; set; }
public string prev { get; set; }
public string last { get; set; }
}

@ -0,0 +1,6 @@
namespace active_allocator.Models.FireFlyTransactions;
public class Meta
{
public Pagination pagination { get; set; }
}

@ -0,0 +1,10 @@
namespace active_allocator.Models.FireFlyTransactions;
public class Pagination
{
public int total { get; set; }
public int count { get; set; }
public int per_page { get; set; }
public int current_page { get; set; }
public int total_pages { get; set; }
}

@ -0,0 +1,10 @@
namespace active_allocator.Models.FireFlyTransactions;
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class Root
{
public List<Datum> data { get; set; }
public Meta meta { get; set; }
public Links links { get; set; }
}

@ -0,0 +1,66 @@
namespace active_allocator.Models.FireFlyTransactions;
public class Transaction
{
public string user { get; set; }
public string transaction_journal_id { get; set; }
public string type { get; set; }
public DateTime date { get; set; }
public int? order { get; set; }
public string currency_id { get; set; }
public string currency_code { get; set; }
public string currency_symbol { get; set; }
public string currency_name { get; set; }
public int? currency_decimal_places { get; set; }
public string foreign_currency_id { get; set; }
public string foreign_currency_code { get; set; }
public string foreign_currency_symbol { get; set; }
public int? foreign_currency_decimal_places { get; set; }
public string amount { get; set; }
public string foreign_amount { get; set; }
public string description { get; set; }
public string source_id { get; set; }
public string source_name { get; set; }
public string source_iban { get; set; }
public string source_type { get; set; }
public string destination_id { get; set; }
public string destination_name { get; set; }
public string destination_iban { get; set; }
public string destination_type { get; set; }
public string budget_id { get; set; }
public string budget_name { get; set; }
public string category_id { get; set; }
public string category_name { get; set; }
public string bill_id { get; set; }
public string bill_name { get; set; }
public bool reconciled { get; set; }
public string notes { get; set; }
public object tags { get; set; }
public string internal_reference { get; set; }
public string external_id { get; set; }
public string external_url { get; set; }
public string original_source { get; set; }
public int? recurrence_id { get; set; }
public int? recurrence_total { get; set; }
public int? recurrence_count { get; set; }
public string bunq_payment_id { get; set; }
public string import_hash_v2 { get; set; }
public string sepa_cc { get; set; }
public string sepa_ct_op { get; set; }
public string sepa_ct_id { get; set; }
public string sepa_db { get; set; }
public string sepa_country { get; set; }
public string sepa_ep { get; set; }
public string sepa_ci { get; set; }
public string sepa_batch_id { get; set; }
public DateTime? interest_date { get; set; }
public DateTime? book_date { get; set; }
public DateTime? process_date { get; set; }
public DateTime? due_date { get; set; }
public DateTime? payment_date { get; set; }
public DateTime? invoice_date { get; set; }
public double? latitude { get; set; }
public double? longitude { get; set; }
public int? zoom_level { get; set; }
public bool has_attachments { get; set; }
}

@ -0,0 +1,7 @@
namespace active_allocator.Models.FireFlyTransactions;
public class _0
{
public string rel { get; set; }
public string uri { get; set; }
}

@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.Runtime;
using active_allocator.Services;
namespace active_allocator.Models.Transactions;
public class TransactionCreate
{
public int Owner { get; set; }
public DateTime Date { get; set; }
public string ExternalId { get; set; }
public string Description { get; set; }
public int? DebitAccount { get; set; }
public int? CreditAccount { get; set; }
public decimal Amount { get; set; }
public int CurrencyType { get; set; }
public string Notes { get; set; }
public bool IsPending { get; set; }
}
/*
// TransactionCreate myDeserializedClass = JsonConvert.DeserializeObject<TransactionCreate>(myJsonResponse);
{
"Owner":1,
"SplitDescription":"string",
"UreatedOn":"2018-09-17T12:46:47+01:00",
"UpdatedOn":"2018-09-17T12:46:47+01:00",
"ExternalId":"string",
"Transactions":[
{
"Description":"string",
"DebitAccount":1,
"CreditAccount":1,
"Amount":"string",
"CreatedOn":"2018-09-17T12:46:47+01:00",
"UpdatedOn":"2018-09-17T12:46:47+01:00",
"ExternalId":"string",
"CreditAccount":1,
"Notes":"string",
"IsPending":true
}
]
}*/

@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Runtime;
using active_allocator.Services;
namespace active_allocator.Models.Transactions;
public class TransactionDto
{
public int Id { get; set; }
public int OwnerId { get; set; }
public DateTime Date { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
public string ExternalId { get; set; }
public string Description { get; set; }
public int DebitAccountId { get; set; }
public int CreditAccountId { get; set; }
public decimal Amount { get; set; }
public active_allocator.Entities.CurrencyType CurrencyType { get; set; }
public string Notes { get; set; }
public bool IsPending { get; set; }
}

@ -1,9 +1,11 @@
namespace active_allocator.Models.Users;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using active_allocator.Entities;
public class UpdateRequest
public class UserUpdateRequest
{
public string Username { get; set; }
public string FirstName { get; set; }
@ -18,6 +20,7 @@ public class UpdateRequest
// treat empty string as null for password fields to
// make them optional in front end apps
private string _password;
[MinLength(6)]
public string Password
{

@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
using active_allocator.Services;
using Microsoft.OpenApi.Models;
using System.Text.Json.Serialization;
internal class Program
{
@ -24,7 +25,15 @@ internal class Program
});
});
builder.Services.AddControllersWithViews();
builder.Services.AddControllersWithViews().AddJsonOptions(x =>
{
// serialize enums as strings in api responses (e.g. Role)
x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
// ignore omitted parameters on models to enable optional params (e.g. User update)
x.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
@ -40,6 +49,10 @@ internal class Program
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IJwtUtils, JwtUtils>();
builder.Services.AddScoped<IAccountService, AccountService>();
builder.Services.AddScoped<ICurrencyTypeService, CurrencyTypeService>();
builder.Services.AddScoped<IFireFlyService, FireFlyService>();
builder.Services.AddScoped<ITransactionService, TransactionService>();
var app = builder.Build();

@ -0,0 +1,117 @@
namespace active_allocator.Services;
using AutoMapper;
using BCrypt.Net;
using active_allocator.Entities;
using active_allocator.Helpers;
using active_allocator.Models.Accounts;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using Internal;
public interface IAccountService
{
IEnumerable<Account> GetAll();
Account GetById(int id);
void Create(AccountCreateRequest model);
void Update(int id, AccountUpdateRequest model);
void Delete(int id);
}
public class AccountService : IAccountService
{
private DataContext _context;
private readonly IMapper _mapper;
private IUserService _userService;
private ICurrencyTypeService _currencyTypeService;
public AccountService(
DataContext context,
IMapper mapper,
IUserService userService,
ICurrencyTypeService currencyTypeService)
{
_context = context;
_mapper = mapper;
_userService = userService;
_currencyTypeService = currencyTypeService;
}
public IEnumerable<Account> GetAll()
{
return _context.Accounts;
}
public Account GetById(int id)
{
return getAccount(id);
}
public void Create(AccountCreateRequest model)
{
// Check that account with same name or same external number doesn't exist
IEnumerable<Account> accountsWithSameName = _context.Accounts.Where(a => a.Name.ToUpper() == model.Name.ToUpper());
if (accountsWithSameName.Count() > 0)
throw new AppException("Account with name '" + model.Name + "' already exists");
if (!string.IsNullOrWhiteSpace(model.ExternalAccountNumber))
{
IEnumerable<Account> matches = _context.Accounts.Where(a => a.ExternalAccountNumber == model.ExternalAccountNumber);
if (matches.Count() > 0)
throw new AppException("Account with external account number '" + model.ExternalAccountNumber + "' already exists under account named '" + matches.First().Name + "'");
}
// map model to new account object
//var account = _mapper.Map<Account>(model);
Account account = new Account {
Name = model.Name,
OwnerId = model.Owner,
Owner = _userService.GetById(model.Owner),
InitialBalance = Convert.ToDecimal(model.InitialBalance),
Balance = Convert.ToDecimal(model.InitialBalance),
LastActivity = DateTime.UtcNow,
CreatedOn = DateTime.UtcNow,
CurrencyId = model.Currency,
Currency = _currencyTypeService.GetById(model.Currency),
ExternalAccountNumber = model.ExternalAccountNumber
};
_context.Accounts.Add(account);
_context.SaveChanges();
}
public void Update(int id, AccountUpdateRequest model)
{
var account = getAccount(id);
// validate
if (model.Name != account.Name && _context.Accounts.Any(x => x.Name == model.Name))
throw new AppException("Account with the name '" + model.Name + "' already exists");
// copy model to account and save
_mapper.Map(model, account);
_context.Accounts.Update(account);
_context.SaveChanges();
}
public void Delete(int id)
{
var account = getAccount(id);
_context.Accounts.Remove(account);
_context.SaveChanges();
}
// helper methods
private Account getAccount(int id)
{
var account = _context.Accounts.Find(id);
if (account == null) throw new KeyNotFoundException("Account not found");
return account;
}
}

@ -0,0 +1,94 @@
namespace active_allocator.Services;
using AutoMapper;
using BCrypt.Net;
using active_allocator.Entities;
using active_allocator.Helpers;
using active_allocator.Models.CurrencyType;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public interface ICurrencyTypeService
{
IEnumerable<CurrencyType> GetAll();
CurrencyType GetById(int id);
void Create(CurrencyTypeCreateRequest model);
//void Update(int id, AccountUpdateRequest model);
void Delete(int id);
}
public class CurrencyTypeService : ICurrencyTypeService
{
private DataContext _context;
private readonly IMapper _mapper;
public CurrencyTypeService(
DataContext context,
IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public IEnumerable<CurrencyType> GetAll()
{
return _context.CurrencyTypes;
}
public CurrencyType GetById(int id)
{
return getCurrencyType(id);
}
public void Create(CurrencyTypeCreateRequest model)
{
// Check that CurrencyType with same code doesn't exist
IEnumerable<CurrencyType> sameCode = _context.CurrencyTypes.Where(a => a.Code.ToUpper() == model.Code.ToUpper());
if (sameCode.Count() > 0)
throw new AppException("CurrencyType with code '" + model.Code + "' already exists");
// Check that CurrencyType with same symbol doesn't exist
IEnumerable<CurrencyType> sameSymbol = _context.CurrencyTypes.Where(a => a.Symbol.ToUpper() == model.Symbol.ToUpper());
if (sameSymbol.Count() > 0)
throw new AppException("CurrencyType with symbol '" + model.Symbol + "' already exists");
// map model to new account object
var currencyType = _mapper.Map<CurrencyType>(model);
_context.CurrencyTypes.Add(currencyType);
_context.SaveChanges();
}
/*public void Update(int id, AccountUpdateRequest model)
{
var account = getAccount(id);
// validate
if (model.Name != account.Name && _context.Accounts.Any(x => x.Name == model.Name))
throw new AppException("Account with the name '" + model.Name + "' already exists");
// copy model to account and save
_mapper.Map(model, account);
_context.Accounts.Update(account);
_context.SaveChanges();
}*/
public void Delete(int id)
{
var currencyType = getCurrencyType(id);
_context.CurrencyTypes.Remove(currencyType);
_context.SaveChanges();
}
// helper methods
private CurrencyType getCurrencyType(int id)
{
var currencyType = _context.CurrencyTypes.Find(id);
if (currencyType == null) throw new KeyNotFoundException("CurrencyType not found");
return currencyType;
}
}

@ -0,0 +1,139 @@
namespace active_allocator.Services;
using AutoMapper;
using BCrypt.Net;
using active_allocator.Entities;
using active_allocator.Helpers;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json;
using Internal;
using active_allocator.Models.FireFlyTransactions;
public interface IFireFlyService
{
Task<IEnumerable<Datum>> GetAll();/*
Account GetById(int id);
void Create(AccountCreateRequest model);
void Update(int id, AccountUpdateRequest model);
void Delete(int id);*/
}
public class FireFlyService : IFireFlyService
{
private DataContext _context;
private readonly IMapper _mapper;
private string baseUrl = "http://10.0.10.21:8080/api/v1/";
private string bearerToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiODc3ZTVlNzYyMDBiMTJiMDk2ZTQ5NjM2NWQyY2EwZjk5MmVjMzZkNGVjNmVjYjUzYTk4YzZjZTg2Y2I5N2JhMjNjODZjMDNlMDJiNDk3OWYiLCJpYXQiOjE2ODQ4NTgwODYuNjk0OTY4LCJuYmYiOjE2ODQ4NTgwODYuNjk0OTcxLCJleHAiOjE3MTY0ODA0ODYuNjI1MTA3LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.QbKdlv8h0Wqz-zd_hinX288SUWgiyEqDijHmO-6-JN2YN79GungzCddR169mjp9G-AIUykhQ3tOYKiQWSnreOLXZoWJDs6RX6MxqTZIvIFx-Fjt_5qaFt8D6pqm-LVs0j-c0xlW1BvGoZy-tHNnNbaH15vWuLN4sxsekbsfdcIq1JKsR5pYlI59SUdQzkISFjDCKqJaSCQM_lPAaYv9EEmf54yOy8hGHLQHdJmPyv-ngCar0KB5DKMimAof40FuQVMvYUueWxSej-NYp26ZXMRlRAU3A7qL2qVZA6R-Hh9wKkO8evyCrRoh3cQB1XaPMpJMN6E7m_7tsxHnOUNZ2fj0vW7LR4QKbgePhhpW6aXIrkH1_FkeMZn2MNrwoo1FuhEd3ij73K0-rbkikrKhW0G2hyqh0Lvf1HtU0kzWmOCNV9oqjHLm8fPAkcB1g8ngPcoc5ZdcEe1MF9ngJqwNvdQgqIsYr3SmWoY2G2ZVa7Fr3hn7-4D91DV5Pk_R52IfCPWqUvyPIoZfM6vGKuv4F9aGzcTotU69IJUr0-k780elzm1k7K2otYmskJe_xa4sW7E2-OYuFqD2TMBVp6j3rB6ZR5LCGZP9jETJsFis23JSzZpe0XAFf6SVi8BBOOoJxqyefZr-gdOdrZ6_5ffVKmVUbXzXQMEIC-GDz-2Vkbrs";
public FireFlyService(
DataContext context,
IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public async Task<IEnumerable<Datum>> GetAll()
{
string endpointUrl = "transactions?page=";
int totalPages = 1;
List<Datum> transactions = new List<Datum>();
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
for (int i = 1; i <= totalPages; i++)
{
using (var response = await httpClient.GetAsync(baseUrl + endpointUrl + i.ToString()))
{
string apiResponse = await response.Content.ReadAsStringAsync();
Root payload = JsonConvert.DeserializeObject<Root>(apiResponse);
if (payload?.meta?.pagination != null)
totalPages = payload.meta.pagination.total_pages;
transactions.AddRange(payload.data);
}
}
}
return transactions;
}
/*
public Account GetById(int id)
{
return getAccount(id);
}
public void Create(AccountCreateRequest model)
{
// Check that account with same name or same external number doesn't exist
IEnumerable<Account> accountsWithSameName = _context.Accounts.Where(a => a.Name.ToUpper() == model.Name.ToUpper());
if (accountsWithSameName.Count() > 0)
throw new AppException("Account with name '" + model.Name + "' already exists");
if (!string.IsNullOrWhiteSpace(model.ExternalAccountNumber))
{
IEnumerable<Account> matches = _context.Accounts.Where(a => a.ExternalAccountNumber == model.ExternalAccountNumber);
if (matches.Count() > 0)
throw new AppException("Account with external account number '" + model.ExternalAccountNumber + "' already exists under account named '" + matches.First().Name + "'");
}
// map model to new account object
//var account = _mapper.Map<Account>(model);
Account account = new Account {
Name = model.Name,
OwnerId = model.Owner,
Owner = _userService.GetById(model.Owner),
InitialBalance = Convert.ToDecimal(model.InitialBalance),
Balance = Convert.ToDecimal(model.InitialBalance),
LastActivity = DateTime.UtcNow,
CreatedOn = DateTime.UtcNow,
CurrencyId = model.Currency,
Currency = _currencyTypeService.GetById(model.Currency),
ExternalAccountNumber = model.ExternalAccountNumber
};
_context.Accounts.Add(account);
_context.SaveChanges();
}
public void Update(int id, AccountUpdateRequest model)
{
var account = getAccount(id);
// validate
if (model.Name != account.Name && _context.Accounts.Any(x => x.Name == model.Name))
throw new AppException("Account with the name '" + model.Name + "' already exists");
// copy model to account and save
_mapper.Map(model, account);
_context.Accounts.Update(account);
_context.SaveChanges();
}
public void Delete(int id)
{
var account = getAccount(id);
_context.Accounts.Remove(account);
_context.SaveChanges();
}
// helper methods
private Account getAccount(int id)
{
var account = _context.Accounts.Find(id);
if (account == null) throw new KeyNotFoundException("Account not found");
return account;
}*/
}

@ -0,0 +1,121 @@
namespace active_allocator.Services;
using AutoMapper;
using BCrypt.Net;
using active_allocator.Entities;
using active_allocator.Helpers;
using active_allocator.Models.Transactions;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using Microsoft.EntityFrameworkCore;
public interface ITransactionService
{
IEnumerable<Transaction> GetAll();
Transaction GetById(int id);
void Create(TransactionCreate model);
//void Update(int id, AccountUpdateRequest model);
void Delete(int id);
}
public class TransactionService : ITransactionService
{
private DataContext _context;
private readonly IMapper _mapper;
public TransactionService(
DataContext context,
IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public IEnumerable<Transaction> GetAll()
{
return _context.Transactions
.Include(t => t.CurrencyType)
.Include(t => t.DebitAccount)
.Include(t => t.CreditAccount)
.ToList();
}
public Transaction GetById(int id)
{
return getTransaction(id);
}
public void Create(TransactionCreate model)
{
Transaction transaction = new Transaction {
Owner = _context.Users.Find(model.Owner),
Description = model.Description,
Date = model.Date,
CreatedOn = DateTime.UtcNow,
UpdatedOn = DateTime.UtcNow,
ExternalId = model.ExternalId,
DebitAccount = model.DebitAccount == null ? null : _context.Accounts.Find(model.DebitAccount.Value),
CreditAccount = model.CreditAccount == null ? null : _context.Accounts.Find(model.CreditAccount.Value),
Amount = Convert.ToDecimal(model.Amount),
CurrencyType = _context.CurrencyTypes.Find(model.CurrencyType),
Notes = model.Notes,
IsPending = model.IsPending,
};
// Transaction Duplication Check
if (transaction.ExternalId != null && _context.Transactions.Any(x => x.ExternalId == transaction.ExternalId))
throw new AppException("Transaction with the external ID '" + transaction.ExternalId + "' already exists");
if (_context.Transactions.Any(x =>
x.Owner == transaction.Owner
&& x.Description == transaction.Description
&& x.Date == transaction.Date
&& x.DebitAccount == transaction.DebitAccount
&& x.CreditAccount == transaction.CreditAccount
&& x.Amount == transaction.Amount))
{
throw new AppException("Transaction with the same fields already exists");
}
// map model to new account object
//var account = _mapper.Map<Account>(model);
_context.Transactions.Add(transaction);
_context.SaveChanges();
}
/*public void Update(int id, AccountUpdateRequest model)
{
var account = getOuterTransaction(id);
// validate
if (model.Name != account.Name && _context.Accounts.Any(x => x.Name == model.Name))
throw new AppException("Account with the name '" + model.Name + "' already exists");
// copy model to account and save
_mapper.Map(model, account);
_context.Accounts.Update(account);
_context.SaveChanges();
}*/
public void Delete(int id)
{
var transaction = getTransaction(id);
_context.Transactions.Remove(transaction);
_context.SaveChanges();
}
private Transaction getTransaction(int id)
{
var transaction = _context.Transactions.Find(id);
_context.Entry(transaction).Reference(t => t.CurrencyType).Load();
_context.Entry(transaction).Reference(t => t.DebitAccount).Load();
_context.Entry(transaction).Reference(t => t.CreditAccount).Load();
if (transaction == null) throw new KeyNotFoundException("Transaction not found");
return transaction;
}
}

@ -6,6 +6,9 @@ using active_allocator.Authorization;
using active_allocator.Entities;
using active_allocator.Helpers;
using active_allocator.Models.Users;
using System;
using System.Collections;
using System.Collections.Generic;
public interface IUserService
{
@ -13,7 +16,7 @@ public interface IUserService
void Register(RegisterRequest model);
IEnumerable<User> GetAll();
User GetById(int id);
void Update(int id, UpdateRequest model);
void Update(int id, UserUpdateRequest model);
void Delete(int id);
}
@ -22,15 +25,18 @@ public class UserService : IUserService
private DataContext _context;
private IJwtUtils _jwtUtils;
private readonly IMapper _mapper;
private IFireFlyService _fireFlyService;
public UserService(
DataContext context,
IMapper mapper,
IJwtUtils jwtUtils)
IJwtUtils jwtUtils,
IFireFlyService fireFlyService)
{
_context = context;
_mapper = mapper;
_jwtUtils = jwtUtils;
_fireFlyService = fireFlyService;
}
public AuthenticateResponse Authenticate(AuthenticateRequest model)
@ -70,6 +76,18 @@ public class UserService : IUserService
public IEnumerable<User> GetAll()
{
/*foreach (Models.FireFlyTransactions.Datum item in _fireFlyService.GetAll())
{
Console.WriteLine($"Id: {item.id}");
foreach (Models.FireFlyTransactions.Transaction tr in item.attributes.transactions)
{
Console.WriteLine($"\t{Convert.ToDecimal(tr.amount)} Description: {tr.description}");
}
Console.WriteLine("");
}
*/
return _context.Users;
}
@ -78,7 +96,7 @@ public class UserService : IUserService
return getUser(id);
}
public void Update(int id, UpdateRequest model)
public void Update(int id, UserUpdateRequest model)
{
var user = getUser(id);

Loading…
Cancel
Save