Skip to content

Commit 655ba45

Browse files
committed
Filter away shared camps in the API requests
1 parent 2ffef47 commit 655ba45

File tree

8 files changed

+96
-26
lines changed

8 files changed

+96
-26
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace App\Doctrine\Filter;
4+
5+
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
6+
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
7+
use ApiPlatform\Metadata\IriConverterInterface;
8+
use ApiPlatform\Metadata\Operation;
9+
use App\Entity\BelongsToCampInterface;
10+
use App\Entity\Camp;
11+
use App\Entity\CampCollaboration;
12+
use App\Util\QueryBuilderHelper;
13+
use Doctrine\ORM\QueryBuilder;
14+
use Doctrine\Persistence\ManagerRegistry;
15+
use Psr\Log\LoggerInterface;
16+
use Symfony\Component\PropertyInfo\Type;
17+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
18+
19+
final class CampCollaboratorFilter extends AbstractFilter {
20+
public const QUERY_PARAM_NAME = 'campCollaborator';
21+
22+
public function __construct(
23+
private IriConverterInterface $iriConverter,
24+
private QueryBuilderHelper $queryBuilderHelper,
25+
ManagerRegistry $managerRegistry,
26+
?LoggerInterface $logger = null,
27+
?array $properties = null,
28+
?NameConverterInterface $nameConverter = null
29+
) {
30+
parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
31+
}
32+
33+
// This function is only used to hook in documentation generators (supported by Swagger and Hydra)
34+
public function getDescription(string $resourceClass): array {
35+
return [self::QUERY_PARAM_NAME => [
36+
'property' => self::QUERY_PARAM_NAME,
37+
'type' => Type::BUILTIN_TYPE_STRING,
38+
'required' => false,
39+
]];
40+
}
41+
42+
protected function filterProperty(
43+
string $property,
44+
$value,
45+
QueryBuilder $queryBuilder,
46+
QueryNameGeneratorInterface $queryNameGenerator,
47+
string $resourceClass,
48+
?Operation $operation = null,
49+
array $context = []
50+
): void {
51+
if (!class_implements($resourceClass, BelongsToCampInterface::class)) {
52+
throw new \Exception("CampCollaboratorFilter can only be applied to entities which implement BelongsToCampInterface (received: {$resourceClass}).");
53+
}
54+
55+
if (self::QUERY_PARAM_NAME !== $property) {
56+
return;
57+
}
58+
59+
$campAlias = Camp::class === $resourceClass ?
60+
$queryBuilder->getRootAliases()[0] :
61+
$this->queryBuilderHelper->findOrAddInnerRootJoinAlias($queryBuilder, $queryNameGenerator, 'camp');
62+
63+
// load user from query parameter value
64+
$collaborator = $this->iriConverter->getResourceFromIri($value);
65+
66+
$campsQry = $queryBuilder->getEntityManager()->createQueryBuilder();
67+
$campsQry->select('identity(cc.camp)');
68+
$campsQry->from(CampCollaboration::class, 'cc');
69+
$campsQry->andWhere('cc.user = :collaborator');
70+
$campsQry->andWhere('cc.status = :status_established');
71+
72+
$queryBuilder->andWhere($queryBuilder->expr()->in($campAlias, $campsQry->getDQL()));
73+
$queryBuilder->setParameter('collaborator', $collaborator);
74+
$queryBuilder->setParameter('status_established', CampCollaboration::STATUS_ESTABLISHED);
75+
}
76+
}

api/src/Doctrine/Filter/ContentNodeCampFilter.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,13 @@
88
use ApiPlatform\Metadata\Operation;
99
use App\Entity\Activity;
1010
use App\Entity\ContentNode;
11-
use App\Repository\FiltersByCampCollaboration;
1211
use Doctrine\ORM\QueryBuilder;
1312
use Doctrine\Persistence\ManagerRegistry;
1413
use Psr\Log\LoggerInterface;
1514
use Symfony\Component\PropertyInfo\Type;
1615
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
1716

1817
final class ContentNodeCampFilter extends AbstractFilter {
19-
use FiltersByCampCollaboration;
20-
2118
public const CAMP_QUERY_NAME = 'camp';
2219

2320
public function __construct(

api/src/Doctrine/Filter/ContentNodePeriodFilter.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use ApiPlatform\Metadata\Operation;
99
use App\Entity\Activity;
1010
use App\Entity\ContentNode;
11-
use App\Repository\FiltersByCampCollaboration;
1211
use Doctrine\ORM\Query\Expr\Join;
1312
use Doctrine\ORM\QueryBuilder;
1413
use Doctrine\Persistence\ManagerRegistry;
@@ -17,8 +16,6 @@
1716
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
1817

1918
final class ContentNodePeriodFilter extends AbstractFilter {
20-
use FiltersByCampCollaboration;
21-
2219
public const PERIOD_QUERY_NAME = 'period';
2320

2421
public function __construct(

api/src/Entity/Camp.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use ApiPlatform\Metadata\GetCollection;
1212
use ApiPlatform\Metadata\Patch;
1313
use ApiPlatform\Metadata\Post;
14+
use App\Doctrine\Filter\CampCollaboratorFilter;
1415
use App\InputFilter;
1516
use App\Repository\CampRepository;
1617
use App\Serializer\Normalizer\RelatedCollectionLink;
@@ -64,6 +65,7 @@
6465
normalizationContext: ['groups' => ['read']]
6566
)]
6667
#[ApiFilter(filterClass: SearchFilter::class, properties: ['isPrototype'])]
68+
#[ApiFilter(filterClass: CampCollaboratorFilter::class)]
6769
#[ORM\Entity(repositoryClass: CampRepository::class)]
6870
#[ORM\Index(columns: ['isPrototype'])]
6971
#[ORM\Index(columns: ['isShared'])]

api/src/Entity/Period.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use ApiPlatform\Metadata\GetCollection;
1212
use ApiPlatform\Metadata\Patch;
1313
use ApiPlatform\Metadata\Post;
14+
use App\Doctrine\Filter\CampCollaboratorFilter;
1415
use App\InputFilter;
1516
use App\Repository\PeriodRepository;
1617
use App\Serializer\Normalizer\RelatedCollectionLink;
@@ -62,6 +63,7 @@
6263
order: ['start']
6364
)]
6465
#[ApiFilter(filterClass: SearchFilter::class, properties: ['camp'])]
66+
#[ApiFilter(filterClass: CampCollaboratorFilter::class)]
6567
#[ORM\Entity(repositoryClass: PeriodRepository::class)]
6668
class Period extends BaseEntity implements BelongsToCampInterface {
6769
public const ITEM_NORMALIZATION_CONTEXT = [

frontend/src/components/campAdmin/CampPeriods.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Displays all periods of a single camp and allows to edit them & create new ones
44

55
<template>
66
<content-group
7-
:title="$tc('components.campAdmin.campPeriods.title', api.get().camps().items.length)"
7+
:title="$tc('components.campAdmin.campPeriods.title', camp.periods().items.length)"
88
icon="mdi-calendar-multiple"
99
>
1010
<template #title-actions>

frontend/src/components/collaborator/PromptCollaboratorDeactivate.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ export default {
6767
if (!this.isOwnCampCollaboration) {
6868
return
6969
}
70-
this.api.get().camps().$reload()
70+
this.api
71+
.get()
72+
.camps({ campCollaborator: this.$store.getters.getLoggedInUser?._meta.self })
73+
.$reload()
7174
this.$router.push({ name: 'camps' })
7275
})
7376

frontend/src/views/Camps.vue

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -106,36 +106,28 @@ export default {
106106
}
107107
},
108108
computed: {
109+
currentUserLink() {
110+
return this.$store.getters.getLoggedInUser?._meta.self
111+
},
109112
camps() {
110-
return this.api.get().camps()
113+
return this.api.get().camps({ campCollaborator: this.currentUserLink })
111114
},
112115
periods() {
113-
return this.api.get().periods()
116+
return this.api.get().periods({ campCollaborator: this.currentUserLink })
114117
},
115118
prototypeCamps() {
116-
return this.camps.items.filter((c) => c.isPrototype)
119+
return this.api.get().camps({ isPrototype: true }).items
117120
},
118121
upcomingCamps() {
119122
return Object.values(
120123
groupBy(
121124
this.periods.items.filter((p) => dayjs(p.end).endOf('day').isAfter(dayjs())),
122125
(p) => p.camp()._meta.self
123126
)
124-
)
125-
.map((periods) => ({
126-
camp: periods[0].camp(),
127-
periods: periods,
128-
}))
129-
.filter(({ camp }) => {
130-
const currentUserLink = this.$store.getters.getLoggedInUser?._meta.self
131-
const currentUserCampCollaboration = camp
132-
.campCollaborations()
133-
.items.filter((coll) => typeof coll.user === 'function')
134-
.find((coll) => coll.user()._meta.self === currentUserLink)
135-
return (
136-
currentUserCampCollaboration && !currentUserCampCollaboration._meta.loading
137-
)
138-
})
127+
).map((periods) => ({
128+
camp: periods[0].camp(),
129+
periods: periods,
130+
}))
139131
},
140132
pastCamps() {
141133
return Object.values(
@@ -162,6 +154,7 @@ export default {
162154
},
163155
methods: {
164156
async loadCamps() {
157+
await this.$auth.loadUser()
165158
// Only reload camps if they were loaded before, to avoid console error
166159
if (this.camps._meta.self !== null) {
167160
this.api.reload(this.camps)

0 commit comments

Comments
 (0)