implement community basics
All checks were successful
rvr-nextgen/pipeline/pr-master This commit looks good
All checks were successful
rvr-nextgen/pipeline/pr-master This commit looks good
This commit is contained in:
parent
4dc08dffc9
commit
d763c4344c
20
database/migrations/structure/20230412_1955_communities.sql
Normal file
20
database/migrations/structure/20230412_1955_communities.sql
Normal file
@ -0,0 +1,20 @@
|
||||
CREATE TABLE `communities` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`currency` varchar(3) NOT NULL,
|
||||
`created` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
CREATE TABLE `community_members` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`community_id` int(10) unsigned NOT NULL,
|
||||
`user_id` int(10) unsigned DEFAULT NULL,
|
||||
`owner` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_user_for_community` (`community_id`, `user_id`),
|
||||
KEY `community_id` (`community_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
CONSTRAINT `community_members_community_id` FOREIGN KEY (`community_id`) REFERENCES `communities` (`id`),
|
||||
CONSTRAINT `community_members_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
@ -88,10 +88,14 @@ sub {
|
||||
}
|
||||
|
||||
hr {
|
||||
border: solid #bbbbbb 1px;
|
||||
border: solid #bbbbcc 1px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
@ -418,13 +422,53 @@ div.buttonContainer>button {
|
||||
|
||||
div.box {
|
||||
width: 576px;
|
||||
background-color: #eeeeee;
|
||||
background-color: #eeeef4;
|
||||
border-radius: 3px;
|
||||
margin: 10px auto;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.gridContainer {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
div.gridContainer > div {
|
||||
background-color: #eeeef4;
|
||||
border-radius: 3px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
table.fullWidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
padding: 2px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.choices__inner {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 424px) {
|
||||
div.gridContainer {
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 599px) {
|
||||
header h1 span {
|
||||
display: none;
|
||||
|
62
public/static/js/communities/community_members.js
Normal file
62
public/static/js/communities/community_members.js
Normal file
@ -0,0 +1,62 @@
|
||||
(function () {
|
||||
const element = document.getElementById('new_member_user_id');
|
||||
const choices = new Choices(element, {
|
||||
noResultsText: 'No users found',
|
||||
noChoicesText: 'Start typing to search users'
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
}));
|
||||
|
||||
element.addEventListener('choice', function () {
|
||||
choices.setChoices([], 'value', 'label', true);
|
||||
document.getElementById('new_member_button').disabled = false;
|
||||
});
|
||||
|
||||
document.getElementById('new_member_button').addEventListener('click', function () {
|
||||
document.getElementById('loading').style.visibility = 'visible';
|
||||
|
||||
let data = new FormData();
|
||||
data.append('user_id', document.getElementById('new_member_user_id').value);
|
||||
|
||||
RVR.httpRequest('POST', newMemberUrl, function () {
|
||||
window.location.reload();
|
||||
}, data);
|
||||
});
|
||||
|
||||
const ownerCheckboxesButtons = document.getElementsByClassName('member_owner');
|
||||
for (const ownerCheckboxesButton of ownerCheckboxesButtons) {
|
||||
ownerCheckboxesButton.addEventListener('change', function () {
|
||||
document.getElementById('loading').style.visibility = 'visible';
|
||||
|
||||
let data = new FormData();
|
||||
data.append('community_member_id', this.dataset.id);
|
||||
data.append('owner', this.checked ? 1 : 0);
|
||||
|
||||
RVR.httpRequest('POST', editMemberUrl, function () {
|
||||
document.getElementById('loading').style.visibility = 'hidden';
|
||||
|
||||
if (!this.response.success) {
|
||||
ownerCheckboxesButton.checked = !ownerCheckboxesButton.checked;
|
||||
}
|
||||
}, data);
|
||||
});
|
||||
};
|
||||
|
||||
const deleteButtons = document.getElementsByClassName('delete_member');
|
||||
for (const deleteButton of deleteButtons) {
|
||||
deleteButton.addEventListener('click', function () {
|
||||
document.getElementById('loading').style.visibility = 'visible';
|
||||
|
||||
let data = new FormData();
|
||||
data.append('community_member_id', this.dataset.id);
|
||||
|
||||
RVR.httpRequest('POST', deleteMemberUrl, function () {
|
||||
window.location.reload();
|
||||
}, data);
|
||||
});
|
||||
};
|
||||
})();
|
@ -193,6 +193,14 @@ var RVR = {
|
||||
form.onreset = function () {
|
||||
form.elements.submit.disabled = true;
|
||||
}
|
||||
},
|
||||
|
||||
debounce: function(func, timeout = 300) {
|
||||
let timer;
|
||||
return (...args) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => { func.apply(this, args); }, timeout);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
57
public/static/package-lock.json
generated
Normal file
57
public/static/package-lock.json
generated
Normal file
@ -0,0 +1,57 @@
|
||||
{
|
||||
"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==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"leaflet": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz",
|
||||
"integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ=="
|
||||
},
|
||||
"leaflet.markercluster": {
|
||||
"version": "1.5.3",
|
||||
"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==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"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,6 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"leaflet": "^1.6.0",
|
||||
"leaflet.markercluster": "^1.4.1"
|
||||
"leaflet.markercluster": "^1.4.1",
|
||||
"choices.js": "^10.2.0"
|
||||
}
|
||||
}
|
||||
|
216
src/Controller/CommunityController.php
Normal file
216
src/Controller/CommunityController.php
Normal file
@ -0,0 +1,216 @@
|
||||
<?php namespace RVR\Controller;
|
||||
|
||||
use DateTime;
|
||||
use RVR\PersistentData\Model\Community;
|
||||
use RVR\PersistentData\Model\CommunityMember;
|
||||
use RVR\PersistentData\Model\User;
|
||||
use RVR\Repository\CommunityRepository;
|
||||
use RVR\Repository\CommunityMemberRepository;
|
||||
use RVR\Repository\UserRepository;
|
||||
use SokoWeb\Interfaces\Authorization\ISecured;
|
||||
use SokoWeb\Interfaces\Request\IRequest;
|
||||
use SokoWeb\Interfaces\Response\IContent;
|
||||
use SokoWeb\PersistentData\PersistentDataManager;
|
||||
use SokoWeb\Response\HtmlContent;
|
||||
use SokoWeb\Response\JsonContent;
|
||||
|
||||
class CommunityController implements ISecured
|
||||
{
|
||||
private IRequest $request;
|
||||
|
||||
private PersistentDataManager $pdm;
|
||||
|
||||
private UserRepository $userRepository;
|
||||
|
||||
private CommunityRepository $communityRepository;
|
||||
|
||||
private CommunityMemberRepository $communityMemberRepository;
|
||||
|
||||
public function __construct(IRequest $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->pdm = new PersistentDataManager();
|
||||
$this->userRepository = new UserRepository();
|
||||
$this->communityRepository = new CommunityRepository();
|
||||
$this->communityMemberRepository = new CommunityMemberRepository();
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->request->user() !== null;
|
||||
}
|
||||
|
||||
public function getCommunityHome(): ?IContent
|
||||
{
|
||||
if (!$this->checkPermission($this->request->query('communityId'), false, $community, $ownCommunityMember)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new HtmlContent('communities/community', [
|
||||
'community' => $community,
|
||||
'members' => $this->getMembers($community),
|
||||
'currencyNames' => [],
|
||||
'upcomingEvents' => [],
|
||||
'editPermission' => $ownCommunityMember->getOwner()
|
||||
]);
|
||||
}
|
||||
|
||||
public function getCommunityNew(): IContent
|
||||
{
|
||||
return new HtmlContent('communities/community_edit');
|
||||
}
|
||||
|
||||
public function getCommunityEdit(): ?IContent
|
||||
{
|
||||
if (!$this->checkPermission($this->request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new HtmlContent('communities/community_edit', [
|
||||
'community' => $community
|
||||
]);
|
||||
}
|
||||
|
||||
public function getMembersEdit(): ?IContent
|
||||
{
|
||||
if (!$this->checkPermission($this->request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new HtmlContent('communities/community_members', [
|
||||
'community' => $community,
|
||||
'members' => $this->getMembers($community)
|
||||
]);
|
||||
}
|
||||
|
||||
private function getMembers(Community $community): array
|
||||
{
|
||||
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true));
|
||||
usort($members, function($a, $b) {
|
||||
return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
|
||||
});
|
||||
return $members;
|
||||
}
|
||||
|
||||
|
||||
public function newMember(): ?IContent
|
||||
{
|
||||
if (!$this->checkPermission($this->request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = $this->userRepository->getById($this->request->post('user_id'));
|
||||
|
||||
$communityMember = new CommunityMember();
|
||||
$communityMember->setCommunity($community);
|
||||
$communityMember->setUser($user);
|
||||
$this->pdm->saveToDb($communityMember);
|
||||
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
public function editMember(): ?IContent
|
||||
{
|
||||
if (!$this->checkPermission($this->request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$communityMember = $this->communityMemberRepository->getById($this->request->post('community_member_id'));
|
||||
if ($communityMember->getUserId() === $this->request->user()->getUniqueId()) {
|
||||
return new JsonContent([
|
||||
'error' => ['errorText' => 'Own user cannot be edited.']
|
||||
]);
|
||||
}
|
||||
|
||||
$communityMember->setOwner($this->request->post('owner'));
|
||||
$this->pdm->saveToDb($communityMember);
|
||||
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
public function deleteMember(): ?IContent
|
||||
{
|
||||
if (!$this->checkPermission($this->request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$communityMember = $this->communityMemberRepository->getById($this->request->post('community_member_id'));
|
||||
if ($communityMember->getUserId() === $this->request->user()->getUniqueId()) {
|
||||
return new JsonContent([
|
||||
'error' => ['errorText' => 'Own user cannot be deleted.']
|
||||
]);
|
||||
}
|
||||
|
||||
$this->pdm->deleteFromDb($communityMember);
|
||||
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
public function saveCommunity(): ?IContent
|
||||
{
|
||||
$communityId = $this->request->query('communityId');
|
||||
if ($communityId){
|
||||
if (!$this->checkPermission($communityId, true, $community, $ownCommunityMember)) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
$community = new Community();
|
||||
}
|
||||
|
||||
$name = $this->request->post('name');
|
||||
$currency = $this->request->post('currency');
|
||||
if (strlen($name) === 0 || strlen($currency) === 0 || strlen($currency) > 3) {
|
||||
return new JsonContent([
|
||||
'error' => ['errorText' => 'Please fill all required fields!']
|
||||
]);
|
||||
}
|
||||
|
||||
$community->setName($name);
|
||||
$community->setCurrency($currency);
|
||||
if (!$communityId) {
|
||||
$community->setCreatedDate(new DateTime());
|
||||
}
|
||||
$this->pdm->saveToDb($community);
|
||||
|
||||
if (!$communityId) {
|
||||
/**
|
||||
* @var User $user
|
||||
*/
|
||||
$user = $this->request->user();
|
||||
|
||||
$communityMember = new CommunityMember();
|
||||
$communityMember->setCommunity($community);
|
||||
$communityMember->setUser($user);
|
||||
$communityMember->setOwner(true);
|
||||
$this->pdm->saveToDb($communityMember);
|
||||
}
|
||||
|
||||
return new JsonContent([
|
||||
'redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()])]
|
||||
]);
|
||||
}
|
||||
|
||||
private function checkPermission(
|
||||
int $communityId,
|
||||
bool $needToBeOwner,
|
||||
?Community &$community,
|
||||
?CommunityMember &$ownCommunityMember): bool
|
||||
{
|
||||
$community = $this->communityRepository->getById($communityId);
|
||||
if ($community === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var User $user
|
||||
*/
|
||||
$user = $this->request->user();
|
||||
|
||||
$ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($community, $user);
|
||||
if ($ownCommunityMember === null || ($needToBeOwner && !$ownCommunityMember->getOwner())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,18 +1,22 @@
|
||||
<?php namespace RVR\Controller;
|
||||
|
||||
use RVR\PersistentData\Model\User;
|
||||
use RVR\Repository\CommunityMemberRepository;
|
||||
use SokoWeb\Interfaces\Authorization\ISecured;
|
||||
use SokoWeb\Interfaces\Request\IRequest;
|
||||
use SokoWeb\Interfaces\Response\IContent;
|
||||
use SokoWeb\Response\HtmlContent;
|
||||
use SokoWeb\Response\JsonContent;
|
||||
|
||||
class HomeController implements ISecured
|
||||
{
|
||||
private IRequest $request;
|
||||
|
||||
private CommunityMemberRepository $communityMemberRepository;
|
||||
|
||||
public function __construct(IRequest $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->communityMemberRepository = new CommunityMemberRepository();
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
@ -20,8 +24,25 @@ class HomeController implements ISecured
|
||||
return $this->request->user() !== null;
|
||||
}
|
||||
|
||||
public function getIndex(): IContent
|
||||
public function getHome(): IContent
|
||||
{
|
||||
return new HtmlContent('index');
|
||||
/**
|
||||
* @var User $user
|
||||
*/
|
||||
$user = $this->request->user();
|
||||
|
||||
$ownCommunityMembers = $this->communityMemberRepository->getAllByUser($user, true);
|
||||
$communities = [];
|
||||
foreach ($ownCommunityMembers as $ownCommunityMember) {
|
||||
$communities[] = $ownCommunityMember->getCommunity();
|
||||
}
|
||||
usort($communities, function($a, $b) {
|
||||
return strnatcmp($a->getName(), $b->getName());
|
||||
});
|
||||
|
||||
return new HtmlContent('home', [
|
||||
'communities' => $communities,
|
||||
'upcomingEvents' => []
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
42
src/Controller/UserSearchController.php
Normal file
42
src/Controller/UserSearchController.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php namespace RVR\Controller;
|
||||
|
||||
use RVR\Repository\UserRepository;
|
||||
use SokoWeb\Interfaces\Authorization\ISecured;
|
||||
use SokoWeb\Interfaces\Request\IRequest;
|
||||
use SokoWeb\Interfaces\Response\IContent;
|
||||
use SokoWeb\Response\JsonContent;
|
||||
|
||||
class UserSearchController implements ISecured
|
||||
{
|
||||
private IRequest $request;
|
||||
|
||||
private UserRepository $userRepository;
|
||||
|
||||
public function __construct(IRequest $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->userRepository = new UserRepository();
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->request->user() !== null;
|
||||
}
|
||||
|
||||
public function searchUser(): IContent
|
||||
{
|
||||
$users = iterator_to_array($this->userRepository->searchByName($this->request->query('q')));
|
||||
usort($users, function($a, $b) {
|
||||
return strnatcmp($a->getDisplayName(), $b->getDisplayName());
|
||||
});
|
||||
|
||||
$results = [];
|
||||
foreach ($users as $user) {
|
||||
$results[] = ['value' => $user->getId(), 'label' => $user->getFullDisplayName()];
|
||||
}
|
||||
|
||||
return new JsonContent([
|
||||
'results' => $results
|
||||
]);
|
||||
}
|
||||
}
|
57
src/PersistentData/Model/Community.php
Normal file
57
src/PersistentData/Model/Community.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php namespace RVR\PersistentData\Model;
|
||||
|
||||
use DateTime;
|
||||
use SokoWeb\PersistentData\Model\Model;
|
||||
|
||||
class Community extends Model
|
||||
{
|
||||
protected static string $table = 'communities';
|
||||
|
||||
protected static array $fields = ['name', 'currency', 'created'];
|
||||
|
||||
private string $name = '';
|
||||
|
||||
private string $currency = '';
|
||||
|
||||
private DateTime $created;
|
||||
|
||||
public function setName(string $name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function setCurrency(string $currency): void
|
||||
{
|
||||
$this->currency = $currency;
|
||||
}
|
||||
|
||||
public function setCreatedDate(DateTime $created): void
|
||||
{
|
||||
$this->created = $created;
|
||||
}
|
||||
|
||||
public function setCreated(string $created): void
|
||||
{
|
||||
$this->created = new DateTime($created);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getCurrency(): string
|
||||
{
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
public function getCreatedDate(): DateTime
|
||||
{
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreated(): string
|
||||
{
|
||||
return $this->created->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
72
src/PersistentData/Model/CommunityMember.php
Normal file
72
src/PersistentData/Model/CommunityMember.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php namespace RVR\PersistentData\Model;
|
||||
|
||||
use SokoWeb\PersistentData\Model\Model;
|
||||
|
||||
class CommunityMember extends Model
|
||||
{
|
||||
protected static string $table = 'community_members';
|
||||
|
||||
protected static array $fields = ['community_id', 'user_id', 'owner'];
|
||||
|
||||
protected static array $relations = ['community' => Community::class, 'user' => User::class];
|
||||
|
||||
private ?Community $community = null;
|
||||
|
||||
private ?int $communityId = null;
|
||||
|
||||
private ?User $user = null;
|
||||
|
||||
private ?int $userId = null;
|
||||
|
||||
private bool $owner = false;
|
||||
|
||||
public function setCommunity(Community $community): void
|
||||
{
|
||||
$this->community = $community;
|
||||
}
|
||||
|
||||
public function setCommunityId(int $communityId): void
|
||||
{
|
||||
$this->communityId = $communityId;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function setUserId(int $userId): void
|
||||
{
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
public function setOwner(bool $owner): void
|
||||
{
|
||||
$this->owner = $owner;
|
||||
}
|
||||
|
||||
public function getCommunity(): ?Community
|
||||
{
|
||||
return $this->community;
|
||||
}
|
||||
|
||||
public function getCommunityId(): ?int
|
||||
{
|
||||
return $this->communityId;
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function getUserId(): ?int
|
||||
{
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function getOwner(): bool
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
}
|
@ -171,6 +171,11 @@ class User extends Model implements IUser
|
||||
return $this->nickname ?: $this->fullName;
|
||||
}
|
||||
|
||||
public function getFullDisplayName(): string
|
||||
{
|
||||
return $this->nickname ? $this->nickname . ' (' . $this->fullName . ')' : $this->fullName;
|
||||
}
|
||||
|
||||
public function checkPassword(string $password): bool
|
||||
{
|
||||
if ($this->password === null) {
|
||||
|
48
src/Repository/CommunityMemberRepository.php
Normal file
48
src/Repository/CommunityMemberRepository.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php namespace RVR\Repository;
|
||||
|
||||
use Generator;
|
||||
use RVR\PersistentData\Model\Community;
|
||||
use RVR\PersistentData\Model\CommunityMember;
|
||||
use RVR\PersistentData\Model\User;
|
||||
use SokoWeb\Database\Query\Select;
|
||||
use SokoWeb\PersistentData\PersistentDataManager;
|
||||
|
||||
class CommunityMemberRepository
|
||||
{
|
||||
private PersistentDataManager $pdm;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pdm = new PersistentDataManager();
|
||||
}
|
||||
|
||||
public function getById(int $id): ?CommunityMember
|
||||
{
|
||||
return $this->pdm->selectFromDbById($id, CommunityMember::class);
|
||||
}
|
||||
|
||||
public function getAllByCommunity(Community $community, bool $useRelations = false): Generator
|
||||
{
|
||||
$select = new Select(\Container::$dbConnection);
|
||||
$select->where('community_id', '=', $community->getId());
|
||||
|
||||
yield from $this->pdm->selectMultipleFromDb($select, CommunityMember::class, $useRelations);
|
||||
}
|
||||
|
||||
public function getAllByUser(User $user, bool $useRelations = false): Generator
|
||||
{
|
||||
$select = new Select(\Container::$dbConnection);
|
||||
$select->where('user_id', '=', $user->getId());
|
||||
|
||||
yield from $this->pdm->selectMultipleFromDb($select, CommunityMember::class, $useRelations);
|
||||
}
|
||||
|
||||
public function getByCommunityAndUser(Community $community, User $user) : ?CommunityMember
|
||||
{
|
||||
$select = new Select(\Container::$dbConnection);
|
||||
$select->where('community_id', '=', $community->getId());
|
||||
$select->where('user_id', '=', $user->getId());
|
||||
|
||||
return $this->pdm->selectFromDb($select, CommunityMember::class);
|
||||
}
|
||||
}
|
19
src/Repository/CommunityRepository.php
Normal file
19
src/Repository/CommunityRepository.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php namespace RVR\Repository;
|
||||
|
||||
use RVR\PersistentData\Model\Community;
|
||||
use SokoWeb\PersistentData\PersistentDataManager;
|
||||
|
||||
class CommunityRepository
|
||||
{
|
||||
private PersistentDataManager $pdm;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pdm = new PersistentDataManager();
|
||||
}
|
||||
|
||||
public function getById(int $id): ?Community
|
||||
{
|
||||
return $this->pdm->selectFromDbById($id, Community::class);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
<?php namespace RVR\Repository;
|
||||
|
||||
use Generator;
|
||||
use SokoWeb\Interfaces\Repository\IUserRepository;
|
||||
use SokoWeb\Database\Query\Select;
|
||||
use RVR\PersistentData\Model\User;
|
||||
@ -51,4 +52,14 @@ class UserRepository implements IUserRepository
|
||||
|
||||
return $this->pdm->selectFromDb($select, User::class);
|
||||
}
|
||||
|
||||
public function searchByName(string $name): Generator
|
||||
{
|
||||
$select = new Select(\Container::$dbConnection);
|
||||
$select->where('full_name', 'LIKE', '%' . $name . '%');
|
||||
$select->orWhere('nickname', 'LIKE', '%' . $name . '%');
|
||||
$select->limit(10);
|
||||
|
||||
yield from $this->pdm->selectMultipleFromDb($select, User::class);
|
||||
}
|
||||
}
|
||||
|
50
views/communities/community.php
Normal file
50
views/communities/community.php
Normal file
@ -0,0 +1,50 @@
|
||||
@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>
|
||||
|
||||
<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>
|
||||
<p>Further currencies: <b><?= implode(', ', $currencyNames) ?></b></p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="marginBottom">Upcoming events</h3>
|
||||
<?php if (count($upcomingEvents) > 0): ?>
|
||||
<?php foreach ($upcomingEvents as $event): ?>
|
||||
<!-- todo -->
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<p>There is no upcoming event.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="marginBottom">Finances</h3>
|
||||
<table class="fullWidth">
|
||||
<tr>
|
||||
<td>You owe</td>
|
||||
<td style="text-align: right; color: red;">0 <?= $community->getCurrency() ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>You're owed</td>
|
||||
<td style="text-align: right; color: green;">0 <?= $community->getCurrency() ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Your balance</td>
|
||||
<td style="text-align: right;">0 <?= $community->getCurrency() ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
20
views/communities/community_edit.php
Normal file
20
views/communities/community_edit.php
Normal file
@ -0,0 +1,20 @@
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section(main)
|
||||
<h2><?= isset($community) ? $community->getName() . ' - Edit' : 'New community' ?></h2>
|
||||
<div class="box">
|
||||
<?php
|
||||
$formAction = isset($community) ?
|
||||
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 id="accountFormError" class="formError justify marginTop"></p>
|
||||
<div class="right marginTop">
|
||||
<button type="submit" name="submit"><?= isset($community) ? 'Save' : 'Create' ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
42
views/communities/community_members.php
Normal file
42
views/communities/community_members.php
Normal file
@ -0,0 +1,42 @@
|
||||
@css(node_modules/choices.js/public/assets/styles/choices.min.css)
|
||||
@js(node_modules/choices.js/public/assets/scripts/choices.js)
|
||||
@js(js/communities/community_members.js)
|
||||
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section(main)
|
||||
<h2><?= $community->getName() ?> - Edit members</h2>
|
||||
<div class="box">
|
||||
<table class="fullWidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50%;"></th>
|
||||
<th style="width: 25%; text-align: center;">Owner</th>
|
||||
<th style="width: 25%;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<?php foreach ($members as $member): ?>
|
||||
<?php $editable = $member->getUserId() !== Container::$request->user()->getUniqueId(); ?>
|
||||
<tr>
|
||||
<td><?= $member->getUser()->getDisplayName() ?></td>
|
||||
<td style="text-align: center;"><input type="checkbox" class="member_owner" data-id="<?= $member->getId() ?>" <?= $member->getOwner() ? 'checked' : '' ?> <?= !$editable ? 'disabled' : '' ?> /></td>
|
||||
<td style="text-align: right;"><button type="button" class="small red delete_member" data-id="<?= $member->getId() ?>" <?= !$editable ? 'disabled' : '' ?>>Delete</button></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr>
|
||||
<td><select type="text" id="new_member_user_id"></td>
|
||||
<td></td>
|
||||
<td style="text-align: right;"><button type="button" class="small" id="new_member_button" disabled>Add</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section(pageScript)
|
||||
<script>
|
||||
var searchUserUrl = '/<?= Container::$routeCollection->getRoute('searchUser')->generateLink(['q' => 'QUERY']) ?>';
|
||||
var newMemberUrl = '/<?= Container::$routeCollection->getRoute('community-members-new')->generateLink(['communityId' => $community->getId()]) ?>';
|
||||
var editMemberUrl = '/<?= Container::$routeCollection->getRoute('community-members-edit')->generateLink(['communityId' => $community->getId()]) ?>';
|
||||
var deleteMemberUrl = '/<?= Container::$routeCollection->getRoute('community-members-delete')->generateLink(['communityId' => $community->getId()]) ?>';
|
||||
</script>
|
||||
@endsection
|
28
views/home.php
Normal file
28
views/home.php
Normal file
@ -0,0 +1,28 @@
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section(main)
|
||||
<div class="gridContainer">
|
||||
<div>
|
||||
<h3 class="marginBottom">Communities</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>
|
||||
<?php endforeach; ?>
|
||||
<?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>
|
||||
<?php if (count($upcomingEvents) > 0): ?>
|
||||
<?php foreach ($upcomingEvents as $event): ?>
|
||||
<!-- todo -->
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<p>There is no upcoming event.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -1,8 +0,0 @@
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section(main)
|
||||
<h2><?= $_ENV['APP_NAME'] ?></h2>
|
||||
<div class="box">
|
||||
<p>This is the new <?= $_ENV['APP_NAME'] ?>.</p>
|
||||
</div>
|
||||
@endsection
|
16
web.php
16
web.php
@ -12,7 +12,7 @@ if (!empty($_ENV['DEV'])) {
|
||||
|
||||
Container::$routeCollection = new SokoWeb\Routing\RouteCollection();
|
||||
|
||||
Container::$routeCollection->get('index', '', [RVR\Controller\HomeController::class, 'getIndex']);
|
||||
Container::$routeCollection->get('home', '', [RVR\Controller\HomeController::class, 'getHome']);
|
||||
Container::$routeCollection->get('startSession', 'startSession.json', [RVR\Controller\HomeController::class, 'startSession']);
|
||||
Container::$routeCollection->group('login', function (SokoWeb\Routing\RouteCollection $routeCollection) {
|
||||
$routeCollection->get('login', '', [RVR\Controller\LoginController::class, 'getLoginForm']);
|
||||
@ -41,6 +41,20 @@ Container::$routeCollection->group('account', function (SokoWeb\Routing\RouteCol
|
||||
$routeCollection->get('account.googleAuthenticate', 'googleAuthenticate', [RVR\Controller\UserController::class, 'getGoogleAuthenticateRedirect']);
|
||||
$routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [RVR\Controller\UserController::class, 'authenticateWithGoogle']);
|
||||
});
|
||||
Container::$routeCollection->get('searchUser', 'searchUser', [RVR\Controller\UserSearchController::class, 'searchUser']);
|
||||
Container::$routeCollection->group('communities', function (SokoWeb\Routing\RouteCollection $routeCollection) {
|
||||
$routeCollection->get('community-new', 'new', [RVR\Controller\CommunityController::class, 'getCommunityNew']);
|
||||
$routeCollection->post('community-new-action', 'new', [RVR\Controller\CommunityController::class, 'saveCommunity']);
|
||||
$routeCollection->group('{communityId}', function (SokoWeb\Routing\RouteCollection $routeCollection) {
|
||||
$routeCollection->get('community', '', [RVR\Controller\CommunityController::class, 'getCommunityHome']);
|
||||
$routeCollection->get('community-edit', 'edit', [RVR\Controller\CommunityController::class, 'getCommunityEdit']);
|
||||
$routeCollection->post('community-edit-action', 'edit', [RVR\Controller\CommunityController::class, 'saveCommunity']);
|
||||
$routeCollection->get('community-members', 'members', [RVR\Controller\CommunityController::class, 'getMembersEdit']);
|
||||
$routeCollection->post('community-members-new', 'newMember', [RVR\Controller\CommunityController::class, 'newMember']);
|
||||
$routeCollection->post('community-members-edit', 'editMember', [RVR\Controller\CommunityController::class, 'editMember']);
|
||||
$routeCollection->post('community-members-delete', 'deleteMember', [RVR\Controller\CommunityController::class, 'deleteMember']);
|
||||
});
|
||||
});
|
||||
|
||||
Container::$sessionHandler = new SokoWeb\Session\DatabaseSessionHandler();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user