Compare commits

...
Author SHA1 Message Date
bence 9e448e62c6 Merge pull request 'feature/RVRNEXT-34-use-text-labels-instead-of-placeholders-in-forms' (!47) from feature/RVRNEXT-34-use-text-labels-instead-of-placeholders-in-forms into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #47
2023-05-02 19:54:08 +02:00
bence 7f1177cc55 RVRNEXT-34 swap delete and save in transaction_edit
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-02 19:53:06 +02:00
bence a941f5a1cd RVRNEXT-34 remove placeholders, use labels instead 2023-05-02 19:51:15 +02:00
bence ba5dfdbe4d RVRNEXT-34 add style class for form label 2023-05-02 19:49:43 +02:00
bence 5557f00e57 Merge pull request 'RVRNEXT-36 add autocomplete values for username and password fields' (!46) from feature/RVRNEXT-36-add-autocompletes-for-usernames-and-passwords into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #46
2023-05-02 13:13:25 +02:00
bence baddc17b33 RVRNEXT-36 add autocomplete values for username and password fields
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-02 13:12:19 +02:00
bence 8d495c9f37 Merge pull request 'feature/RVRNEXT-33-no-permanent-session' (!45) from feature/RVRNEXT-33-no-permanent-session into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #45
2023-05-02 13:03:32 +02:00
bence cc8f1e1ace RVRNEXT-33 session should be valid for a session
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-02 12:59:45 +02:00
bence 97e04d785c RVRNEXT-33 adapt to new soko-web interfaces 2023-05-02 12:59:45 +02:00
bence 0c102da1d7 RVRNEXT-33 update soko-web to 0.10 2023-05-02 12:58:18 +02:00
bence 7f882f2e71 Merge pull request 'RVRNEXT-28 add further checks before manupulating db' (!44) from feature/RVRNEXT-28-check-foreign-key-constraints-before-db into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #44
2023-05-02 02:58:35 +02:00
bence cb76cbb9b1 RVRNEXT-28 add further checks before manupulating db
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-02 02:56:16 +02:00
bence 1f32387f63 Merge pull request 'RVRNEXT-30 check if member already has associated transaction' (!43) from bugfix/RVRNEXT-29-prevent-deleting-member-who-has-transaction into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #43
2023-05-02 02:51:02 +02:00
bence be93c75fe7 RVRNEXT-30 check if member already has associated transaction
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-02 02:40:27 +02:00
bence 38d18873b8 Merge pull request 'feature/RVRNEXT-30-replace-choices-to-tomselect' (!41) from feature/RVRNEXT-30-replace-choices-to-tomselect into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #41
2023-05-02 02:24:40 +02:00
bence 5053d37388 RVRNEXT-30 initialize TomSelect in community_members.js
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-02 02:22:57 +02:00
bence c00be9a49d RVRNEXT-30 remove unnecessary css selector 2023-05-02 02:22:35 +02:00
bence 881280fa3e RVRNEXT-30 use tom-select assets in community_member view 2023-05-02 02:22:10 +02:00
bence 40eb8b5335 RVRNEXT-30 fix submit button name 2023-05-02 02:21:49 +02:00
bence 789e9e199f RVRNEXT-30 install tom-select instead of choices.js 2023-05-02 02:20:40 +02:00
bence be521a42ee Merge pull request 'RVRNEXT-26 show confirmation modal before deleting something' (!40) from feature/RVRNEXT-26-confirmation-before-delete into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #40
2023-05-02 01:49:13 +02:00
bence 65424c2944 RVRNEXT-26 show confirmation modal before deleting something
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-02 01:03:39 +02:00
bence 490e48a87a Merge pull request 'rename routes' (!39) from feature/rename-routes into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #39
2023-05-02 00:06:11 +02:00
bence b958a2f5b3 rename routes
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-02 00:02:38 +02:00
bence 2da0ccb09b Merge pull request 'feature/layout-improvements' (!38) from feature/layout-improvements into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #38
2023-05-01 23:42:30 +02:00
bence 584fcaba39 make community layout clearer
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-01 23:37:30 +02:00
bence b5dc96f98b fix stupid variable names 2023-05-01 23:02:57 +02:00
bence 4dbbb1321c Merge pull request 'feature/RVRNEXT-7-calculate-balance-for-community-members' (!37) from feature/RVRNEXT-7-calculate-balance-for-community-members into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #37
2023-05-01 22:26:03 +02:00
bence 0a7cfdbb8e RVRNEXT-7 show balances on community home page
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-01 22:01:11 +02:00
bence 751c1895ca RVRNEXT-7 calculate balances in CommunityController 2023-05-01 22:00:32 +02:00
bence 3fd43b6e41 RVRNEXT-7 add BalanceCalculator class 2023-05-01 22:00:13 +02:00
bence ae3554c365 RVRNEXT-7 layout fine-tuning 2023-05-01 21:59:55 +02:00
bence 5526a55145 RVRNEXT-7 add link for transactions 2023-05-01 19:32:35 +02:00
bence 3e6514a2e5 Merge pull request 'feature/RVRNEXT-6-transactions' (!36) from feature/RVRNEXT-6-transactions into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #36
2023-05-01 19:28:27 +02:00
bence 8566641459 RVRNEXT-6 add new routes for transactions
rvr-nextgen/pipeline/pr-master This commit looks good
2023-05-01 19:24:24 +02:00
bence 14f7a5b9b5 RVRNEXT-6 add views for transactions 2023-05-01 19:23:48 +02:00
bence 6fb951a01e RVRNEXT-6 add transactioncontroller 2023-05-01 19:23:31 +02:00
bence c7c2df89c5 RVRNEXT-6 add ExchangeRateCalculator 2023-05-01 19:23:19 +02:00
bence 67114211ec RVRNEXT-6 add model and repository for transactions 2023-05-01 19:19:39 +02:00
bence f2d99c4f0f RVRNEXT-6 add migration for transaction db structure 2023-05-01 19:19:10 +02:00
bence 2ed01df331 RVRNEXT-6 layout fine-tuning 2023-05-01 19:18:48 +02:00
bence f89bed25da RVRNEXT-6 update soko-web 2023-05-01 19:15:45 +02:00
bence d707c20ab6 Merge pull request 'feature/update-soko-web-to-0.7' (!35) from feature/update-soko-web-to-0.7 into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #35
2023-04-30 21:07:16 +02:00
bence 49d72f1d48 adapt $withRelations usage to soko-web 0.7
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-30 20:25:21 +02:00
bence eabf17cb77 update soko-web to 0.7 2023-04-30 20:24:10 +02:00
bence 7b45069904 Merge pull request 'feature/RVRNEXT-5-main-currency-of-community-should-be-a-general-currency' (!34) from feature/RVRNEXT-5-main-currency-of-community-should-be-a-general-currency into master
rvr-nextgen/pipeline/head This commit looks good
Reviewed-on: #34
2023-04-28 21:44:18 +02:00
bence 49df206ddc RVRNEXT-5 move saveCommunity under getCommunityEdit
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-28 21:20:08 +02:00
bence e0ea18a6d0 RVRNEXT-5 migration for main_currency_id
rvr-nextgen/pipeline/pr-master This commit looks good
2023-04-28 21:06:36 +02:00
bence 20a850f011 RVRNEXT-5 make community main currency a general currency 2023-04-28 21:06:18 +02:00
bence 157f530ad5 RVRNEXT-5 add withRelations to CommunityMemberRepository getters 2023-04-28 20:59:14 +02:00
bence 0b261b6272 RVRNEXT-5 fix audit logger for non-web access 2023-04-28 20:58:19 +02:00
42 changed files with 1265 additions and 275 deletions

No files matched your search

+3 -3
View File
@@ -17,9 +17,9 @@ class Container
static SokoWeb\Interfaces\Database\IConnection $dbConnection;
static SokoWeb\Interfaces\Database\IAuditLogger $auditLogger;
static SokoWeb\Interfaces\PersistentData\IPersistentDataManager $persistentDataManager;
static SokoWeb\Interfaces\Routing\IRouteCollection $routeCollection;
static SokoWeb\Interfaces\Session\ISessionHandler $sessionHandler;
static SokoWeb\Interfaces\Request\IRequest $request;
static ?SokoWeb\Interfaces\Routing\IRouteCollection $routeCollection = null;
static ?SokoWeb\Interfaces\Session\ISessionHandler $sessionHandler = null;
static ?SokoWeb\Interfaces\Request\IRequest $request = null;
}
Container::$dbConnection = new SokoWeb\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
+1 -1
View File
@@ -10,7 +10,7 @@
}
],
"require": {
"esoko/soko-web": "0.6",
"esoko/soko-web": "0.10",
"firebase/php-jwt": "^6.4"
},
"require-dev": {
Generated
+4 -4
View File
@@ -4,15 +4,15 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7b9e3854a5b7ef69d360c1dfbac75c17",
"content-hash": "607b383c44826c6307dd79b0362ca320",
"packages": [
{
"name": "esoko/soko-web",
"version": "0.6",
"version": "0.10",
"source": {
"type": "git",
"url": "https://git.esoko.eu/esoko/soko-web.git",
"reference": "5e0579463cf0f4203c46e0d4f9c09cd283dbf0b8"
"reference": "fd286c9cffadc4a26609bfa00944f8014a2f3f59"
},
"require": {
"phpmailer/phpmailer": "^6.8",
@@ -33,7 +33,7 @@
"GNU GPL 3.0"
],
"description": "Lightweight web framework",
"time": "2023-04-19T21:35:03+00:00"
"time": "2023-05-02T10:55:47+00:00"
},
{
"name": "firebase/php-jwt",
@@ -0,0 +1,19 @@
<?php
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\Currency;
use SokoWeb\Database\Query\Select;
$select = new Select(Container::$dbConnection);
$communities = Container::$persistentDataManager->selectMultipleFromDb($select, Community::class);
foreach ($communities as $community) {
$mainCurrency = new Currency();
$mainCurrency->setCommunity($community);
$mainCurrency->setCode($community->getCurrency());
$mainCurrency->setRoundDigits(0);
Container::$persistentDataManager->saveToDb($mainCurrency);
$community->setMainCurrency($mainCurrency);
Container::$persistentDataManager->saveToDb($community);
}
@@ -0,0 +1,4 @@
ALTER TABLE `communities`
ADD `main_currency_id` int(10) unsigned DEFAULT NULL,
ADD KEY `main_currency_id` (`main_currency_id`),
ADD CONSTRAINT `communities_main_currency_id` FOREIGN KEY (`main_currency_id`) REFERENCES `currencies` (`id`);
@@ -0,0 +1,19 @@
CREATE TABLE `transactions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`community_id` int(10) unsigned NOT NULL,
`currency_id` int(10) unsigned NOT NULL,
`payer_user_id` int(10) unsigned NOT NULL,
`payee_user_id` int(10) unsigned NULL,
`description` varchar(255) NOT NULL,
`sum` decimal(19,9) NOT NULL,
`time` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `community_id` (`community_id`),
KEY `currency_id` (`currency_id`),
KEY `payer_user_id` (`payer_user_id`),
KEY `payee_user_id` (`payee_user_id`),
CONSTRAINT `transactions_community_id` FOREIGN KEY (`community_id`) REFERENCES `communities` (`id`),
CONSTRAINT `transactions_currency_id` FOREIGN KEY (`currency_id`) REFERENCES `currencies` (`id`),
CONSTRAINT `transactions_payer_user_id` FOREIGN KEY (`payer_user_id`) REFERENCES `users` (`id`),
CONSTRAINT `transactions_payee_user_id` FOREIGN KEY (`payee_user_id`) REFERENCES `users` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
+69 -7
View File
@@ -31,6 +31,10 @@ main {
color: #ffffff;
}
::placeholder, select > option[value=""], .gray {
color: #8e8e8e;
}
p, h1, h2, h3, input, textarea, select, button, a, table, label {
font-family: 'Oxygen', sans-serif;
}
@@ -63,7 +67,7 @@ p, h2, h3 {
line-height: 150%;
}
p {
p, th, td {
font-weight: 400;
font-size: 16px;
}
@@ -100,10 +104,23 @@ hr {
font-weight: 700;
}
p.small, span.small {
p.small, span.small, td.small {
font-size: 14px;
}
.red {
color: #a80908;
}
.green {
color: #008000;
}
.mono {
font-family: 'Oxygen Mono', mono;
font-weight: 400;
}
.justify {
text-align: justify;
}
@@ -150,6 +167,12 @@ a:hover, a:focus {
text-decoration: underline;
}
a.block {
color: initial;
font-weight: initial;
text-decoration: initial;
}
button, a.button {
cursor: pointer;
font-size: 16px;
@@ -215,6 +238,7 @@ button.noRightRadius, a.button.noRightRadius {
button.gray, a.button.gray {
background-color: #808080;
color: #ffffff;
}
button.gray:enabled:hover, button.gray:enabled:focus, a.button.gray:hover, a.button.gray:focus {
@@ -223,6 +247,7 @@ button.gray:enabled:hover, button.gray:enabled:focus, a.button.gray:hover, a.but
button.red, a.button.red {
background-color: #aa5e5e;
color: #ffffff;
}
button.red:enabled:hover, button.red:enabled:focus, a.button.red:hover, a.button.red:focus {
@@ -421,7 +446,6 @@ div.buttonContainer>button {
}
div.box {
width: 576px;
background-color: #eeeef4;
border-radius: 3px;
margin: 10px auto;
@@ -429,6 +453,15 @@ div.box {
box-sizing: border-box;
}
div.compactBox {
width: 576px;
}
div.transaction {
display: grid;
grid-template-columns: auto auto;
}
div.gridContainer {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
@@ -451,7 +484,7 @@ table.fullWidth {
}
table th {
font-weight: bold;
font-weight: 700;
}
table th, table td {
@@ -467,8 +500,37 @@ table th:not(:last-child), table td:not(:last-child) {
padding-right: 3px;
}
.choices__inner {
box-sizing: border-box;
p.paginateContainer {
font-size: 0;
}
p.paginateContainer > * {
font-size: initial;
background-color: #5e77aa;
border: solid #5e77aa 1px;
color: #ffffff;
font-weight: 700;
padding: 3px 6px;
text-align: center;
display: inline-block;
height: 25px;
line-height: 25px;
width: 25px;
}
p.paginateContainer > a:hover, p.paginateContainer > .selected {
background-color: #3b5998;
border: solid #29457f 1px;
text-decoration: none;
}
p.paginateContainer > *:not(:last-child) {
border-right: solid #869ab9 1px;
}
p.formLabel {
font-weight: 700;
font-size: 14px;
}
@media screen and (max-width: 424px) {
@@ -501,7 +563,7 @@ table th:not(:last-child), table td:not(:last-child) {
padding-left: 15px;
padding-right: 15px;
}
div.box {
div.compactBox {
width: initial;
}
}
@@ -1,17 +1,30 @@
(function () {
const element = document.getElementById('newMember').elements['user_id'];
const choices = new Choices(element, {
noResultsText: 'No users found',
noChoicesText: 'Start typing to search users'
const select = new TomSelect(element, {
valueField: 'value',
labelField: 'label',
searchField: 'label',
loadThrottle: 300,
load: function (query, callback) {
var self = this;
RVR.httpRequest('GET', searchUserUrl.replace('QUERY', encodeURIComponent(query)), function () {
self.clearOptions();
callback(this.response.results);
});
},
});
element.addEventListener('search', RVR.debounce(async function (e) {
RVR.httpRequest('GET', searchUserUrl.replace('QUERY', encodeURIComponent(e.detail.value)), function () {
choices.setChoices(this.response.results, 'value', 'label', true);
});
}));
select.on('change', function (value) {
this.clearOptions();
});
element.addEventListener('choice', function () {
choices.setChoices([], 'value', 'label', true);
select.on('blur', function (value) {
this.clearOptions();
});
select.on('type', function (value) {
if (value === '') {
this.clearOptions();
}
});
})();
+28 -2
View File
@@ -88,6 +88,28 @@ var RVR = {
}
},
setOnclickForFormConfirmation: function (button) {
button.onclick = function(e) {
e.preventDefault();
var self = this;
RVR.showModalWithContent('Confirmation', this.dataset.confirmation, [
{
type: 'button',
text: self.textContent,
classNames: ['red'],
onclick: function() {
var event = new Event('submit', {'bubbles': true, 'cancelable': true});
self.form.dispatchEvent(event);
RVR.hideModal();
}
}
]);
};
},
showModal: function (id) {
document.getElementById(id).style.visibility = 'visible';
document.getElementById('cover').style.visibility = 'visible';
@@ -174,7 +196,7 @@ var RVR = {
}
}
form.elements.submit.disabled = !anyChanged;
form.elements['submit_button'].disabled = !anyChanged;
},
observeInputsInForm: function (form, observedInputs) {
@@ -197,7 +219,7 @@ var RVR = {
}
form.onreset = function () {
form.elements.submit.disabled = true;
form.elements['submit_button'].disabled = true;
}
},
@@ -234,6 +256,10 @@ var RVR = {
if (form.dataset.observeInputs) {
RVR.observeInputsInForm(form, form.dataset.observeInputs.split(','));
}
if (form.elements['submit_button'] && form.elements['submit_button'].dataset.confirmation) {
RVR.setOnclickForFormConfirmation(form.elements['submit_button']);
}
}
document.getElementById('cover').onclick = function () {
+15 -34
View File
@@ -2,33 +2,18 @@
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"@babel/runtime": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
"@orchidjs/sifter": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@orchidjs/sifter/-/sifter-1.0.3.tgz",
"integrity": "sha512-zCZbwKegHytfsPm8Amcfh7v/4vHqTAaOu6xFswBYcn8nznBOuseu6COB2ON7ez0tFV0mKL0nRNnCiZZA+lU9/g==",
"requires": {
"regenerator-runtime": "^0.13.11"
"@orchidjs/unicode-variants": "^1.0.4"
}
},
"choices.js": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/choices.js/-/choices.js-10.2.0.tgz",
"integrity": "sha512-8PKy6wq7BMjNwDTZwr3+Zry6G2+opJaAJDDA/j3yxvqSCnvkKe7ZIFfIyOhoc7htIWFhsfzF9tJpGUATcpUtPg==",
"requires": {
"deepmerge": "^4.2.2",
"fuse.js": "^6.6.2",
"redux": "^4.2.0"
}
},
"deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
},
"fuse.js": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
"integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA=="
"@orchidjs/unicode-variants": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@orchidjs/unicode-variants/-/unicode-variants-1.0.4.tgz",
"integrity": "sha512-NvVBRnZNE+dugiXERFsET1JlKZfM5lJDEpSMilKW4bToYJ7pxf0Zne78xyXB2ny2c2aHfJ6WLnz1AaTNHAmQeQ=="
},
"leaflet": {
"version": "1.9.3",
@@ -40,18 +25,14 @@
"resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz",
"integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA=="
},
"redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"tom-select": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tom-select/-/tom-select-2.2.2.tgz",
"integrity": "sha512-igGah1yY6yhrnN2h/Ky8I5muw/nE/YQxIsEZoYu5qaA4bsRibvKto3s8QZZosKpOd0uO8fNYhRfAwgHB4IAYew==",
"requires": {
"@babel/runtime": "^7.9.2"
"@orchidjs/sifter": "^1.0.3",
"@orchidjs/unicode-variants": "^1.0.4"
}
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
}
}
}
+1 -1
View File
@@ -2,6 +2,6 @@
"dependencies": {
"leaflet": "^1.6.0",
"leaflet.markercluster": "^1.4.1",
"choices.js": "^10.2.0"
"tom-select": "^2.2.2"
}
}
+138 -45
View File
@@ -1,6 +1,7 @@
<?php namespace RVR\Controller;
use DateTime;
use RVR\Finance\BalanceCalculator;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\CommunityMember;
use RVR\PersistentData\Model\Currency;
@@ -10,6 +11,7 @@ use RVR\Repository\CommunityRepository;
use RVR\Repository\CommunityMemberRepository;
use RVR\Repository\CurrencyExchangeRateRepository;
use RVR\Repository\CurrencyRepository;
use RVR\Repository\TransactionRepository;
use RVR\Repository\UserRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Response\IContent;
@@ -28,6 +30,8 @@ class CommunityController implements IAuthenticationRequired
private CurrencyExchangeRateRepository $currencyExchangeRatesRepository;
private TransactionRepository $transactionRepository;
public function __construct()
{
$this->userRepository = new UserRepository();
@@ -35,6 +39,7 @@ class CommunityController implements IAuthenticationRequired
$this->communityMemberRepository = new CommunityMemberRepository();
$this->currencyRepository = new CurrencyRepository();
$this->currencyExchangeRatesRepository = new CurrencyExchangeRateRepository();
$this->transactionRepository = new TransactionRepository();
}
public function isAuthenticationRequired(): bool
@@ -48,11 +53,48 @@ class CommunityController implements IAuthenticationRequired
return null;
}
\Container::$persistentDataManager->loadRelationsFromDb($community, false, ['main_currency']);
$balanceCalculator = new BalanceCalculator($community);
$debts = $balanceCalculator->calculate();
$debtItems = [];
$debtBalance = 0.0;
$outstandingItems = [];
$outstandingBalance = 0.0;
foreach ($debts as $debt) {
if ($debt['payer']->getId() === \Container::$request->user()->getUniqueId()) {
$debtBalance += $debt['amount'];
$debtItems[] = $debt;
}
if ($debt['payee']->getId() === \Container::$request->user()->getUniqueId()) {
$outstandingBalance += $debt['amount'];
$outstandingItems[] = $debt;
}
}
$balance = $outstandingBalance - $debtBalance;
return new HtmlContent('communities/community', [
'community' => $community,
'upcomingEvents' => [],
'debtItems' => $debtItems,
'debtBalance' => $debtBalance,
'outstandingItems' => $outstandingItems,
'outstandingBalance' => $outstandingBalance,
'balance' => $balance,
'editPermission' => $ownCommunityMember->getOwner()
]);
}
public function getCommunitySettings(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), false, $community, $ownCommunityMember)) {
return null;
}
return new HtmlContent('communities/community_settings', [
'community' => $community,
'members' => $this->getMembers($community),
'currencies' => $this->getCurrencies($community),
'upcomingEvents' => [],
'editPermission' => $ownCommunityMember->getOwner()
]);
}
@@ -73,6 +115,63 @@ class CommunityController implements IAuthenticationRequired
]);
}
public function saveCommunity(): ?IContent
{
$name = \Container::$request->post('name');
if (strlen($name) === 0) {
return new JsonContent([
'error' => ['errorText' => 'Please fill all required fields!']
]);
}
$communityId = \Container::$request->query('communityId');
if ($communityId){
if (!$this->checkPermission($communityId, true, $community, $ownCommunityMember)) {
return null;
}
} else {
$mainCurrencyCode = \Container::$request->post('main_currency_code');
$mainCurrencyRoundDigits = \Container::$request->post('main_currency_round_digits');
if (strlen($mainCurrencyCode) === 0 || strlen($mainCurrencyCode) > 3 || $mainCurrencyRoundDigits < 0 || $mainCurrencyRoundDigits > 9) {
return new JsonContent([
'error' => ['errorText' => 'Please fill all required fields!']
]);
}
$community = new Community();
$community->setCreatedDate(new DateTime());
}
$community->setName($name);
\Container::$persistentDataManager->saveToDb($community);
if (!$communityId) {
/**
* @var User $user
*/
$user = \Container::$request->user();
$communityMember = new CommunityMember();
$communityMember->setCommunity($community);
$communityMember->setUser($user);
$communityMember->setOwner(true);
\Container::$persistentDataManager->saveToDb($communityMember);
$mainCurrency = new Currency();
$mainCurrency->setCommunity($community);
$mainCurrency->setCode($mainCurrencyCode);
$mainCurrency->setRoundDigits($mainCurrencyRoundDigits);
\Container::$persistentDataManager->saveToDb($mainCurrency);
$community->setMainCurrency($mainCurrency);
\Container::$persistentDataManager->saveToDb($community);
}
return new JsonContent([
'redirect' => ['target' => \Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()])]
]);
}
public function getMembersEdit(): ?IContent
{
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
@@ -100,7 +199,19 @@ class CommunityController implements IAuthenticationRequired
]);
}
} else {
if ($this->transactionRepository->isAnyCommon()) {
return new JsonContent([
'error' => ['errorText' => 'There are transactions with common payee!']
]);
}
$user = $this->userRepository->getById(\Container::$request->post('user_id'));
if ($this->communityMemberRepository->getByCommunityAndUser($community, $user) !== null) {
return new JsonContent([
'error' => ['errorText' => 'This user is already a member of this community.']
]);
}
$communityMember = new CommunityMember();
$communityMember->setCommunity($community);
$communityMember->setUser($user);
@@ -125,6 +236,13 @@ class CommunityController implements IAuthenticationRequired
]);
}
\Container::$persistentDataManager->loadRelationsFromDb($communityMember, false, ['user']);
if ($this->transactionRepository->isAnyForUser($communityMember->getUser())) {
return new JsonContent([
'error' => ['errorText' => 'There are transactions where the member is payer or payee!']
]);
}
\Container::$persistentDataManager->deleteFromDb($communityMember);
return new JsonContent(['success' => true]);
@@ -185,6 +303,20 @@ class CommunityController implements IAuthenticationRequired
}
$currency = $this->currencyRepository->getById(\Container::$request->query('currency_id'));
if ($currency->getId() === $community->getMainCurrencyId()) {
return null;
}
if ($this->transactionRepository->isAnyForCurrency($currency)) {
return new JsonContent([
'error' => ['errorText' => 'There are transactions with this currency!']
]);
}
foreach ($this->currencyExchangeRatesRepository->getAllByCurrency($currency) as $currencyExchangeRate) {
\Container::$persistentDataManager->deleteFromDb($currencyExchangeRate);
}
\Container::$persistentDataManager->deleteFromDb($currency);
return new JsonContent(['success' => true]);
@@ -197,7 +329,7 @@ class CommunityController implements IAuthenticationRequired
}
$currency = $this->currencyRepository->getByCommunityAndCurrencyCode($community, \Container::$request->query('code'));
if ($currency === null) {
if ($currency === null || $currency->getId() === $community->getMainCurrencyId()) {
return null;
}
@@ -261,51 +393,9 @@ class CommunityController implements IAuthenticationRequired
return new JsonContent(['success' => true]);
}
public function saveCommunity(): ?IContent
{
$name = \Container::$request->post('name');
$currency = \Container::$request->post('currency');
if (strlen($name) === 0 || strlen($currency) === 0 || strlen($currency) > 3) {
return new JsonContent([
'error' => ['errorText' => 'Please fill all required fields!']
]);
}
$communityId = \Container::$request->query('communityId');
if ($communityId){
if (!$this->checkPermission($communityId, true, $community, $ownCommunityMember)) {
return null;
}
} else {
$community = new Community();
$community->setCreatedDate(new DateTime());
}
$community->setName($name);
$community->setCurrency($currency);
\Container::$persistentDataManager->saveToDb($community);
if (!$communityId) {
/**
* @var User $user
*/
$user = \Container::$request->user();
$communityMember = new CommunityMember();
$communityMember->setCommunity($community);
$communityMember->setUser($user);
$communityMember->setOwner(true);
\Container::$persistentDataManager->saveToDb($communityMember);
}
return new JsonContent([
'redirect' => ['target' => \Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()])]
]);
}
private function getMembers(Community $community): array
{
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true));
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true, ['user']));
usort($members, function($a, $b) {
return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
});
@@ -318,6 +408,9 @@ class CommunityController implements IAuthenticationRequired
usort($currencies, function($a, $b) {
return strnatcmp($a->getCode(), $b->getCode());
});
usort($currencies, function($a, $b) use ($community) {
return (int)($b->getId() === $community->getMainCurrencyId()) - (int)($a->getId() === $community->getMainCurrencyId());
});
return $currencies;
}
+2 -1
View File
@@ -1,5 +1,6 @@
<?php namespace RVR\Controller;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\User;
use RVR\Repository\CommunityMemberRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
@@ -27,7 +28,7 @@ class HomeController implements IAuthenticationRequired
*/
$user = \Container::$request->user();
$ownCommunityMembers = $this->communityMemberRepository->getAllByUser($user, true);
$ownCommunityMembers = $this->communityMemberRepository->getAllByUser($user, true, ['community']);
$communities = [];
foreach ($ownCommunityMembers as $ownCommunityMember) {
$communities[] = $ownCommunityMember->getCommunity();
+7 -7
View File
@@ -53,7 +53,7 @@ class LoginController
$oAuth = new GoogleOAuth(new Request());
$url = $oAuth->getDialogUrl(
$state,
\Container::$request->getBase() . \Container::$routeCollection->getRoute('login-google-action')->generateLink(),
\Container::$request->getBase() . \Container::$routeCollection->getRoute('login.google-action')->generateLink(),
$nonce
);
@@ -106,7 +106,7 @@ class LoginController
return new JsonContent([
'error' => [
'errorText' => 'No user found with the given email address / username or the given password is wrong. You can <a href="' .
\Container::$routeCollection->getRoute('password-requestReset')->generateLink(['email' => \Container::$request->post('email')]) . '" title="Request password reset">request password reset</a>!'
\Container::$routeCollection->getRoute('password.requestReset')->generateLink(['email' => \Container::$request->post('email')]) . '" title="Request password reset">request password reset</a>!'
]
]);
}
@@ -119,7 +119,7 @@ class LoginController
public function loginWithGoogle()
{
$defaultError = 'Authentication with Google failed. Please <a href="' . \Container::$routeCollection->getRoute('login-google')->generateLink() . '" title="Login with Google">try again</a>!';
$defaultError = 'Authentication with Google failed. Please <a href="' . \Container::$routeCollection->getRoute('login.google')->generateLink() . '" title="Login with Google">try again</a>!';
if (\Container::$request->user() !== null) {
$this->deleteRedirectUrl();
@@ -133,7 +133,7 @@ class LoginController
$oAuth = new GoogleOAuth(new Request());
$tokenData = $oAuth->getToken(
\Container::$request->query('code'),
\Container::$request->getBase() . \Container::$routeCollection->getRoute('login-google-action')->generateLink()
\Container::$request->getBase() . \Container::$routeCollection->getRoute('login.google-action')->generateLink()
);
if (!isset($tokenData['id_token'])) {
@@ -246,7 +246,7 @@ class LoginController
if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
return new JsonContent([
'redirect' => [
'target' => \Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token])
'target' => \Container::$routeCollection->getRoute('password.reset')->generateLink(['token' => $token])
]
]);
}
@@ -281,10 +281,10 @@ class LoginController
$mail = new Mail();
$mail->addRecipient($email);
$mail->setSubject($_ENV['APP_NAME'] . ' - Password reset');
$mail->setBodyFromTemplate('password-reset', [
$mail->setBodyFromTemplate('password.reset', [
'EMAIL' => $email,
'RESET_LINK' => \Container::$request->getBase() .
\Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token]),
\Container::$routeCollection->getRoute('password.reset')->generateLink(['token' => $token]),
'EXPIRES' => $expires->format('Y-m-d H:i T')
]);
$mail->send();
+4 -4
View File
@@ -104,10 +104,10 @@ class OAuthController
{
return new JsonContent([
'issuer' => $_ENV['APP_URL'],
'authorization_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth-auth')->generateLink(),
'token_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth-token')->generateLink(),
'userinfo_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth-userinfo')->generateLink(),
'jwks_uri' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth-certs')->generateLink(),
'authorization_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.auth')->generateLink(),
'token_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.token')->generateLink(),
'userinfo_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.userinfo')->generateLink(),
'jwks_uri' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.certs')->generateLink(),
'response_types_supported' =>
[
'code',
+163
View File
@@ -0,0 +1,163 @@
<?php namespace RVR\Controller;
use Container;
use DateTime;
use RVR\Finance\ExchangeRateCalculator;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\CommunityMember;
use RVR\PersistentData\Model\Transaction;
use RVR\PersistentData\Model\User;
use RVR\Repository\CommunityMemberRepository;
use RVR\Repository\CommunityRepository;
use RVR\Repository\CurrencyRepository;
use RVR\Repository\TransactionRepository;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Authorization\ISecured;
use SokoWeb\Interfaces\Response\IContent;
use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\JsonContent;
class TransactionController implements IAuthenticationRequired, ISecured
{
private CommunityRepository $communityRepository;
private CommunityMemberRepository $communityMemberRepository;
private CurrencyRepository $currencyRepository;
private TransactionRepository $transactionRepository;
private Community $community;
private CommunityMember $ownCommunityMember;
public function __construct()
{
$this->communityRepository = new CommunityRepository();
$this->communityMemberRepository = new CommunityMemberRepository();
$this->currencyRepository = new CurrencyRepository();
$this->transactionRepository = new TransactionRepository();
}
public function isAuthenticationRequired(): bool
{
return true;
}
public function authorize(): bool
{
$communityId = \Container::$request->query('communityId');
$this->community = $this->communityRepository->getById($communityId);
if ($this->community === null) {
return false;
}
/**
* @var User $user
*/
$user = \Container::$request->user();
$this->ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($this->community, $user);
if ($this->ownCommunityMember === null) {
return false;
}
return true;
}
public function getTransactions(): IContent
{
Container::$persistentDataManager->loadRelationsFromDb($this->community, true, ['main_currency']);
$exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
$itemsPerPage = 50;
$numberOfTransactions = $this->transactionRepository->countAllByCommunity($this->community);
$pages = ceil($numberOfTransactions / $itemsPerPage);
$currentPage = Container::$request->query('page') ?: 0;
$transactions = $this->transactionRepository->getPagedByCommunity(
$this->community,
$currentPage * $itemsPerPage,
$itemsPerPage,
true,
['currency', 'payer_user', 'payee_user']
);
return new HtmlContent('communities/transactions', [
'community' => $this->community,
'exchangeRateCalculator' => $exchangeRateCalculator,
'pages' => $pages,
'currentPage' => $currentPage,
'numberOfTransactions' => $numberOfTransactions,
'transactions' => $transactions
]);
}
public function getTransactionEdit(): ?IContent
{
$transactionId = Container::$request->query('transactionId');
if ($transactionId) {
$transaction = $this->transactionRepository->getById($transactionId);
if ($transaction === null) {
return null;
}
} else {
$transaction = null;
}
return new HtmlContent('communities/transaction_edit', [
'community' => $this->community,
'transaction' => $transaction,
'members' => $this->getMembers($this->community),
'currencies' => $this->getCurrencies($this->community)
]);
}
public function saveTransaction(): ?IContent
{
$transactionId = Container::$request->query('transactionId');
if ($transactionId) {
$transaction = $this->transactionRepository->getById($transactionId);
} else {
$transaction = new Transaction();
$transaction->setCommunity($this->community);
}
$transaction->setCurrencyId(Container::$request->post('currency_id'));
$transaction->setPayerUserId(Container::$request->post('payer_user_id'));
$transaction->setPayeeUserId(Container::$request->post('payee_user_id') ?: null);
$transaction->setDescription(Container::$request->post('description'));
$transaction->setSum(Container::$request->post('sum'));
$transaction->setTimeDate(new DateTime(Container::$request->post('time')));
Container::$persistentDataManager->saveToDb($transaction);
return new JsonContent(['success' => true]);
}
public function deleteTransaction(): IContent
{
$transaction = $this->transactionRepository->getById(Container::$request->query('transactionId'));
Container::$persistentDataManager->deleteFromDb($transaction);
return new JsonContent(['success' => true]);
}
private function getMembers(Community $community): array
{
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true, ['user']));
usort($members, function($a, $b) {
return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
});
return $members;
}
private function getCurrencies(Community $community): array
{
$currencies = iterator_to_array($this->currencyRepository->getAllByCommunity($community));
usort($currencies, function($a, $b) {
return strnatcmp($a->getCode(), $b->getCode());
});
usort($currencies, function($a, $b) use ($community) {
return (int)($b->getId() === $community->getMainCurrencyId()) - (int)($a->getId() === $community->getMainCurrencyId());
});
return $currencies;
}
}
+3
View File
@@ -6,6 +6,9 @@ class AuditLogger extends AuditLoggerBase
{
protected function getModifierId()
{
if (\Container::$request === null) {
return null;
}
$user = \Container::$request->user();
if ($user === null) {
return null;
+90
View File
@@ -0,0 +1,90 @@
<?php namespace RVR\Finance;
use RVR\PersistentData\Model\Community;
use RVR\Repository\CommunityMemberRepository;
use RVR\Repository\TransactionRepository;
class BalanceCalculator
{
private Community $community;
private TransactionRepository $transactionRepository;
private CommunityMemberRepository $communityMemberRepository;
private ExchangeRateCalculator $exchangeRateCalculator;
private array $members;
private array $payments;
public function __construct(Community $community)
{
$this->community = $community;
$this->transactionRepository = new TransactionRepository();
$this->communityMemberRepository = new CommunityMemberRepository();
$this->exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
}
public function calculate(): array
{
$this->collectMembers();
$this->createPaymentsMatrix();
$this->sumTransactions();
return $this->calculateActualDebts();
}
private function collectMembers(): void
{
$this->members = [];
foreach ($this->communityMemberRepository->getAllByCommunity($this->community, true, ['user']) as $member) {
$this->members[$member->getUserId()] = $member;
}
}
private function createPaymentsMatrix(): void
{
$this->payments = [];
foreach ($this->members as $payerUserId => $member) {
$this->payments[$payerUserId] = [];
foreach ($this->members as $payeeUserId => $member) {
$this->payments[$payerUserId][$payeeUserId] = 0;
}
}
}
private function sumTransactions(): void
{
$membersCount = count($this->members);
$transactions = $this->transactionRepository->getAllByCommunity($this->community, true, ['currency']);
foreach ($transactions as $transaction) {
$sum = $this->exchangeRateCalculator->calculate($transaction->getSum(), $transaction->getCurrency(), $transaction->getTimeDate());
if ($transaction->getPayeeUserId()) {
$this->payments[$transaction->getPayerUserId()][$transaction->getPayeeUserId()] += $sum;
} else {
foreach ($this->members as $payeeUserId => $member) {
$this->payments[$transaction->getPayerUserId()][$payeeUserId] += $sum / $membersCount;
}
}
}
}
private function calculateActualDebts(): array
{
$actualDebts = [];
foreach ($this->payments as $payerUserId => $paymentsOfPayer) {
foreach ($paymentsOfPayer as $payeeUserId => $sum) {
$actualDebt = $this->payments[$payeeUserId][$payerUserId] - $sum;
if (round($actualDebt, $this->community->getMainCurrency()->getRoundDigits()) > 0.0) {
$actualDebts[] = ['payer' => $this->members[$payerUserId], 'payee' => $this->members[$payeeUserId], 'amount' => $actualDebt];
}
}
}
return $actualDebts;
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php namespace RVR\Finance;
use DateTime;
use RVR\PersistentData\Model\Currency;
use RVR\PersistentData\Model\CurrencyExchangeRate;
use RVR\Repository\CurrencyExchangeRateRepository;
class ExchangeRateCalculator
{
private Currency $mainCurrency;
private CurrencyExchangeRateRepository $currencyExchangeRateRepository;
private array $exchangeRates = [];
public function __construct(Currency $mainCurrency)
{
$this->mainCurrency = $mainCurrency;
$this->currencyExchangeRateRepository = new CurrencyExchangeRateRepository();
}
public function calculate(float $sumInCurrency, Currency $currency, DateTime $time): float
{
if ($currency->getId() === $this->mainCurrency->getId()) {
return $sumInCurrency;
}
$currentExchangeRate = $this->getCurrentExchangeRate($currency, $time);
if ($currentExchangeRate === null) {
return 0.0;
}
return $sumInCurrency * $currentExchangeRate->getExchangeRate();
}
private function getCurrentExchangeRate(Currency $currency, DateTime $time): ?CurrencyExchangeRate
{
if (!isset($this->exchangeRates[$currency->getId()])) {
$this->exchangeRates[$currency->getId()] = iterator_to_array($this->currencyExchangeRateRepository->getAllByCurrency($currency));
}
$currentExchangeRate = null;
foreach ($this->exchangeRates[$currency->getId()] as $exchangeRate) {
if ($exchangeRate->getValidFrom() > $time) {
break;
}
$currentExchangeRate = $exchangeRate;
}
return $currentExchangeRate;
}
}
+27 -1
View File
@@ -7,12 +7,18 @@ class Community extends Model
{
protected static string $table = 'communities';
protected static array $fields = ['name', 'currency', 'created'];
protected static array $fields = ['name', 'currency', 'main_currency_id', 'created'];
protected static array $relations = ['main_currency' => Currency::class];
private string $name = '';
private string $currency = '';
private ?Currency $mainCurrency = null;
private ?int $mainCurrencyId = null;
private DateTime $created;
public function setName(string $name): void
@@ -25,6 +31,16 @@ class Community extends Model
$this->currency = $currency;
}
public function setMainCurrency(Currency $mainCurrency): void
{
$this->mainCurrency = $mainCurrency;
}
public function setMainCurrencyId(int $mainCurrencyId): void
{
$this->mainCurrencyId = $mainCurrencyId;
}
public function setCreatedDate(DateTime $created): void
{
$this->created = $created;
@@ -45,6 +61,16 @@ class Community extends Model
return $this->currency;
}
public function getMainCurrency(): ?Currency
{
return $this->mainCurrency;
}
public function getMainCurrencyId(): ?int
{
return $this->mainCurrencyId;
}
public function getCreatedDate(): DateTime
{
return $this->created;
+160
View File
@@ -0,0 +1,160 @@
<?php namespace RVR\PersistentData\Model;
use DateTime;
use SokoWeb\PersistentData\Model\Model;
class Transaction extends Model
{
protected static string $table = 'transactions';
protected static array $fields = ['community_id', 'currency_id', 'payer_user_id', 'payee_user_id', 'description', 'sum', 'time'];
protected static array $relations = [
'community' => Community::class,
'currency' => Currency::class,
'payer_user' => User::class,
'payee_user' => User::class
];
private ?Community $community = null;
private int $communityId;
private ?Currency $currency = null;
private int $currencyId;
private ?User $payerUser = null;
private int $payerUserId;
private ?User $payeeUser = null;
private ?int $payeeUserId = null;
private string $description = '';
private float $sum = 0.0;
private DateTime $time;
public function setCommunity(Community $community): void
{
$this->community = $community;
}
public function setCommunityId(int $communityId): void
{
$this->communityId = $communityId;
}
public function setCurrency(Currency $currency): void
{
$this->currency = $currency;
}
public function setCurrencyId(int $currencyId): void
{
$this->currencyId = $currencyId;
}
public function setPayerUser(User $payerUser): void
{
$this->payerUser = $payerUser;
}
public function setPayerUserId(int $payerUserId): void
{
$this->payerUserId = $payerUserId;
}
public function setPayeeUser(?User $payeeUser): void
{
$this->payeeUser = $payeeUser;
}
public function setPayeeUserId(?int $payeeUserId): void
{
$this->payeeUserId = $payeeUserId;
}
public function setDescription(string $description): void
{
$this->description = $description;
}
public function setSum(float $sum): void
{
$this->sum = $sum;
}
public function setTimeDate(DateTime $time): void
{
$this->time = $time;
}
public function setTime(string $time): void
{
$this->time = new DateTime($time);
}
public function getCommunity(): ?Community
{
return $this->community;
}
public function getCommunityId(): int
{
return $this->communityId;
}
public function getCurrency(): ?Currency
{
return $this->currency;
}
public function getCurrencyId(): int
{
return $this->currencyId;
}
public function getPayerUser(): ?User
{
return $this->payerUser;
}
public function getPayerUserId(): int
{
return $this->payerUserId;
}
public function getPayeeUser(): ?User
{
return $this->payeeUser;
}
public function getPayeeUserId(): ?int
{
return $this->payeeUserId;
}
public function getDescription(): string
{
return $this->description;
}
public function getSum(): float
{
return $this->sum;
}
public function getTimeDate(): DateTime
{
return $this->time;
}
public function getTime(): string
{
return $this->time->format('Y-m-d H:i:s');
}
}
+4 -4
View File
@@ -13,20 +13,20 @@ class CommunityMemberRepository
return \Container::$persistentDataManager->selectFromDbById($id, CommunityMember::class);
}
public function getAllByCommunity(Community $community, bool $useRelations = false): Generator
public function getAllByCommunity(Community $community, bool $useRelations = false, array $withRelations = []): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('community_id', '=', $community->getId());
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, CommunityMember::class, $useRelations);
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, CommunityMember::class, $useRelations, $withRelations);
}
public function getAllByUser(User $user, bool $useRelations = false): Generator
public function getAllByUser(User $user, bool $useRelations = false, array $withRelations = []): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $user->getId());
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, CommunityMember::class, $useRelations);
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, CommunityMember::class, $useRelations, $withRelations);
}
public function getByCommunityAndUser(Community $community, User $user) : ?CommunityMember
+72
View File
@@ -0,0 +1,72 @@
<?php namespace RVR\Repository;
use Container;
use Generator;
use RVR\PersistentData\Model\Community;
use RVR\PersistentData\Model\Currency;
use RVR\PersistentData\Model\Transaction;
use RVR\PersistentData\Model\User;
use SokoWeb\Database\Query\Select;
class TransactionRepository
{
public function getById(int $id): ?Transaction
{
return Container::$persistentDataManager->selectFromDbById($id, Transaction::class);
}
public function getAllByCommunity(Community $community, bool $useRelations = false, array $withRelations = []): Generator
{
$select = $this->selectAllByCommunity($community);
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
}
public function countAllByCommunity(Community $community): int
{
return $this->selectAllByCommunity($community)->count();
}
public function isAnyForUser(User $user): bool
{
$select = new Select(Container::$dbConnection, Transaction::getTable());
$select->where('payer_user_id', '=', $user->getId());
$select->orWhere('payee_user_id', '=', $user->getId());
$select->orWhere('payee_user_id', '=', null);
return $select->count() > 0;
}
public function isAnyCommon(): bool
{
$select = new Select(Container::$dbConnection, Transaction::getTable());
$select->where('payee_user_id', '=', null);
return $select->count() > 0;
}
public function isAnyForCurrency(Currency $currency): bool
{
$select = new Select(Container::$dbConnection, Transaction::getTable());
$select->where('currency_id', '=', $currency->getId());
return $select->count() > 0;
}
public function getPagedByCommunity(Community $community, int $start, int $limit, bool $useRelations = false, array $withRelations = []): Generator
{
$select = new Select(Container::$dbConnection);
$select->where('community_id', '=', $community->getId());
$select->orderBy('time', 'DESC');
$select->limit($limit, $start);
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
}
private function selectAllByCommunity(Community $community)
{
$select = new Select(Container::$dbConnection, Transaction::getTable());
$select->where('community_id', '=', $community->getId());
return $select;
}
}
+20 -12
View File
@@ -4,17 +4,17 @@
@section(main)
<h2>Account</h2>
<div class="box">
<div class="box compactBox">
<form id="accountForm" action="<?= Container::$routeCollection->getRoute('account-action')->generateLink() ?>" method="post" data-reload-on-success="true" data-observe-inputs="email,username,password_new,password_new_confirm,nickname,phone,id_number">
<?php if ($user['password'] !== null && $user['google_sub'] !== null): ?>
<p class="justify small">Please confirm your identity with your password or with Google to modify your account.</p>
<div class="inputWithButton">
<input type="password" class="text" name="password" placeholder="Current password" required minlength="6" autofocus><!--
<input type="password" class="text" name="password" autocomplete="current-password" required minlength="6" autofocus><!--
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
</div>
<?php elseif ($user['password'] !== null): ?>
<p class="justify small">Please confirm your identity with your password to modify your account.</p>
<input type="password" class="text big fullWidth" name="password" placeholder="Current password" required minlength="6" autofocus>
<input type="password" class="text big fullWidth" name="password" autocomplete="current-password" required minlength="6" autofocus>
<?php elseif ($user['google_sub'] !== null): ?>
<p class="justify small">Please confirm your identity with Google to modify your account.</p>
<div class="inputWithButton">
@@ -23,18 +23,26 @@
</div>
<?php endif; ?>
<hr>
<input type="email" class="text big fullWidth" name="email" placeholder="Email address" value="<?= $user['email'] ?>">
<input type="text" class="text big fullWidth marginTop" name="username" placeholder="Username" value="<?= $user['username'] ?>">
<input type="password" class="text big fullWidth marginTop" name="password_new" placeholder="New password" minlength="6">
<input type="password" class="text big fullWidth marginTop" name="password_new_confirm" placeholder="New password confirmation" minlength="6">
<p class="formLabel">Email address</p>
<input type="email" class="text big fullWidth" name="email" autocomplete="username" value="<?= $user['email'] ?>">
<p class="formLabel marginTop">Username</p>
<input type="text" class="text big fullWidth" name="username" value="<?= $user['username'] ?>">
<p class="formLabel marginTop">New password</p>
<input type="password" class="text big fullWidth" name="password_new" autocomplete="new-password" minlength="6">
<p class="formLabel marginTop">New password confirmation</p>
<input type="password" class="text big fullWidth" name="password_new_confirm" autocomplete="new-password" minlength="6">
<hr>
<input type="text" class="text big fullWidth marginTop" name="full_name" placeholder="Full name" value="<?= $user['full_name'] ?>" disabled>
<input type="text" class="text big fullWidth marginTop" name="nickname" placeholder="Nickname" value="<?= $user['nickname'] ?>">
<input type="text" class="text big fullWidth marginTop" name="phone" placeholder="Phone" value="<?= $user['phone'] ?>">
<input type="text" class="text big fullWidth marginTop" name="id_number" placeholder="ID number" value="<?= $user['id_number'] ?>">
<p class="formLabel marginTop">Full name</p>
<input type="text" class="text big fullWidth" name="full_name" value="<?= $user['full_name'] ?>" disabled>
<p class="formLabel marginTop marginTop">Nickname</p>
<input type="text" class="text big fullWidth" name="nickname" value="<?= $user['nickname'] ?>">
<p class="formLabel marginTop marginTop">Phone</p>
<input type="text" class="text big fullWidth" name="phone" value="<?= $user['phone'] ?>">
<p class="formLabel marginTop marginTop">ID number</p>
<input type="text" class="text big fullWidth" name="id_number" value="<?= $user['id_number'] ?>">
<p id="accountFormError" class="formError justify marginTop"></p>
<div class="right marginTop">
<button type="submit" name="submit" disabled>Save</button>
<button type="submit" name="submit_button" disabled>Save</button>
</div>
</form>
</div>
+1 -1
View File
@@ -5,7 +5,7 @@
@section(main)
<h2>Authenticate with Google</h2>
<?php if (!$success): ?>
<div class="box">
<div class="box compactBox">
<p class="error justify">
<?php if (isset($errorText)): ?>
<?= $errorText ?>
+32 -33
View File
@@ -1,34 +1,16 @@
@extends(templates/layout_normal)
@section(main)
<h2><?= $community->getName() ?> <span class="small">[<a href="<?= Container::$routeCollection->getRoute('community-edit')->generateLink(['communityId' => $community->getId()]) ?>">edit</a>]</span></h2>
<h2>
<?= $community->getName() ?>
<?php if ($editPermission): ?>
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.edit')->generateLink(['communityId' => $community->getId()]) ?>">edit</a>]</span>
<?php endif; ?>
</h2>
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communityId' => $community->getId()]) ?>">Settings</a></p>
<div class="gridContainer marginTop">
<div>
<h3 class="marginBottom">Members</h3>
<?php foreach ($members as $member): ?>
<p><?= $member->getUser()->getDisplayName() ?></p>
<?php endforeach; ?>
<?php if ($editPermission): ?>
<hr>
<p><a href="<?= Container::$routeCollection->getRoute('community-members')->generateLink(['communityId' => $community->getId()]) ?>">Edit members</a></p>
<?php endif; ?>
</div>
<div>
<h3 class="marginBottom">Currencies</h3>
<p>Main currency: <b><?= $community->getCurrency() ?></b></p>
<?php if (count($currencies) > 0): ?>
<p>Further currencies: <b>
<?php foreach ($currencies as $currency): ?>
<a href="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode()]) ?>"><?= $currency->getCode() ?></a>
<?php endforeach; ?>
</b></p>
<?php endif; ?>
<?php if ($editPermission): ?>
<hr>
<p><a href="<?= Container::$routeCollection->getRoute('community-currencies')->generateLink(['communityId' => $community->getId()]) ?>">Edit currencies</a></p>
<?php endif; ?>
</div>
<div>
<h3 class="marginBottom">Upcoming events</h3>
<?php if (count($upcomingEvents) > 0): ?>
@@ -40,19 +22,36 @@
<?php endif; ?>
</div>
<div>
<?php
$mainCurrencyCode = $community->getMainCurrency()->getCode();
$mainCurrencyRoundDigits = $community->getMainCurrency()->getRoundDigits();
?>
<h3 class="marginBottom">Finances</h3>
<table class="fullWidth">
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId()]) ?>">Transactions</a>
<table class="fullWidth marginTop">
<tr>
<td>You owe</td>
<td style="text-align: right; color: red;">0 <?= $community->getCurrency() ?></td>
<td class="bold">You owe</td>
<td class="mono red" style="text-align: right;"><?= number_format($debtBalance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr>
<?php foreach ($debtItems as $item): ?>
<tr>
<td class="small"><?= $item['payee']->getUser()->getDisplayName() ?></td>
<td class="small mono red" style="text-align: right;"><?= number_format($item['amount'], $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr>
<?php endforeach; ?>
<tr>
<td>You're owed</td>
<td style="text-align: right; color: green;">0 <?= $community->getCurrency() ?></td>
<td class="bold">You're owed</td>
<td class="mono green" style="text-align: right;"><?= number_format($outstandingBalance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr>
<?php foreach ($outstandingItems as $item): ?>
<tr>
<td class="small"><?= $item['payer']->getUser()->getDisplayName() ?></td>
<td class="small mono green" style="text-align: right;"><?= number_format($item['amount'], $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr>
<?php endforeach; ?>
<tr>
<td>Your balance</td>
<td style="text-align: right;">0 <?= $community->getCurrency() ?></td>
<td class="bold">Your balance</td>
<td class="mono <?= $balance < 0 ? 'red' : ($balance > 0 ? 'green' : '') ?>" style="text-align: right;;"><?= number_format($balance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
</tr>
</table>
</div>
+18 -12
View File
@@ -1,12 +1,16 @@
@extends(templates/layout_normal)
@section(main)
<h2><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Edit currencies</h2>
<div class="box">
<h2>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
<a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communityId' => $community->getId()]) ?>">Settings</a> »
Edit currencies
</h2>
<div class="box compactBox">
<table class="fullWidth">
<thead>
<tr>
<th style="width: calc(100% / 3); text-align: left;">Currency</th>
<th style="width: calc(100% / 3); text-align: left;">Code</th>
<th style="width: calc(100% / 3); text-align: left;">Round digits</th>
<th style="width: calc(100% / 3);"></th>
</tr>
@@ -14,29 +18,31 @@
<?php foreach ($currencies as $currency): ?>
<tr>
<td>
<form id="editCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currencies-edit')->generateLink(['communityId' => $community->getId(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
<form id="deleteCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currencies-delete')->generateLink(['communityId' => $community->getId(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<form id="editCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencies.edit-action')->generateLink(['communityId' => $community->getId(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
<form id="deleteCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencies.delete-action')->generateLink(['communityId' => $community->getId(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<input type="text" form="editCurrency_<?= $currency->getId() ?>" class="text fullWidth" name="code" value="<?= $currency->getCode() ?>" maxlength="3" required>
</td>
<td>
<input type="number" form="editCurrency_<?= $currency->getId() ?>" class="text fullWidth" name="round_digits" value="<?= $currency->getRoundDigits() ?>" min="0" max="9" required>
</td>
<td style="text-align: right;">
<button type="submit" form="editCurrency_<?= $currency->getId() ?>" name="submit" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteCurrency_<?= $currency->getId() ?>" class="small red">Delete</button>
<td style="text-align: right; font-size: 0;">
<button type="submit" form="editCurrency_<?= $currency->getId() ?>" name="submit_button" class="small" disabled>Save</button>
<?php if ($currency->getId() !== $community->getMainCurrencyId()): ?>
<button type="submit" form="deleteCurrency_<?= $currency->getId() ?>" name="submit_button" data-confirmation="Are you sure you want to delete this currency?" class="small red marginLeft">Delete</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<tr>
<td>
<form id="newCurrency" action="<?= Container::$routeCollection->getRoute('community-currencies-new')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
<input type="text" form="newCurrency" class="text fullWidth" name="code" placeholder="Currency" maxlength="3" required>
<form id="newCurrency" action="<?= Container::$routeCollection->getRoute('community.currencies.new-action')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
<input type="text" form="newCurrency" class="text fullWidth" name="code" maxlength="3" required>
</td>
<td>
<input type="number" form="newCurrency" class="text fullWidth" name="round_digits" placeholder="Round digits" min="0" max="9" required>
<input type="number" form="newCurrency" class="text fullWidth" name="round_digits" min="0" max="9" required>
</td>
<td style="text-align: right;">
<button type="submit" form="newCurrency" name="submit" class="small" disabled>Add</button>
<button type="submit" form="newCurrency" name="submit_button" class="small" disabled>Add</button>
</td>
</tr>
</table>
+14 -7
View File
@@ -3,23 +3,30 @@
@section(main)
<h2>
<?php if (isset($community)): ?>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Edit
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
Edit
<?php else: ?>
New community
<?php endif; ?>
</h2>
<div class="box">
<div class="box compactBox">
<?php
$formAction = isset($community) ?
Container::$routeCollection->getRoute('community-edit-action')->generateLink(['communityId' => $community->getId()]) :
Container::$routeCollection->getRoute('community-new-action')->generateLink();
Container::$routeCollection->getRoute('community.edit-action')->generateLink(['communityId' => $community->getId()]) :
Container::$routeCollection->getRoute('community.new-action')->generateLink();
?>
<form id="communityForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="true">
<input type="text" class="text big fullWidth" name="name" placeholder="Name" value="<?= isset($community) ? $community->getName() : '' ?>" required>
<input type="text" class="text big fullWidth marginTop" name="currency" value="<?= isset($community) ? $community->getCurrency() : '' ?>" placeholder="Default currency" maxlength="3" required>
<p class="formLabel">Name</p>
<input type="text" class="text big fullWidth" name="name" value="<?= isset($community) ? $community->getName() : '' ?>" required>
<?php if (!isset($community)): ?>
<p class="formLabel marginTop">Main currency code</p>
<input type="text" class="text big fullWidth" name="main_currency_code" maxlength="3" required>
<p class="formLabel marginTop">Main currency round digits</p>
<input type="number" class="text big fullWidth" name="main_currency_round_digits" min="0" max="9" required>
<?php endif; ?>
<p id="communityFormError" class="formError justify marginTop"></p>
<div class="right marginTop">
<button type="submit" name="submit"><?= isset($community) ? 'Save' : 'Create' ?></button>
<button type="submit" name="submit_button"><?= isset($community) ? 'Save' : 'Create' ?></button>
</div>
</form>
</div>
+15 -11
View File
@@ -1,16 +1,20 @@
@css(node_modules/choices.js/public/assets/styles/choices.min.css)
@js(node_modules/choices.js/public/assets/scripts/choices.js)
@css(/static/node_modules/tom-select/dist/css/tom-select.min.css)
@js(/static/node_modules/tom-select/dist/js/tom-select.base.min.js)
@js(js/communities/community_members.js)
@extends(templates/layout_normal)
@section(main)
<h2><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Edit members</h2>
<div class="box">
<h2>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
<a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communityId' => $community->getId()]) ?>">Settings</a> »
Edit members
</h2>
<div class="box compactBox">
<table class="fullWidth">
<thead>
<tr>
<th style="width: 50%;"></th>
<th style="width: 50%;">User</th>
<th style="width: 25%; text-align: center;">Owner</th>
<th style="width: 25%;"></th>
</tr>
@@ -19,8 +23,8 @@
<?php $editable = $member->getUserId() !== Container::$request->user()->getUniqueId(); ?>
<tr>
<td>
<form id="editMember_<?= $member->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-members-edit')->generateLink(['communityId' => $community->getId(), 'community_member_id' => $member->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="owner"></form>
<form id="deleteMember_<?= $member->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-members-delete')->generateLink(['communityId' => $community->getId(), 'community_member_id' => $member->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<form id="editMember_<?= $member->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.members.edit-action')->generateLink(['communityId' => $community->getId(), 'community_member_id' => $member->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="owner"></form>
<form id="deleteMember_<?= $member->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.members.delete-action')->generateLink(['communityId' => $community->getId(), 'community_member_id' => $member->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<?= $member->getUser()->getDisplayName() ?>
</td>
<td style="text-align: center;">
@@ -28,22 +32,22 @@
</td>
<td style="text-align: right;">
<?php if ($editable): ?>
<button type="submit" form="editMember_<?= $member->getId() ?>" name="submit" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteMember_<?= $member->getId() ?>" class="small red">Delete</button>
<button type="submit" form="editMember_<?= $member->getId() ?>" name="submit_button" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteMember_<?= $member->getId() ?>" name="submit_button" data-confirmation="Are you sure you want to delete this member?" class="small red">Delete</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<tr>
<td>
<form id="newMember" action="<?= Container::$routeCollection->getRoute('community-members-new')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="user_id"></form>
<form id="newMember" action="<?= Container::$routeCollection->getRoute('community.members.new-action')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="user_id"></form>
<select type="text" form="newMember" name="user_id">
</td>
<td style="text-align: center;">
<input type="checkbox" form="newMember" name="owner" />
</td>
<td style="text-align: right;">
<button type="submit" form="newMember" name="submit" class="small" disabled>Add</button>
<button type="submit" form="newMember" name="submit_button" class="small" disabled>Add</button>
</td>
</tr>
</table>
+39
View File
@@ -0,0 +1,39 @@
@extends(templates/layout_normal)
@section(main)
<h2>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
Settings
</h2>
<div class="gridContainer marginTop">
<div>
<h3 class="marginBottom">
Members
<?php if ($editPermission): ?>
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.members')->generateLink(['communityId' => $community->getId()]) ?>">edit</a>]</span>
<?php endif; ?>
</h3>
<?php foreach ($members as $member): ?>
<p><?= $member->getUser()->getDisplayName() ?></p>
<?php endforeach; ?>
</div>
<div>
<h3 class="marginBottom">
Currencies
<?php if ($editPermission): ?>
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.currencies')->generateLink(['communityId' => $community->getId()]) ?>">edit</a>]</span>
<?php endif; ?>
</h3>
<?php foreach ($currencies as $currency): ?>
<p>
<?php if ($currency->getId() === $community->getMainCurrencyId()): ?>
<b><?= $currency->getCode() ?></b>
<?php else: ?>
<a href="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode()]) ?>"><?= $currency->getCode() ?></a>
<?php endif; ?>
</p>
<?php endforeach; ?>
</div>
</div>
@endsection
+14 -10
View File
@@ -1,8 +1,12 @@
@extends(templates/layout_normal)
@section(main)
<h2><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> - Exchange rates for <?= $currency->getCode() ?></h2>
<div class="box">
<h2>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
<a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communityId' => $community->getId()]) ?>">Settings</a> »
Exchange rates for <?= $currency->getCode() ?>
</h2>
<div class="box compactBox">
<table class="fullWidth">
<thead>
<tr>
@@ -20,16 +24,16 @@
<tr>
<?php if ($editPermission): ?>
<td>
<form id="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates-edit')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
<form id="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates-delete')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<form id="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates.edit-action')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
<form id="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates.delete-action')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true"></form>
<input type="number" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" class="text fullWidth" name="exchange_rate" value="<?= $currencyExchangeRate->getExchangeRate() ?>" min="0" step="0.000000001" required>
</td>
<td>
<input type="datetime-local" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" class="text fullWidth" name="valid_from" value="<?= $currencyExchangeRate->getValidFromDate()->format('Y-m-d\TH:i') ?>" required>
</td>
<td style="text-align: right;">
<button type="submit" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" name="submit" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" class="small red">Delete</button>
<button type="submit" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" name="submit_button" class="small marginRight" disabled>Save</button><!--
--><button type="submit" form="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" name="submit_button" data-confirmation="Are you sure you want to delete this exchange rate?" class="small red">Delete</button>
</td>
<?php else: ?>
<td><?= $currencyExchangeRate->getExchangeRate() ?></td>
@@ -40,14 +44,14 @@
<?php if ($editPermission): ?>
<tr>
<td>
<form id="newExchangeRate" action="<?= Container::$routeCollection->getRoute('community-currency-exchange-rates-new')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
<input type="number" form="newExchangeRate" class="text fullWidth" name="exchange_rate" placeholder="Exchange rate" min="0" step="0.000000001" required>
<form id="newExchangeRate" action="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates.new-action')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
<input type="number" form="newExchangeRate" class="text fullWidth" name="exchange_rate" min="0" step="0.000000001" required>
</td>
<td>
<input type="datetime-local" form="newExchangeRate" class="text fullWidth" name="valid_from" placeholder="Valid from" required>
<input type="datetime-local" form="newExchangeRate" class="text fullWidth" name="valid_from" required>
</td>
<td style="text-align: right;">
<button type="submit" form="newExchangeRate" name="submit" class="small" disabled>Add</button>
<button type="submit" form="newExchangeRate" name="submit_button" class="small" disabled>Add</button>
</td>
</tr>
<?php endif; ?>
+59
View File
@@ -0,0 +1,59 @@
@extends(templates/layout_normal)
@section(main)
<h2>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId()]) ?>">Transactions</a> »
<?php if (isset($transaction)): ?>
Edit transaction
<?php else: ?>
New transaction
<?php endif; ?>
</h2>
<div class="box compactBox">
<?php
$formAction = isset($transaction) ?
Container::$routeCollection->getRoute('community.transactions.edit-action')->generateLink(['communityId' => $community->getId(), 'transactionId' => $transaction->getId()]) :
Container::$routeCollection->getRoute('community.transactions.new-action')->generateLink(['communityId' => $community->getId()]);
?>
<form id="transactionForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId()]) ?>">
<p class="formLabel">Payer</p>
<select class="big fullWidth" name="payer_user_id" required>
<option value="" hidden></option>
<?php foreach ($members as $member): ?>
<option value="<?= $member->getUser()->getId() ?>" <?= isset($transaction) && $transaction->getPayerUserId() === $member->getUser()->getId() ? 'selected' : '' ?>><?= $member->getUser()->getDisplayName() ?></option>
<?php endforeach; ?>
</select>
<p class="formLabel marginTop">Payee</p>
<select class="big fullWidth" name="payee_user_id">
<option value="">[common]</option>
<?php foreach ($members as $member): ?>
<option value="<?= $member->getUser()->getId() ?>" <?= isset($transaction) && $transaction->getPayeeUserId() === $member->getUser()->getId() ? 'selected' : '' ?>><?= $member->getUser()->getDisplayName() ?></option>
<?php endforeach; ?>
</select>
<p class="formLabel marginTop">Description</p>
<input type="text" class="text big fullWidth" name="description" value="<?= isset($transaction) ? $transaction->getDescription() : '' ?>" required>
<p class="formLabel marginTop">Sum</p>
<input type="number" class="text big fullWidth" name="sum" value="<?= isset($transaction) ? $transaction->getSum() : '' ?>" min="0" step="0.000000001" required>
<p class="formLabel marginTop">Currency</p>
<select class="big fullWidth" name="currency_id" required>
<option value="" hidden></option>
<?php foreach ($currencies as $currency): ?>
<option value="<?= $currency->getId() ?>" <?= isset($transaction) && $transaction->getCurrencyId() === $currency->getId() ? 'selected' : '' ?>><?= $currency->getCode() ?></option>
<?php endforeach; ?>
</select>
<p class="formLabel marginTop">Time</p>
<input type="datetime-local" class="text big fullWidth" name="time" value="<?= isset($transaction) ? $transaction->getTimeDate()->format('Y-m-d\TH:i') : (new DateTime())->format('Y-m-d\TH:i') ?>" required>
<p class="formError justify marginTop"></p>
<div class="right marginTop" style="font-size: 0;">
<button type="submit" name="submit_button"><?= isset($transaction) ? 'Save' : 'Create' ?></button>
<?php if (isset($transaction)): ?>
<button type="submit" form="deleteTransaction" name="submit_button" data-confirmation="Are you sure you want to delete this transaction?" class="red marginRight">Delete</button>
<?php endif; ?>
</div>
</form>
<?php if (isset($transaction)): ?>
<form id="deleteTransaction" action="<?= Container::$routeCollection->getRoute('community.transactions.delete-action')->generateLink(['communityId' => $community->getId(), 'transactionId' => $transaction->getId()]) ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId()]) ?>"></form>
<?php endif; ?>
</div>
@endsection
+68
View File
@@ -0,0 +1,68 @@
@extends(templates/layout_normal)
@section(main)
<h2>
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
Transactions
</h2>
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communityId' => $community->getId()]) ?>">New transaction</a></p>
<?php if ($numberOfTransactions > 0): ?>
<?php if ($pages > 1): ?>
<p class="paginateContainer marginTop">
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => 0]) ?>">«</a>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => max(0, $currentPage - 1)]) ?>">‹</a>
<?php for ($i = 0; $i < $pages; $i++): ?>
<?php if ($currentPage == $i): ?>
<span class="selected"><?= $i + 1 ?></span>
<?php else: ?>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => $i]) ?>"><?= $i + 1 ?></a>
<?php endif; ?>
<?php endfor; ?>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => min($pages - 1, $currentPage + 1)]) ?>">›</a>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => $pages - 1]) ?>">»</a>
</p>
<?php endif; ?>
<?php foreach ($transactions as $transaction): ?>
<a class="block" href="<?= Container::$routeCollection->getRoute('community.transactions.edit')->generateLink(['communityId' => $community->getId(), 'transactionId' => $transaction->getId()]) ?>">
<div class="box transaction">
<div>
<p style="font-weight: bold;"><?= $transaction->getDescription() ?></p>
<p class="small"><?= $transaction->getPayerUser()->getDisplayName() ?> ► <?= $transaction->getPayeeUser() ? $transaction->getPayeeUser()->getDisplayName() : '[common]' ?></p>
<p class="small"><?= $transaction->getTimeDate()->format('Y-m-d H:i') ?></p>
</div>
<div style="text-align: right;">
<h3><?= number_format($exchangeRateCalculator->calculate($transaction->getSum(), $transaction->getCurrency(), $transaction->getTimeDate()), $community->getMainCurrency()->getRoundDigits()) ?> <?= $community->getMainCurrency()->getCode() ?></h3>
<?php if ($community->getMainCurrencyId() !== $transaction->getCurrencyId()): ?>
<p style="color: #8e8e8e; font-weight: bold;"><?= number_format($transaction->getSum(), $transaction->getCurrency()->getRoundDigits()) ?> <?= $transaction->getCurrency()->getCode() ?></p>
<?php endif; ?>
</div>
</div>
</a>
<?php endforeach; ?>
<?php if ($pages > 1): ?>
<p class="paginateContainer marginTop">
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => 0]) ?>">«</a>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => max(0, $currentPage - 1)]) ?>">‹</a>
<?php for ($i = 0; $i < $pages; $i++): ?>
<?php if ($currentPage == $i): ?>
<span class="selected"><?= $i + 1 ?></span>
<?php else: ?>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => $i]) ?>"><?= $i + 1 ?></a>
<?php endif; ?>
<?php endfor; ?>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => min($pages - 1, $currentPage + 1)]) ?>">›</a>
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => $pages - 1]) ?>">»</a>
</p>
<?php endif; ?>
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communityId' => $community->getId()]) ?>">New transaction</a></p>
<?php else: ?>
<div class="box">
<p>There are no transactions yet.</p>
</div>
<?php endif; ?>
@endsection
+4 -3
View File
@@ -3,7 +3,10 @@
@section(main)
<div class="gridContainer">
<div>
<h3 class="marginBottom">Communities</h3>
<h3 class="marginBottom">
Communities
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.new')->generateLink() ?>">new</a>]</span>
</h3>
<?php if (count($communities) > 0): ?>
<?php foreach ($communities as $community): ?>
<p><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a></p>
@@ -11,8 +14,6 @@
<?php else: ?>
<p>You have no community.</p>
<?php endif; ?>
<hr>
<p><a href="<?= Container::$routeCollection->getRoute('community-new')->generateLink() ?>">New community</a></p>
</div>
<div>
<h3 class="marginBottom">Upcoming events</h3>
+1 -1
View File
@@ -2,7 +2,7 @@
@section(main)
<h2>Login up with Google</h2>
<div class="box">
<div class="box compactBox">
<p class="error justify"><?= $error ?></p>
</div>
@endsection
+7 -5
View File
@@ -2,18 +2,20 @@
@section(main)
<h2>Login</h2>
<div class="box">
<div class="box compactBox">
<form id="loginForm" action="<?= Container::$routeCollection->getRoute('login-action')->generateLink() ?>" method="post" data-redirect-on-success="<?= $redirectUrl ?>">
<input type="text" class="text big fullWidth" name="email" placeholder="Email address / Username" required autofocus>
<input type="password" class="text big fullWidth marginTop" name="password" placeholder="Password" required minlength="6">
<p class="formLabel">Email address / Username</p>
<input type="text" class="text big fullWidth" name="email" autocomplete="username" required autofocus>
<p class="formLabel marginTop">Password</p>
<input type="password" class="text big fullWidth" name="password" autocomplete="current-password" required minlength="6">
<p id="loginFormError" class="formError justify marginTop"></p>
<div class="right marginTop">
<button type="submit">Login</button>
</div>
<p class="center marginTop"><a href="<?= Container::$routeCollection->getRoute('password-requestReset')->generateLink() ?>" title="Request password reset">Forgot your password?</a></p>
<p class="center marginTop"><a href="<?= Container::$routeCollection->getRoute('password.requestReset')->generateLink() ?>" title="Request password reset">Forgot your password?</a></p>
<hr>
<div class="center">
<a class="button yellow" href="<?= Container::$routeCollection->getRoute('login-google')->generateLink() ?>" title="Login with Google">Login with Google</a>
<a class="button yellow" href="<?= Container::$routeCollection->getRoute('login.google')->generateLink() ?>" title="Login with Google">Login with Google</a>
</div>
</form>
</div>
+4 -3
View File
@@ -4,9 +4,10 @@
@section(main)
<h2>Request password reset</h2>
<div class="box">
<form id="passwordResetForm" action="<?= Container::$routeCollection->getRoute('password-requestReset-action')->generateLink() ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('password-requestReset.success')->generateLink() ?>">
<input type="email" class="text big fullWidth" name="email" placeholder="Email address" value="<?= isset($email) ? $email : '' ?>" required autofocus>
<div class="box compactBox">
<form id="passwordResetForm" action="<?= Container::$routeCollection->getRoute('password.requestReset-action')->generateLink() ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('password.requestReset.success')->generateLink() ?>">
<p class="formLabel">Email address</p>
<input type="email" class="text big fullWidth" name="email" value="<?= isset($email) ? $email : '' ?>" required autofocus>
<?php if (!empty($_ENV['RECAPTCHA_SITEKEY'])): ?>
<div class="marginTop">
<div class="g-recaptcha" data-sitekey="<?= $_ENV['RECAPTCHA_SITEKEY'] ?>"></div>
@@ -2,7 +2,7 @@
@section(main)
<h2>Request password reset</h2>
<div class="box">
<div class="box compactBox">
<p class="justify">Password reset was successfully requested. Please check your email and click on the link to reset your password!</p>
</div>
@endsection
+9 -6
View File
@@ -2,19 +2,22 @@
@section(main)
<h2>Reset password</h2>
<div class="box">
<div class="box compactBox">
<?php if ($success) : ?>
<form id="resetPasswordForm" action="<?= Container::$routeCollection->getRoute('password-reset.action')->generateLink(['token' => $token]) ?>"" method="post" data-redirect-on-success="<?= $redirectUrl ?>">
<input type="email" class="text big fullWidth" name="email" placeholder="Email address" value="<?= $email ?>" disabled>
<input type="password" class="text big fullWidth marginTop" name="password" placeholder="Password" required minlength="6" autofocus>
<input type="password" class="text big fullWidth marginTop" name="password_confirm" placeholder="Password confirmation" required minlength="6">
<form id="resetPasswordForm" action="<?= Container::$routeCollection->getRoute('password.reset-action')->generateLink(['token' => $token]) ?>"" method="post" data-redirect-on-success="<?= $redirectUrl ?>">
<p class="formLabel">Email address</p>
<input type="text" class="text big fullWidth" name="email" autocomplete="username" value="<?= $email ?>" disabled>
<p class="formLabel marginTop">Password</p>
<input type="password" class="text big fullWidth" name="password" autocomplete="new-password" required minlength="6" autofocus>
<p class="formLabel marginTop">Password confirmation</p>
<input type="password" class="text big fullWidth" name="password_confirm" autocomplete="new-password" required minlength="6">
<p id="resetPasswordFormError" class="formError justify marginTop"></p>
<div class="right">
<button class="marginTop" type="submit">Reset password</button>
</div>
</form>
<?php else: ?>
<p class="error justify">Confirming your identity failed. Please check the link you entered, or retry <a href="<?= Container::$routeCollection->getRoute('password-requestReset')->generateLink() ?>" title="Request password reset">requesting password reset</a>!</p>
<p class="error justify">Confirming your identity failed. Please check the link you entered, or retry <a href="<?= Container::$routeCollection->getRoute('password.requestReset')->generateLink() ?>" title="Request password reset">requesting password reset</a>!</p>
<?php endif; ?>
</div>
@endsection
+1 -1
View File
@@ -2,7 +2,7 @@
@section(main)
<h2>OAuth error</h2>
<div class="box">
<div class="box compactBox">
<p class="error justify"><?= $error ?></p>
</div>
@endsection
+4 -3
View File
@@ -9,13 +9,14 @@
<?php if (preg_match('/^(http(s)?:)?\/\//', $_ENV['STATIC_ROOT']) === 1): ?>
<link href="<?= $_ENV['STATIC_ROOT'] ?>" rel="preconnect">
<?php endif; ?>
<link href="https://fonts.gstatic.com" rel="preconnect">
<link href="https://fonts.googleapis.com" rel="preconnect">
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
<link href="https://www.googletagmanager.com" rel="preconnect">
<link href="https://www.google-analytics.com" rel="preconnect">
<?php endif; ?>
<link href="https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&amp;display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Oxygen+Mono:wght@400&amp;display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&amp;display=block" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Oxygen+Mono:wght@400&amp;display=block" rel="stylesheet">
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/rvr.css?rev=<?= REVISION ?>" rel="stylesheet">
@yields('externalCss')
@yields('inlineCss')
+46 -42
View File
@@ -4,6 +4,7 @@ use SokoWeb\Response\HttpResponse;
use SokoWeb\Routing\RouteCollection;
use SokoWeb\Session\DatabaseSessionHandler;
use SokoWeb\Request\Request;
use SokoWeb\Request\Session;
use RVR\Controller\HomeController;
use RVR\Controller\LoginController;
use RVR\Controller\OAuthAuthController;
@@ -11,6 +12,7 @@ use RVR\Controller\OAuthController;
use RVR\Controller\UserController;
use RVR\Controller\UserSearchController;
use RVR\Controller\CommunityController;
use RVR\Controller\TransactionController;
use RVR\Repository\UserRepository;
require 'app.php';
@@ -26,26 +28,25 @@ if (!empty($_ENV['DEV'])) {
Container::$routeCollection = new RouteCollection();
Container::$routeCollection->get('home', '', [HomeController::class, 'getHome']);
Container::$routeCollection->get('startSession', 'startSession.json', [HomeController::class, 'startSession']);
Container::$routeCollection->group('login', function (RouteCollection $routeCollection) {
$routeCollection->get('login', '', [LoginController::class, 'getLoginForm']);
$routeCollection->post('login-action', '', [LoginController::class, 'login']);
$routeCollection->get('login-google', 'google', [LoginController::class, 'getGoogleLoginRedirect']);
$routeCollection->get('login-google-action', 'google/code', [LoginController::class, 'loginWithGoogle']);
$routeCollection->get('login.google', 'google', [LoginController::class, 'getGoogleLoginRedirect']);
$routeCollection->get('login.google-action', 'google/code', [LoginController::class, 'loginWithGoogle']);
});
Container::$routeCollection->group('oauth', function (RouteCollection $routeCollection) {
$routeCollection->get('oauth-auth', 'auth', [OAuthAuthController::class, 'auth']);
$routeCollection->post('oauth-token', 'token', [OAuthController::class, 'getToken']);
$routeCollection->get('oauth-userinfo', 'userinfo', [OAuthController::class, 'getUserInfo']);
$routeCollection->get('oauth-config', '.well-known/openid-configuration', [OAuthController::class, 'getConfig']);
$routeCollection->get('oauth-certs', 'certs', [OAuthController::class, 'getCerts']);
$routeCollection->get('oauth.auth', 'auth', [OAuthAuthController::class, 'auth']);
$routeCollection->post('oauth.token', 'token', [OAuthController::class, 'getToken']);
$routeCollection->get('oauth.userinfo', 'userinfo', [OAuthController::class, 'getUserInfo']);
$routeCollection->get('oauth.config', '.well-known/openid-configuration', [OAuthController::class, 'getConfig']);
$routeCollection->get('oauth.certs', 'certs', [OAuthController::class, 'getCerts']);
});
Container::$routeCollection->group('password', function (RouteCollection $routeCollection) {
$routeCollection->get('password-requestReset', 'requestReset', [LoginController::class, 'getRequestPasswordResetForm']);
$routeCollection->post('password-requestReset-action', 'requestReset', [LoginController::class, 'requestPasswordReset']);
$routeCollection->get('password-requestReset.success', 'requestReset/success', [LoginController::class, 'getRequestPasswordResetSuccess']);
$routeCollection->get('password-reset', 'reset/{token}', [LoginController::class, 'getResetPasswordForm']);
$routeCollection->post('password-reset.action', 'reset/{token}', [LoginController::class, 'resetPassword']);
$routeCollection->get('password.requestReset', 'requestReset', [LoginController::class, 'getRequestPasswordResetForm']);
$routeCollection->post('password.requestReset-action', 'requestReset', [LoginController::class, 'requestPasswordReset']);
$routeCollection->get('password.requestReset.success', 'requestReset/success', [LoginController::class, 'getRequestPasswordResetSuccess']);
$routeCollection->get('password.reset', 'reset/{token}', [LoginController::class, 'getResetPasswordForm']);
$routeCollection->post('password.reset-action', 'reset/{token}', [LoginController::class, 'resetPassword']);
});
Container::$routeCollection->get('logout', 'logout', [LoginController::class, 'logout']);
Container::$routeCollection->group('account', function (RouteCollection $routeCollection) {
@@ -56,56 +57,59 @@ Container::$routeCollection->group('account', function (RouteCollection $routeCo
});
Container::$routeCollection->get('searchUser', 'searchUser', [UserSearchController::class, 'searchUser']);
Container::$routeCollection->group('communities', function (RouteCollection $routeCollection) {
$routeCollection->get('community-new', 'new', [CommunityController::class, 'getCommunityNew']);
$routeCollection->post('community-new-action', 'new', [CommunityController::class, 'saveCommunity']);
$routeCollection->get('community.new', 'new', [CommunityController::class, 'getCommunityNew']);
$routeCollection->post('community.new-action', 'new', [CommunityController::class, 'saveCommunity']);
$routeCollection->group('{communityId}', function (RouteCollection $routeCollection) {
$routeCollection->get('community', '', [CommunityController::class, 'getCommunityHome']);
$routeCollection->get('community-edit', 'edit', [CommunityController::class, 'getCommunityEdit']);
$routeCollection->post('community-edit-action', 'edit', [CommunityController::class, 'saveCommunity']);
$routeCollection->get('community-members', 'members', [CommunityController::class, 'getMembersEdit']);
$routeCollection->post('community-members-new', 'newMember', [CommunityController::class, 'saveMember']);
$routeCollection->post('community-members-edit', 'editMember', [CommunityController::class, 'saveMember']);
$routeCollection->post('community-members-delete', 'deleteMember', [CommunityController::class, 'deleteMember']);
$routeCollection->get('community-currencies', 'currencies', [CommunityController::class, 'getCurrenciesEdit']);
$routeCollection->post('community-currencies-new', 'newCurrency', [CommunityController::class, 'saveCurrency']);
$routeCollection->post('community-currencies-edit', 'editCurrency', [CommunityController::class, 'saveCurrency']);
$routeCollection->post('community-currencies-delete', 'deleteCurrency', [CommunityController::class, 'deleteCurrency']);
$routeCollection->get('community.settings', 'settings', [CommunityController::class, 'getCommunitySettings']);
$routeCollection->get('community.edit', 'edit', [CommunityController::class, 'getCommunityEdit']);
$routeCollection->post('community.edit-action', 'edit', [CommunityController::class, 'saveCommunity']);
$routeCollection->get('community.members', 'members', [CommunityController::class, 'getMembersEdit']);
$routeCollection->post('community.members.new-action', 'members/new', [CommunityController::class, 'saveMember']);
$routeCollection->post('community.members.edit-action', 'members/edit', [CommunityController::class, 'saveMember']);
$routeCollection->post('community.members.delete-action', 'members/delete', [CommunityController::class, 'deleteMember']);
$routeCollection->get('community.currencies', 'currencies', [CommunityController::class, 'getCurrenciesEdit']);
$routeCollection->post('community.currencies.new-action', 'currencies/new', [CommunityController::class, 'saveCurrency']);
$routeCollection->post('community.currencies.edit-action', 'currencies/edit', [CommunityController::class, 'saveCurrency']);
$routeCollection->post('community.currencies.delete-action', 'currencies/delete', [CommunityController::class, 'deleteCurrency']);
$routeCollection->group('currencyExchangeRates', function (RouteCollection $routeCollection) {
$routeCollection->get('community-currency-exchange-rates', '{code}', [CommunityController::class, 'getCurrencyExchangeRates']);
$routeCollection->post('community-currency-exchange-rates-new', '{code}/new', [CommunityController::class, 'saveCurrencyExchangeRate']);
$routeCollection->post('community-currency-exchange-rates-edit', '{code}/edit', [CommunityController::class, 'saveCurrencyExchangeRate']);
$routeCollection->post('community-currency-exchange-rates-delete', '{code}/delete', [CommunityController::class, 'deleteCurrencyExchangeRate']);
$routeCollection->get('community.currencyExchangeRates', '{code}', [CommunityController::class, 'getCurrencyExchangeRates']);
$routeCollection->post('community.currencyExchangeRates.new-action', '{code}/new', [CommunityController::class, 'saveCurrencyExchangeRate']);
$routeCollection->post('community.currencyExchangeRates.edit-action', '{code}/edit', [CommunityController::class, 'saveCurrencyExchangeRate']);
$routeCollection->post('community.currencyExchangeRates.delete-action', '{code}/delete', [CommunityController::class, 'deleteCurrencyExchangeRate']);
});
$routeCollection->group('transactions', function (RouteCollection $routeCollection) {
$routeCollection->get('community.transactions', '', [TransactionController::class, 'getTransactions']);
$routeCollection->get('community.transactions.new', 'new', [TransactionController::class, 'getTransactionEdit']);
$routeCollection->post('community.transactions.new-action', 'new', [TransactionController::class, 'saveTransaction']);
$routeCollection->get('community.transactions.edit', '{transactionId}', [TransactionController::class, 'getTransactionEdit']);
$routeCollection->post('community.transactions.edit-action', '{transactionId}', [TransactionController::class, 'saveTransaction']);
$routeCollection->post('community.transactions.delete-action', '{transactionId}/delete', [TransactionController::class, 'deleteTransaction']);
});
});
});
Container::$sessionHandler = new DatabaseSessionHandler(Container::$dbConnection);
Container::$sessionHandler = new DatabaseSessionHandler(
Container::$dbConnection,
'sessions',
new DateTime('-7 days')
);
session_set_save_handler(Container::$sessionHandler, true);
session_start([
'gc_probability' => 0, // old sessions are deleted by MaintainDatabaseCommand
'cookie_lifetime' => 604800,
'cookie_lifetime' => 0,
'cookie_path' => '/',
'cookie_httponly' => true,
'cookie_samesite' => 'Lax'
]);
if (isset($_COOKIE[session_name()])) {
// extend session cookie lifetime is cookie already exists
setcookie(session_name(), session_id(), [
'expires' => time() + 604800,
'path' => '/',
'httponly' => true,
'samesite' => 'Lax'
]);
}
Container::$request = new Request(
$_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'],
$_GET,
$_POST,
getallheaders(),
$_SESSION,
new Session($_SESSION),
new UserRepository()
);