Skip to content

Commit 2ffef47

Browse files
committed
Keep track of when and who last set the isShared flag
1 parent 871da79 commit 2ffef47

File tree

8 files changed

+114
-1
lines changed

8 files changed

+114
-1
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20250903125914 extends AbstractMigration {
14+
public function getDescription(): string {
15+
return 'Add sharedSince and sharedBy to camp';
16+
}
17+
18+
public function up(Schema $schema): void {
19+
// this up() migration is auto-generated, please modify it to your needs
20+
$this->addSql('ALTER TABLE camp ADD sharedSince TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
21+
$this->addSql('ALTER TABLE camp ADD sharedById VARCHAR(16) DEFAULT NULL');
22+
$this->addSql('ALTER TABLE camp ADD CONSTRAINT FK_C19442304B2BC976 FOREIGN KEY (sharedById) REFERENCES "user" (id) NOT DEFERRABLE');
23+
$this->addSql('CREATE INDEX IDX_C19442304B2BC976 ON camp (sharedById)');
24+
}
25+
26+
public function down(Schema $schema): void {
27+
// this down() migration is auto-generated, please modify it to your needs
28+
$this->addSql('ALTER TABLE camp DROP CONSTRAINT FK_C19442304B2BC976');
29+
$this->addSql('DROP INDEX IDX_C19442304B2BC976');
30+
$this->addSql('ALTER TABLE camp DROP sharedSince');
31+
$this->addSql('ALTER TABLE camp DROP sharedById');
32+
}
33+
}

api/src/Entity/Camp.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use App\Serializer\Normalizer\RelatedCollectionLink;
1717
use App\State\CampCreateProcessor;
1818
use App\State\CampRemoveProcessor;
19+
use App\State\CampUpdateProcessor;
1920
use App\Util\EntityMap;
2021
use App\Validator\AssertContainsAtLeastOneManager;
2122
use Doctrine\Common\Collections\ArrayCollection;
@@ -38,6 +39,7 @@
3839
normalizationContext: self::ITEM_NORMALIZATION_CONTEXT,
3940
),
4041
new Patch(
42+
processor: CampUpdateProcessor::class,
4143
security: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)',
4244
denormalizationContext: ['groups' => ['write', 'update']],
4345
normalizationContext: self::ITEM_NORMALIZATION_CONTEXT,
@@ -196,6 +198,24 @@ class Camp extends BaseEntity implements BelongsToCampInterface, CopyFromPrototy
196198
#[ORM\Column(type: 'boolean', nullable: false, options: ['default' => false])]
197199
public bool $isShared = false;
198200

201+
/**
202+
* Date and time when the camp was last set to be shared publicly.
203+
*/
204+
#[ApiProperty(example: '2025-10-01T00:00:00+00:00', required: true, openapiContext: ['format' => 'date-time'])]
205+
#[Groups(['read'])]
206+
#[ORM\Column(type: 'datetime', nullable: true)]
207+
public ?\DateTimeInterface $sharedSince = null;
208+
209+
/**
210+
* The person who last set the camp to be shared publicly.
211+
*/
212+
#[Assert\DisableAutoMapping]
213+
#[ApiProperty(writable: false)]
214+
#[Groups(['read'])]
215+
#[ORM\ManyToOne(targetEntity: User::class)]
216+
#[ORM\JoinColumn(nullable: true)]
217+
public ?User $sharedBy = null;
218+
199219
/**
200220
* Whether this camp may serve as a template for creating other camps.
201221
*/
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace App\State;
4+
5+
use ApiPlatform\State\ProcessorInterface;
6+
use App\Entity\Camp;
7+
use App\Entity\CampCollaboration;
8+
use App\State\Util\AbstractPersistProcessor;
9+
use App\State\Util\PropertyChangeListener;
10+
use Symfony\Bundle\SecurityBundle\Security;
11+
12+
/**
13+
* @template-extends AbstractPersistProcessor<CampCollaboration>
14+
*/
15+
class CampUpdateProcessor extends AbstractPersistProcessor {
16+
public function __construct(
17+
ProcessorInterface $decorated,
18+
private Security $security,
19+
) {
20+
$sharingChangeListener = PropertyChangeListener::of(
21+
extractProperty: fn (Camp $data) => $data->isShared,
22+
beforeAction: fn (Camp $data) => $this->onBeforeStatusChange($data),
23+
);
24+
25+
parent::__construct(
26+
$decorated,
27+
propertyChangeListeners: [
28+
$sharingChangeListener,
29+
]
30+
);
31+
}
32+
33+
public function onBeforeStatusChange(Camp $data): Camp {
34+
if (true == $data->isShared) {
35+
$data->sharedSince = new \DateTime();
36+
$data->sharedBy = $this->security->getUser();
37+
}
38+
39+
return $data;
40+
}
41+
}

common/helpers/dateHelperUTCFormatted.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ function dateLong(dateTimeString, tc) {
88
return dayjs.utc(dateTimeString).format(tc('global.datetime.dateLong'))
99
}
1010

11+
function dateTimeLong(dateTimeString, tc) {
12+
return dayjs.utc(dateTimeString).format(tc('global.datetime.dateTimeLong'))
13+
}
14+
1115
function hourShort(dateTimeString, tc) {
1216
return dayjs.utc(dateTimeString).format(tc('global.datetime.hourShort'))
1317
}
@@ -101,6 +105,7 @@ function dateRange(start, end, tc) {
101105
export {
102106
dateShort,
103107
dateLong,
108+
dateTimeLong,
104109
timeDurationShort,
105110
hourShort,
106111
hourLong,

common/helpers/userDisplayName.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
* Returns a display name for a user
33
*/
44
export default function (user) {
5-
return user.displayName || ''
5+
return user?.displayName || ''
66
}

frontend/src/components/campAdmin/CampSharingSettings.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
}}</router-link>
2525
</template>
2626
</i18n>
27+
<v-list-item-subtitle v-if="camp.isShared" class="pb-1">{{
28+
$tc('components.campAdmin.campSharingSettings.sharedSince', 1, {
29+
sharedSince: dateTimeLong(camp.sharedSince),
30+
sharedBy: userDisplayName(camp.sharedBy()),
31+
})
32+
}}</v-list-item-subtitle>
2733
</v-list-item-content>
2834
<v-list-item-action v-if="camp.isShared" class="align-self-start">
2935
<v-tooltip top>
@@ -61,6 +67,8 @@ import ContentGroup from '@/components/layout/ContentGroup.vue'
6167
import ApiForm from '../form/api/ApiForm.vue'
6268
import router, { adminRoute, campRoute } from '@/router.js'
6369
import { campRoleMixin } from '@/mixins/campRoleMixin.js'
70+
import userDisplayName from '@/common/helpers/userDisplayName.js'
71+
import { dateTimeLong } from '@/common/helpers/dateHelperUTCFormatted.js'
6472
6573
export default {
6674
name: 'CampSharingSettings',
@@ -85,6 +93,10 @@ export default {
8593
},
8694
},
8795
methods: {
96+
userDisplayName,
97+
dateTimeLong(dateTimeString) {
98+
return dateTimeLong(dateTimeString, this.$tc.bind(this))
99+
},
88100
async copyCampUrlToClipboard() {
89101
await navigator.clipboard.writeText(this.campUrl)
90102

frontend/src/locales/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
"description": "Alle Personen, die den Link zum Lager kennen, können das Programm, das Team, die Materiallisten und Verantwortlichkeiten und alles andere in diesem Lager ansehen. Nur Personen im {team} können Daten ändern.",
137137
"title": "Lager ist öffentlich freigegeben"
138138
},
139+
"sharedSince": "Zuletzt freigegeben am {sharedSince} von {sharedBy}",
139140
"team": "Team",
140141
"title": "Lager teilen"
141142
},

frontend/src/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
"description": "All people who know the link to the camp can see the programme, team, material lists, responsibilities and all other data in this camp. Only people in the {team} can change data.",
137137
"title": "Camp is shared publicly"
138138
},
139+
"sharedSince": "Currently shared since {sharedSince} by {sharedBy}",
139140
"team": "team",
140141
"title": "Sharing settings"
141142
},

0 commit comments

Comments
 (0)