Skip to content

Commit 4747685

Browse files
author
burdo
committed
next needed thing is addEditPopupLink
1 parent ca5f598 commit 4747685

21 files changed

Lines changed: 460 additions & 47 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package de.kaleidox.workbench.controller;
2+
3+
import de.kaleidox.workbench.model.EntityInfo;
4+
import de.kaleidox.workbench.util.Exceptions;
5+
import jakarta.persistence.EntityManager;
6+
import org.comroid.api.data.bind.DataStructure;
7+
import org.comroid.api.func.util.Pair;
8+
import org.hibernate.SessionFactory;
9+
import org.hibernate.metamodel.spi.MetamodelImplementor;
10+
import org.hibernate.persister.entity.SingleTableEntityPersister;
11+
import org.springframework.beans.factory.annotation.Autowired;
12+
import org.springframework.stereotype.Controller;
13+
import org.springframework.ui.Model;
14+
import org.springframework.web.bind.annotation.GetMapping;
15+
import org.springframework.web.bind.annotation.PathVariable;
16+
import org.springframework.web.bind.annotation.PostMapping;
17+
import org.springframework.web.bind.annotation.RequestBody;
18+
import org.springframework.web.bind.annotation.RequestMapping;
19+
import org.springframework.web.bind.annotation.RequestParam;
20+
21+
import java.util.Map;
22+
import java.util.concurrent.ConcurrentHashMap;
23+
24+
import static org.comroid.api.Polyfill.*;
25+
26+
@Controller
27+
@RequestMapping("/popup")
28+
public class PopupController {
29+
public static final Map<Class<?>, EntityInfo.Provider<?>> INFO_SERIALIZATION_PROVIDERS = new ConcurrentHashMap<>();
30+
@Autowired private EntityManager entityManager;
31+
32+
// /popup/edit/assignment/{entryInfo}/startTime/
33+
@GetMapping("/edit/{dtype}/{info}/{property}/show")
34+
public String edit(
35+
Model model,
36+
@PathVariable("dtype") String dtype,
37+
@PathVariable("info") String infoStr,
38+
@PathVariable("property") String property,
39+
@RequestParam("redirect_url") String redirectUrl
40+
) {
41+
var meta = getEditMeta(dtype, infoStr, property);
42+
var current = meta.getFirst().getFrom(meta.getSecond());
43+
44+
model.addAttribute("meta", meta);
45+
model.addAttribute("current", String.valueOf(current));
46+
model.addAttribute("redirect_url", redirectUrl);
47+
48+
return "popup/edit_value";
49+
}
50+
51+
@PostMapping("/edit/{dtype}/{info}/{property}/apply")
52+
public void edit(
53+
@PathVariable("dtype") String dtype,
54+
@PathVariable("info") String infoStr,
55+
@PathVariable("property") String property,
56+
@RequestBody String valueStr
57+
) {
58+
var meta = getEditMeta(dtype, infoStr, property);
59+
meta.getFirst().parseAndSet(meta.getSecond(), valueStr);
60+
}
61+
62+
private Pair<DataStructure<?>.Property<Object>, Object> getEditMeta(String dtype, String infoStr, String property) {
63+
var type = getEntityClassByDiscriminator(dtype);
64+
var struct = DataStructure.of(type);
65+
var provider = INFO_SERIALIZATION_PROVIDERS.entrySet()
66+
.stream()
67+
.filter(e -> e.getKey().isAssignableFrom(type))
68+
.findAny()
69+
.map(Map.Entry::getValue)
70+
.orElseThrow();
71+
var info = provider.backward(infoStr);
72+
var it = provider.findById(uncheckedCast(info)).orElseThrow();
73+
var prop = struct.getProperty(property).orElseThrow();
74+
return new Pair<>(prop, it);
75+
}
76+
77+
@SuppressWarnings("resource")
78+
private Class<?> getEntityClassByDiscriminator(String dtype) {
79+
// todo: remove deprecated api usage
80+
81+
var sessionFactory = entityManager.getEntityManagerFactory().unwrap(SessionFactory.class);
82+
var metamodel = (MetamodelImplementor) sessionFactory.getMetamodel();
83+
84+
for (var entityName : metamodel.getAllEntityNames()) {
85+
var persister = metamodel.entityPersister(entityName);
86+
if (persister instanceof SingleTableEntityPersister singleTablePersister) {
87+
String discriminatorValue = (String) singleTablePersister.getDiscriminatorValue();
88+
if (dtype.equals(discriminatorValue)) {
89+
return singleTablePersister.getMappedClass();
90+
}
91+
}
92+
}
93+
94+
throw Exceptions.noSuchDtype();
95+
}
96+
}

src/main/java/de/kaleidox/workbench/controller/TimetableController.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ public String create() {
7474

7575
@GetMapping("/{customerName}/{departmentName}/{startTime}")
7676
public String view(
77-
Model model, @PathVariable @Param("customerName") String customerName,
77+
Model model,
78+
@PathVariable @Param("customerName") String customerName,
7879
@PathVariable @Param("departmentName") String departmentName,
7980
@PathVariable @Param("startTime") LocalDateTime startTime
8081
) {
@@ -85,7 +86,26 @@ public String view(
8586
var eKey = new TimetableEntry.CompositeKey(customer, department, startTime);
8687
var entry = entries.findById(eKey).orElseThrow();
8788

88-
model.addAttribute("entry", entry);
89+
model.addAttribute("entry", entry)
90+
.addAttribute("dtype", "timetable_entry");
91+
8992
return "timetable/view_entry";
9093
}
94+
95+
@GetMapping("/{customerName}/{departmentName}/{startTime}/assignment/create")
96+
public String createAssignment(
97+
Model model, @PathVariable @Param("customerName") String customerName,
98+
@PathVariable @Param("departmentName") String departmentName,
99+
@PathVariable @Param("startTime") LocalDateTime startTime
100+
) {
101+
var customer = customers.findById(customerName).orElseThrow();
102+
var department = customer.findDepartment(departmentName)
103+
.orElseGet(() -> departments.getOrCreateDefault(customer));
104+
105+
var eKey = new TimetableEntry.CompositeKey(customer, department, startTime);
106+
var entry = entries.findById(eKey).orElseThrow();
107+
108+
model.addAttribute("entry", entry);
109+
return "timetable/assignment/create";
110+
}
91111
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package de.kaleidox.workbench.model;
2+
3+
import org.comroid.api.data.seri.StringSerializable;
4+
import org.comroid.api.data.seri.adp.StringSerializationProvider;
5+
6+
import java.util.Optional;
7+
8+
public interface EntityInfo extends StringSerializable {
9+
Object toCompositeKey();
10+
11+
interface Provider<T extends EntityInfo> extends StringSerializationProvider<T> {
12+
Optional<?> findById(T info);
13+
}
14+
}

src/main/java/de/kaleidox/workbench/model/entry/Timeframe.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package de.kaleidox.workbench.model.entry;
22

3+
import de.kaleidox.workbench.util.Exceptions;
4+
35
import java.time.LocalDateTime;
46

57
public interface Timeframe {
68
static void validateTimeframe(LocalDateTime startTime, LocalDateTime endTime) {
7-
if (startTime.isAfter(endTime)) throw new IllegalArgumentException("Start Time cannot be after End Time");
9+
if (startTime.isAfter(endTime)) throw Exceptions.invalidTimeframe();
810
}
911

1012
LocalDateTime getStartTime();

src/main/java/de/kaleidox/workbench/model/jpa/timetable/Assignment.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import de.kaleidox.workbench.model.jpa.representant.User;
66
import de.kaleidox.workbench.repo.UserRepository;
77
import de.kaleidox.workbench.util.ApplicationContextProvider;
8+
import de.kaleidox.workbench.util.Exceptions;
89
import jakarta.persistence.Embeddable;
910
import jakarta.persistence.ManyToOne;
1011
import lombok.Data;
@@ -17,7 +18,7 @@
1718
@Data
1819
@Embeddable
1920
@EqualsAndHashCode(of = "user")
20-
public class Assignment implements Timeframe {
21+
public class Assignment implements Timeframe, TimetableEntryReferent {
2122
public static Assignment parse(String parse) {
2223
var split = parse.split(": *");
2324
var username = split[0];
@@ -55,6 +56,7 @@ public record CreateData(
5556
@Nullable String notes
5657
) {
5758
public CreateData {
59+
if (username == null) throw Exceptions.noSuchUser();
5860
if (startTime != null && endTime != null) Timeframe.validateTimeframe(startTime, endTime);
5961
}
6062
}

src/main/java/de/kaleidox/workbench/model/jpa/timetable/Interruption.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020
@NoArgsConstructor
2121
@AllArgsConstructor
2222
@EqualsAndHashCode(of = "time")
23-
public class Interruption {
23+
public class Interruption implements TimetableEntryReferent {
2424
public static Interruption parse(String parse) {
2525
var split = parse.split(", ");
26-
var time = TimetableEntry.HOUR_FORMATTER.parse(split[0]);
26+
var time = TimetableEntry.parseDateTime(split[0]);
2727
var duration = Polyfill.parseDuration(split[1]);
28-
return new Interruption().setTime(LocalDateTime.from(time)).setDuration(duration);
28+
return new Interruption().setTime(time).setDuration(duration);
2929
}
3030

3131
LocalDateTime time;

src/main/java/de/kaleidox/workbench/model/jpa/timetable/TimetableEntry.java

Lines changed: 97 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package de.kaleidox.workbench.model.jpa.timetable;
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import de.kaleidox.workbench.controller.PopupController;
5+
import de.kaleidox.workbench.model.EntityInfo;
46
import de.kaleidox.workbench.model.entry.Timeframe;
57
import de.kaleidox.workbench.model.jpa.representant.User;
68
import de.kaleidox.workbench.model.jpa.representant.customer.Customer;
79
import de.kaleidox.workbench.model.jpa.representant.customer.Department;
10+
import de.kaleidox.workbench.repo.CustomerRepository;
11+
import de.kaleidox.workbench.repo.TimetableEntryRepository;
12+
import de.kaleidox.workbench.util.Exceptions;
813
import jakarta.persistence.Basic;
914
import jakarta.persistence.ElementCollection;
1015
import jakarta.persistence.Embeddable;
@@ -14,31 +19,52 @@
1419
import jakarta.persistence.ManyToOne;
1520
import lombok.Data;
1621
import lombok.EqualsAndHashCode;
22+
import org.comroid.annotations.Instance;
1723
import org.jetbrains.annotations.NotNull;
1824
import org.jetbrains.annotations.Nullable;
1925

26+
import java.time.LocalDate;
2027
import java.time.LocalDateTime;
28+
import java.time.LocalTime;
2129
import java.time.format.DateTimeFormatter;
2230
import java.util.ArrayList;
2331
import java.util.Collection;
32+
import java.util.Optional;
2433

34+
import static de.kaleidox.workbench.util.ApplicationContextProvider.*;
2535
import static java.time.format.DateTimeFormatter.*;
36+
import static org.comroid.api.Polyfill.*;
2637

2738
@Data
2839
@Entity
2940
@IdClass(TimetableEntry.CompositeKey.class)
3041
@EqualsAndHashCode(of = { "customer", "department", "startTime" })
31-
public class TimetableEntry implements Timeframe {
42+
public class TimetableEntry implements Timeframe, TimetableEntryReferent {
3243
public static final DateTimeFormatter DATE_FORMATTER = ofPattern("EE dd.MM.yy");
3344
public static final DateTimeFormatter HOUR_FORMATTER = ofPattern("HH:mm");
34-
@Id @ManyToOne Customer customer;
35-
@Id @ManyToOne Department department;
36-
@Id @Basic LocalDateTime startTime;
37-
@Basic LocalDateTime endTime;
38-
@Nullable String notes;
39-
@ManyToOne User createdBy;
40-
@ElementCollection Collection<Interruption> interruptions = new ArrayList<>();
41-
@ElementCollection Collection<Assignment> assignments = new ArrayList<>();
45+
46+
@SuppressWarnings("unused")
47+
public static LocalDate parseDate(String str) {
48+
return LocalDate.from(DATE_FORMATTER.parse(str));
49+
}
50+
51+
@SuppressWarnings("unused")
52+
public static LocalTime parseTime(String str) {
53+
return LocalTime.from(HOUR_FORMATTER.parse(str));
54+
}
55+
56+
public static LocalDateTime parseDateTime(String str) {
57+
return LocalDateTime.parse(str);
58+
}
59+
60+
@Id @ManyToOne Customer customer;
61+
@Id @ManyToOne Department department;
62+
@Id @Basic LocalDateTime startTime;
63+
@Basic LocalDateTime endTime;
64+
@Nullable String notes;
65+
@ManyToOne User createdBy;
66+
@ElementCollection Collection<Interruption> interruptions = new ArrayList<>();
67+
@ElementCollection Collection<Assignment> assignments = new ArrayList<>();
4268

4369
@JsonIgnore
4470
public String getViewUrlPath() {
@@ -78,12 +104,72 @@ public String getAssignmentsSummaryText() {
78104
};
79105
}
80106

107+
@JsonIgnore
108+
public CompositeKey key() {
109+
return new CompositeKey(this);
110+
}
111+
112+
@JsonIgnore
113+
public Info info() {
114+
return new Info(this);
115+
}
116+
81117
@Embeddable
82118
public record CompositeKey(
83119
@ManyToOne Customer customer, @ManyToOne Department department, LocalDateTime startTime
84-
) {}
120+
) {
121+
public CompositeKey(TimetableEntry entry) {
122+
this(entry.customer, entry.department, entry.startTime);
123+
}
124+
}
125+
126+
public record Info(String customerName, String departmentName, LocalDateTime startTime) implements EntityInfo {
127+
public static Info parse(String str) {
128+
var split = str.split(",");
129+
return new Info(split[0], split[1], TimetableEntry.parseDateTime(split[2]));
130+
}
131+
132+
public Info(TimetableEntry entry) {
133+
this(entry.customer.getName(), entry.department.getName(), entry.startTime);
134+
}
135+
136+
@Override
137+
public String toSerializedString() {
138+
return "%s,%s,%s";
139+
}
85140

86-
public record Info(String customerName, String departmentName, LocalDateTime startTime) {}
141+
@Override
142+
public Object toCompositeKey() {
143+
var customer = bean(CustomerRepository.class).findById(customerName)
144+
.orElseThrow(Exceptions::noSuchCustomer);
145+
return new CompositeKey(customer,
146+
customer.findDepartment(departmentName).orElseThrow(Exceptions::noSuchDepartment),
147+
startTime);
148+
}
149+
150+
public enum KeyProvider implements EntityInfo.Provider<Info> {
151+
@Instance INSTANCE;
152+
153+
KeyProvider() {
154+
PopupController.INFO_SERIALIZATION_PROVIDERS.put(TimetableEntryReferent.class, this);
155+
}
156+
157+
@Override
158+
public Optional<?> findById(Info info) {
159+
return bean(TimetableEntryRepository.class).findById(uncheckedCast(info.toCompositeKey()));
160+
}
161+
162+
@Override
163+
public String forward(Info info) {
164+
return info.toSerializedString();
165+
}
166+
167+
@Override
168+
public Info backward(String str) {
169+
return parse(str);
170+
}
171+
}
172+
}
87173

88174
public record CreateData(
89175
@NotNull String customerName,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package de.kaleidox.workbench.model.jpa.timetable;
2+
3+
public interface TimetableEntryReferent {
4+
}

src/main/java/de/kaleidox/workbench/repo/UserRepository.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,29 @@
33
import de.kaleidox.workbench.model.jpa.representant.User;
44
import org.comroid.api.func.util.Debug;
55
import org.jetbrains.annotations.ApiStatus;
6+
import org.springframework.data.jpa.repository.Query;
67
import org.springframework.data.repository.CrudRepository;
78
import org.springframework.security.core.Authentication;
9+
import org.springframework.stereotype.Controller;
810
import org.springframework.stereotype.Repository;
11+
import org.springframework.web.bind.annotation.GetMapping;
12+
import org.springframework.web.bind.annotation.RequestMapping;
13+
import org.springframework.web.bind.annotation.ResponseBody;
914

15+
import java.util.Collection;
1016
import java.util.Optional;
1117

1218
import static java.util.Optional.*;
1319

20+
@Controller
1421
@Repository
22+
@RequestMapping("/api/users")
1523
public interface UserRepository extends CrudRepository<User, String> {
24+
@ResponseBody
25+
@GetMapping("/names")
26+
@Query("select u.username from User u")
27+
Collection<String> names();
28+
1629
@ApiStatus.Internal
1730
default Optional<User> get(Authentication auth) {
1831
if (auth == null) return Debug.isDebug() ? of(save(User.DEV)) : empty();

0 commit comments

Comments
 (0)