Skip to content

Commit 2666fd4

Browse files
committed
Fixing limited editing
1 parent 9e8efe9 commit 2666fd4

8 files changed

Lines changed: 247 additions & 19 deletions

File tree

src/main/java/no/java/submit/controller/TalkController.java

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import no.java.submit.service.ConferenceService;
2020
import no.java.submit.service.TalksService;
2121
import no.java.submit.service.TimelineService;
22+
import no.java.submit.util.SessionHelper;
2223
import no.java.submit.util.SessionSecretHelper;
2324
import no.java.submit.util.UserHelper;
2425
import org.eclipse.microprofile.config.inject.ConfigProperty;
@@ -56,6 +57,9 @@ public class TalkController {
5657
@Inject
5758
Template sessionForm;
5859

60+
@Inject
61+
Template sessionFormClosed;
62+
5963
@Inject
6064
Template all;
6165

@@ -84,7 +88,7 @@ public TemplateInstance view(@PathParam("sessionId") String sessionId, @Context
8488
try {
8589
var session = talksService.getSession(email, sessionId);
8690

87-
if (!session.containsEmail(email))
91+
if (!email.isEmpty() && !session.containsEmail(email) && !appAdmins.contains(email))
8892
throw new NotAuthorizedException("Not allowed to view this session");
8993

9094
return talk
@@ -203,13 +207,13 @@ public TemplateInstance editSession(String sessionId, String email, String secre
203207
try {
204208
var session = talksService.getSession(email, sessionId);
205209

206-
if (!email.isEmpty() && !session.containsEmail(email))
207-
throw new NotAuthorizedException("Not allowed to view this session");
210+
if (!email.isEmpty() && !session.containsEmail(email) && !appAdmins.contains(email))
211+
throw new NotAuthorizedException("Not allowed to edit this session");
208212

209213
if (!conferenceService.current().id.equals(session.conferenceId))
210214
throw new NotFoundException();
211215

212-
return sessionForm
216+
return (timelineService.isClosed(appAdmins.contains(email)) ? sessionFormClosed : sessionForm)
213217
.data("form", SessionForm.parse(session))
214218
.data("val", Collections.emptyMap())
215219
.data("sessionId", sessionId)
@@ -224,23 +228,58 @@ public TemplateInstance editSession(String sessionId, String email, String secre
224228
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
225229
@Authenticated
226230
public Object editSessionPost(@PathParam("sessionId") String sessionId, SessionForm form, @Context SecurityIdentity securityIdentity) {
231+
var email = UserHelper.getEmail(securityIdentity);
232+
233+
return editSessionPost(sessionId, form, email, null);
234+
}
235+
236+
@POST
237+
@Path("{sessionId}/{secret}/edit")
238+
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
239+
public Object editSessionPost(@PathParam("sessionId") String sessionId, @PathParam("secret") String secret, SessionForm form) {
240+
sessionSecrets.validate(sessionId, secret);
241+
242+
return editSessionPost(sessionId, form, "", secret);
243+
}
244+
245+
public Object editSessionPost(String sessionId, SessionForm form, String email, String secret) {
227246
// Validate form and present form if there are any errors
228247
var validation = validator.validate(form);
229248
if (!validation.isEmpty()) {
230-
return sessionForm
249+
return (timelineService.isClosed(appAdmins.contains(email)) ? sessionFormClosed : sessionForm)
231250
.data("form", form)
232251
.data("val", validation.stream().collect(Collectors.groupingBy(c -> c.getPropertyPath().toString())))
233-
.data("sessionId", sessionId);
252+
.data("sessionId", sessionId)
253+
.data("secret", secret);
234254
}
235255

256+
// Fetch current session
257+
var session = talksService.getSession(email, sessionId);
258+
259+
if (!email.isEmpty() && !session.containsEmail(email) && !appAdmins.contains(email))
260+
throw new NotAuthorizedException("Not allowed to edit this session");
261+
236262
// Prepare form for sending
237-
var session = form.asSession();
238-
session.postedBy = UserHelper.getEmail(securityIdentity);
239-
session.sessionId = sessionId;
263+
var newSession = form.asSession();
264+
265+
if (timelineService.isClosed(appAdmins.contains(email))) {
266+
SessionHelper.partialUpdate(session, newSession);
267+
} else {
268+
// Update session with new data
269+
session.data = newSession.data;
270+
session.speakers = newSession.speakers;
271+
}
240272

241273
// Send form data
242274
talksService.updateSession(session.postedBy, sessionId, session);
243275

276+
if (secret != null) {
277+
// Redirect to preview page with secret
278+
return Response
279+
.seeOther(UriBuilder.fromUri("/talk/{sessionId}/{secret}").build(sessionId, secret))
280+
.build();
281+
}
282+
244283
// Redirect to preview page
245284
return Response
246285
.seeOther(UriBuilder.fromUri("/talk/{sessionId}").build(sessionId))

src/main/java/no/java/submit/service/TalksService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ public boolean containsEmail(String email) {
8181

8282
return false;
8383
}
84+
85+
public Speaker getSpeaker(String name) {
86+
for (Speaker speaker : speakers) {
87+
if (speaker.name.equals(name)) {
88+
return speaker;
89+
}
90+
}
91+
return null;
92+
}
8493
}
8594

8695
class Speaker {

src/main/java/no/java/submit/service/TimelineService.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ public class TimelineService {
2323
@ConfigProperty(name = "timeline.closing")
2424
Instant closing;
2525

26-
@ConfigProperty(name = "timeline.finalized")
27-
Instant finalized;
28-
2926
@ConfigProperty(name = "timeline.feedback")
3027
Instant feedback;
3128

@@ -59,10 +56,6 @@ public boolean isClosed(boolean hasExtension) {
5956
return getClosingHard().isBefore(LocalDateTime.now());
6057
}
6158

62-
public boolean isFinalized() {
63-
return finalized != null && finalized.isBefore(Instant.now());
64-
}
65-
6659
public String format(LocalDate date) {
6760
return formatter.format(date).substring(0, 1).toUpperCase() +
6861
formatter.format(date).substring(1) +

src/main/java/no/java/submit/util/SessionHelper.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import no.java.submit.service.TalksService;
44

55
import java.util.ArrayList;
6+
import java.util.Arrays;
67
import java.util.List;
78
import java.util.Map;
89

@@ -27,4 +28,27 @@ static List<String> getTags(TalksService.Session session) {
2728

2829
return result;
2930
}
31+
32+
static void partialUpdate(TalksService.Session session, TalksService.Session updates) {
33+
for (var key : Arrays.asList("title", "abstract", "outline", "intendedAudience", "equipment", "participation", "workshopPrerequisites", "suggestedKeywords", "infoToProgramCommittee")) {
34+
if (updates.data.containsKey(key))
35+
session.data.put(key, updates.data.get(key));
36+
else
37+
session.data.remove(key);
38+
}
39+
40+
for (var speaker : session.speakers) {
41+
var updated = updates.getSpeaker(speaker.name);
42+
43+
if (updated != null) {
44+
speaker.id = null;
45+
for (var key : Arrays.asList("email", "twitter", "bluesky", "linkedin", "residence", "zipCode", "bio")) {
46+
if (updated.data.containsKey(key))
47+
speaker.data.put(key, updated.data.get(key));
48+
else
49+
speaker.data.remove(key);
50+
}
51+
}
52+
}
53+
}
3054
}

src/main/resources/application.properties

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ quarkus.cache.caffeine."sessions".expire-after-write=5m
1717
conference.current=ffbdc06b-b570-4409-bf2f-7d3b5dd2aed3
1818
timeline.opening=2025-02-14T00:00:00Z
1919
timeline.closing=2025-04-28T23:59:59Z
20-
timeline.finalized=2025-05-31T00:00:00Z
2120
timeline.feedback=2025-06-30T00:00:00Z
2221
timeline.refund=2025-08-01T00:00:00Z
2322

src/main/resources/templates/partial/forms.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
{#fragment inputText}
2+
{@Boolean disabled=false}
23
<div class="field{#if required} required{/if}{#if val.containsKey(path)} error{/if}">
34
<label for="form-{path}">{title}</label>
45
{#if description != null}
56
<p>{description}</p>
67
{/if}
7-
<input type="{type}" name="{path}" value="{value}" id="form-{path}"{#if placeholder != null} placeholder="{placeholder}"{/if}{#if required} required="required"{/if}/>
8+
{#if disabled}
9+
<input type="hidden" name="{path}" value="{value}"/>
10+
{/if}
11+
<input type="{type}" name="{path}" value="{value}" id="form-{path}"{#if placeholder != null} placeholder="{placeholder}"{/if}{#if required} required="required"{/if}{#if disabled} disabled="disabled"{/if}/>
812
{#include partial/forms$validation /}
913
</div>
1014
{/fragment}
1115

1216
{#fragment textarea}
17+
{@Boolean disabled=false}
1318
<div class="field{#if required} required{/if}{#if val.containsKey(path)} error{/if}">
1419
<label for="form-{path}">{title}</label>
1520
{#if description != null}
1621
<p>{description}</p>
1722
{/if}
18-
<textarea name="{path}" rows="{rows}" id="form-{path}">{value}</textarea>
23+
<textarea name="{path}" rows="{rows}" id="form-{path}"{#if disabled} disabled="disabled"{/if}>{value}</textarea>
1924
{#include partial/forms$validation /}
2025
</div>
2126
{/fragment}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
{#include partial/base}
2+
{#if sessionId == null}
3+
<h1>New talk</h1>
4+
{#else}
5+
<h1>Edit talk</h1>
6+
{/if}
7+
8+
{#include partial/forms$validation path="speakers" /}
9+
10+
<form id="talkForm" method="post" class="talk">
11+
<h2>About</h2>
12+
13+
{#include partial/forms$inputText
14+
title="Title" path="title" value=form.title type="text" required=true
15+
placeholder="A short and snappy title"
16+
description="Name your lightning talk, presentation or workshop in a way that draws attention to your submission while also capturing its content. Many participants will use this title to decide if they even read your abstract, so we recommend you to think a few minutes about this name." /}
17+
18+
<div class="split">
19+
<div class="field required{#if val.containsKey("kind")} error{/if}"">
20+
<label for="form-kind">Kind</label>
21+
<p>In which format are you presenting your talk and how long is it? This is not subject to adjustment by the program committee, so please submit additional proposals if your content may fit multiple alternatives.</p>
22+
<input type="hidden" name="kind" value="{form.kind}"/>
23+
<select name="kind" id="form-kind" disabled="disabled">
24+
{#for k in Kind:values}
25+
{#if k.active}
26+
<option value="{k}"{#if form.kind == k} selected="selected"{/if}>{k.name}</option>
27+
{/if}
28+
{/for}
29+
</select>
30+
{#include partial/forms$validation path="kind" /}
31+
</div>
32+
33+
<div class="field required{#if val.containsKey("language")} error{/if}"">
34+
<label for="form-language">Language</label>
35+
<p>In which language will you be holding the talk? It is permitted to use English in your slides, even though you may be speaking in Norwegian, but you should fill out this form in the language you will speak in. We generally recommend that you hold the talk in the language you are most comfortable with, as we do not favor one language over the other.</p>
36+
<input type="hidden" name="language" value="{form.language}"/>
37+
<select name="language" id="form-language" disabled="disabled">
38+
{#for v in Language:values}
39+
<option value="{v}"{#if form.language == v} selected="selected"{/if}>{v.name}</option>
40+
{/for}
41+
</select>
42+
{#include partial/forms$validation path="language" /}
43+
</div>
44+
</div>
45+
46+
{#include partial/forms$textarea
47+
title="Description" path="abstractText" value=form.abstractText rows="5" required=true
48+
placeholder=null
49+
description="Give a concise description of the content and goals of your talk and what audience this presentation or workshop is for. Try not to exceed 300 words, as shorter and more to-the-point descriptions are more likely to be read by participants." /}
50+
51+
{#include partial/forms$inputText
52+
title="Expected audience" path="intendedAudience" value=form.intendedAudience type="text" required=true
53+
placeholder=null
54+
description="Who should attend this session? How will the participants benefit from attending? What experience (if any) should the audience have to get the most value out of your talk?" /}
55+
56+
<div id="form-kind-only"{#if !form.kind.isWorkshop()}style="display: none;"{/if}>
57+
{#include partial/forms$textarea
58+
title="Workshop prerequisites" path="workshopPrerequisites" value=form.workshopPrerequisites rows="5" required=true
59+
placeholder=null
60+
description="If your workshop requires participants to make any preparations in advance (create an account at a cloud services provider, install a specific IDE etc.), please specify it." /}
61+
</div>
62+
63+
{#include partial/forms$inputText
64+
title="Suggested keywords" path="suggestedKeywords" value=form.suggestedKeywords type="text" required=false
65+
placeholder=null
66+
description="Suggest up to five keywords that describe your talk. Note that we only use these keywords as an orientation and often edit them to better fit into our published program." /}
67+
68+
<h2>Internal information (not public)</h2>
69+
70+
{#include partial/forms$textarea
71+
title="Outline" path="outline" value=form.outline rows="10" required=true
72+
placeholder=null
73+
description="The information will be used by the Program Committee to review the details of your talk. The outline should be a rough agenda for the talk, with a short description for each section, and with a rough estimate of the time spent on each. Omitting this section will reduce the chances of your submission being accepted." /}
74+
75+
{#include partial/forms$textarea
76+
title="Equipment" path="equipment" value=form.equipment rows="3" required=false
77+
placeholder=null
78+
description="Please specify any additional special equipment you may need. Note all will get access to WiFi and a projector." /}
79+
80+
{#include partial/forms$inputText
81+
title="Your participation" path="participation" value=form.participation type="text" required=true
82+
placeholder="Indicate if you are attending both days, or only one of them"
83+
description="We consider any speaker as a participant of our conference, and we hope that you can stay for both conference days. There is much to see and do during JavaZone, we promise that it will be a good investment of your time! If you cannot participate on both days, please let us know, such that we can consider this when creating the program. To simplify our planning, we can unfortunately not consider time preferences on a given day." /}
84+
85+
{#include partial/forms$textarea
86+
title="Additional information for the program commitee" path="infoToProgramCommittee" value=form.infoToProgramCommittee rows="3" required=false
87+
placeholder=null
88+
description="Please include any information relevant to the program committee that you do not want to include in your public presentation profile. For example, you can give us your motivation for speaking at JavaZone, include links to videos and slides from previous speaker engagements or any other relevant information for choosing your talk." /}
89+
90+
<p>All talks at JavaZone are filmed, live-streamed and published for free at Vimeo after the conference. Should you have any reservations about this, let the program committee know in the field above. This might affect your chance of getting selected, but if you have a good reason please let us know.</p>
91+
92+
<div id="speakers">
93+
{#for speaker in form.speakers}
94+
{#fragment id=speaker}
95+
<div class="speaker">
96+
<h2>Speaker {speaker_count}</h2>
97+
98+
{#include partial/forms$inputText
99+
title="Name" path=str:format("speakers[%s].name", speaker_index) value=speaker.name type="text" required=true disabled=true
100+
placeholder=null
101+
description=null /}
102+
103+
<div class="split">
104+
{#include partial/forms$inputText
105+
title="Email (not public)" path=str:format("speakers[%s].email", speaker_index) value=speaker.email type="email" required=true
106+
placeholder=null
107+
description=null /}
108+
109+
{#include partial/forms$inputText
110+
title="Twitter" path=str:format("speakers[%s].twitter", speaker_index) value=speaker.twitter type="text" required=false
111+
placeholder="Twitter handle"
112+
description=null /}
113+
</div>
114+
115+
<div class="split">
116+
{#include partial/forms$inputText
117+
title="Bluesky" path=str:format("speakers[%s].bluesky", speaker_index) value=speaker.bluesky type="text" required=false
118+
placeholder="Bluesky handle"
119+
description=null /}
120+
121+
{#include partial/forms$inputText
122+
title="LinkedIn" path=str:format("speakers[%s].linkedin", speaker_index) value=speaker.linkedin type="text" required=false
123+
placeholder="Link to LinkedIn profile"
124+
description=null /}
125+
</div>
126+
127+
<div class="split">
128+
{#include partial/forms$inputText
129+
title="Country of residence (not public)" path=str:format("speakers[%s].residence", speaker_index) value=speaker.residence type="text" required=false
130+
placeholder="Country of residence"
131+
description="This is the country you will be travelling from to JavaZone. This information is intended to give us a better overview and will not be used in the selection process." /}
132+
133+
{#include partial/forms$inputText
134+
title="Norwegian Postal Code (not public)" path=str:format("speakers[%s].zipCode", speaker_index) value=speaker.zipCode type="text" required=false
135+
placeholder="Norwegian postcode"
136+
description="JavaBin is always looking for good speakers to the local JavaBin user groups. By inserting your postal code may it be easier to find your." /}
137+
</div>
138+
139+
{#include partial/forms$textarea
140+
title="Bio" path=str:format("speakers[%s].bio", speaker_index) value=speaker.bio rows="3" required=true
141+
placeholder="Tell the audience who this speaker is, and why she/he is the perfect person to hold this talk."
142+
description="Short description of the speaker (try not to exceed 150 words)" /}
143+
</div>
144+
{/fragment}
145+
{/for}
146+
</div>
147+
148+
<div class="buttons">
149+
<a href="{#if sessionId == null}/{#else}/talk/{sessionId}{#if secret != null}/{secret}{/if}{/if}">Cancel</a>
150+
<button type="submit">Save</button>
151+
</div>
152+
</form>
153+
154+
{/include}

src/main/resources/web/app/app.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,11 @@ form.talk {
249249
}
250250
}
251251

252+
input:disabled, textarea:disabled, select:disabled {
253+
color: var(--subtext-color);
254+
cursor: not-allowed;
255+
}}
256+
252257
&.required label::after {
253258
content: "*";
254259
padding: 0 3pt;

0 commit comments

Comments
 (0)