Compare commits

...

10 Commits

Author SHA1 Message Date
Laurent
c27c525cda dunno 2025-03-22 17:24:05 +01:00
Laurent
bacdc94b1e Fix responsive 2025-03-22 15:06:52 +01:00
Laurent
577603df64 Add graph 2025-03-22 14:56:26 +01:00
Laurent
ba31dd3d05 Fuck TS 2025-03-22 09:53:23 +01:00
Laurent
546506cf59 Add attendance graph 2025-03-22 09:50:04 +01:00
Laurent
4ef9d588f6 Fix mapping to model 2025-03-22 08:16:31 +01:00
Laurent
0c5851b146 Fixed a bug 2025-03-21 20:20:58 +01:00
Laurent
be5e86c10d holy fuck. 2025-03-21 17:57:07 +01:00
Laurent
02ce43e7b7 Adapting title font size 2025-03-21 08:39:28 +01:00
Laurent
b31884e626 Fuck JS 2025-03-20 20:38:16 +01:00
60 changed files with 700 additions and 554 deletions

View File

@@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class LetsmeetApplication { public class LetsmeetApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(LetsmeetApplication.class, args); SpringApplication.run(LetsmeetApplication.class, args);
} }
} }

View File

@@ -8,6 +8,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter; import org.springframework.web.filter.CorsFilter;
@@ -16,6 +17,7 @@ import java.util.Arrays;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableTransactionManagement
public class AppSecurity { public class AppSecurity {
private final AppConfigurations conf; private final AppConfigurations conf;
@@ -29,10 +31,11 @@ public class AppSecurity {
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http return http
.cors(cors -> {}) .cors(cors -> {
})
.csrf(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests((requests) -> requests .authorizeHttpRequests((requests) -> requests
.requestMatchers("/**" ).permitAll() .requestMatchers("/**").permitAll()
//.anyRequest().authenticated() //.anyRequest().authenticated()
) )
.formLogin((form) -> form .formLogin((form) -> form

View File

@@ -1,9 +1,6 @@
package be.naaturel.letsmeet.configurations; package be.naaturel.letsmeet.configurations;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

View File

@@ -4,8 +4,6 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import java.io.Console;
public class Interceptor implements HandlerInterceptor { public class Interceptor implements HandlerInterceptor {
// Request is intercepted by this method before reaching the Controller // Request is intercepted by this method before reaching the Controller

View File

@@ -13,18 +13,18 @@ public class EventController {
private final EventService service; private final EventService service;
@Autowired @Autowired
public EventController(EventService service){ public EventController(EventService service) {
this.service = service; this.service = service;
} }
@GetMapping({"/event/{token}/", "/event/{token}"}) @GetMapping({"/event/{token}/", "/event/{token}"})
public ResponseEntity<?> get(@PathVariable String token){ public ResponseEntity<?> get(@PathVariable String token) {
try{ try {
EventDTO dto = service.getEvent(token); EventDTO dto = service.getEvent(token);
return ResponseEntity.ok(dto); return ResponseEntity.ok(dto);
} catch (Exception e){ } catch (Exception e) {
return ResponseEntity return ResponseEntity
.internalServerError() .internalServerError()
.body("An error has occured : " + e.getMessage()); .body("An error has occured : " + e.getMessage());
@@ -32,12 +32,12 @@ public class EventController {
} }
@PostMapping({"/create", "/create/"}) @PostMapping({"/create", "/create/"})
public ResponseEntity<?> create(@RequestBody EventDTO dto){ public ResponseEntity<?> create(@RequestBody EventDTO dto) {
try { try {
Result<String> res = service.save(dto); Result<String> res = service.save(dto);
return ResponseEntity.ok(res); return ResponseEntity.ok(res);
} catch (Exception e){ } catch (Exception e) {
return ResponseEntity return ResponseEntity
.internalServerError() .internalServerError()
.body("An error has occured : " + e.getMessage()); .body("An error has occured : " + e.getMessage());

View File

@@ -7,7 +7,7 @@ public class Result<T> {
private T value; private T value;
public Result(T value){ public Result(T value) {
this.value = value; this.value = value;
} }

View File

@@ -7,7 +7,7 @@ public class TokenGenerator {
private static int length = 6; private static int length = 6;
public static String generate(){ public static String generate() {
return UUID.randomUUID().toString().replace("-", "").substring(0, length); return UUID.randomUUID().toString().replace("-", "").substring(0, length);
} }

View File

@@ -0,0 +1,20 @@
package be.naaturel.letsmeet.core.models;
public class Attendance {
private EventDate date;
private Attendee attendee;
public Attendance(EventDate date, Attendee attendee) {
this.date = date;
this.attendee = attendee;
}
public Attendee getAttendee() {
return attendee;
}
public EventDate getDate() {
return date;
}
}

View File

@@ -1,38 +1,19 @@
package be.naaturel.letsmeet.core.models; package be.naaturel.letsmeet.core.models;
import java.util.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
public class Attendee { public class Attendee {
private String name; private String name;
private Set<EventDate> dates; public Attendee(String name) {
public Attendee(String name){
this(name, new HashSet<>());
}
public Attendee(String name, Set<EventDate> dates){
this.name = name; this.name = name;
this.dates = dates;
} }
public String getName() { public String getName() {
return name; return name;
} }
public Set<EventDate> getDates() {
return new HashSet<>(this.dates);
}
public void replaceDate(EventDate oldDate, EventDate newDate){
dates.remove(oldDate);
dates.add(newDate);
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) return true; if (this == obj) return true;

View File

@@ -1,30 +1,24 @@
package be.naaturel.letsmeet.core.models; package be.naaturel.letsmeet.core.models;
import java.util.*; import java.util.Set;
public class Event { public class Event {
private String name; private String name;
private Set<Attendee> attendees;
public Event(String name, Set<Attendee> attendees){ private Set<Attendance> attendances;
public Event(String name, Set<Attendance> attendances) {
this.name = name; this.name = name;
this.attendees = attendees; this.attendances = attendances;
} }
public String getName() { public String getName() {
return name; return name;
} }
public Set<Attendee> getAttendees(){ public Set<Attendance> getAttendances() {
return new HashSet<>(this.attendees); return this.attendances;
} }
public Set<EventDate> getDates() {
Set<EventDate> dates = new HashSet<>();
for (Attendee p : this.attendees) {
dates.addAll(p.getDates());
}
return dates;
}
} }

View File

@@ -1,12 +1,10 @@
package be.naaturel.letsmeet.core.models; package be.naaturel.letsmeet.core.models;
import java.util.Objects;
public class EventDate { public class EventDate {
private long timeStamp; private long timeStamp;
public EventDate(long timeStamp){ public EventDate(long timeStamp) {
this.timeStamp = timeStamp; this.timeStamp = timeStamp;
} }

View File

@@ -0,0 +1,34 @@
package be.naaturel.letsmeet.dto.database;
import jakarta.persistence.*;
import java.util.Objects;
@Entity(name = "Attendances")
public class AttendanceEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
public String id;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "event_date_id", nullable = false)
public EventDateEntity date;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "attendee_id", nullable = false)
public AttendeeEntity attendee;
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
AttendanceEntity a = (AttendanceEntity) obj;
return a.date.timeStamp == ((AttendanceEntity) obj).date.timeStamp &&
a.attendee.name == ((AttendanceEntity) obj).attendee.name;
}
@Override
public int hashCode() {
return Objects.hash(date, attendee);
}
}

View File

@@ -2,7 +2,7 @@ package be.naaturel.letsmeet.dto.database;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.Set; import java.util.Objects;
@Entity(name = "Attendees") @Entity(name = "Attendees")
public class AttendeeEntity { public class AttendeeEntity {
@@ -14,12 +14,17 @@ public class AttendeeEntity {
@Column @Column
public String name; public String name;
@ManyToMany(cascade = {CascadeType.ALL}) @Override
@JoinTable public boolean equals(Object obj) {
public Set<EventDateEntity> dates; if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
AttendeeEntity a = (AttendeeEntity) obj;
return Objects.equals(name, a.name);
}
@ManyToOne @Override
@JoinColumn(nullable=true) public int hashCode() {
public EventEntity event; return Objects.hash(name);
}
} }

View File

@@ -3,7 +3,6 @@ package be.naaturel.letsmeet.dto.database;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
@Entity(name = "Dates") @Entity(name = "Dates")
@Table(uniqueConstraints = { @Table(uniqueConstraints = {
@@ -18,13 +17,6 @@ public class EventDateEntity {
@Column @Column
public long timeStamp; public long timeStamp;
@ManyToOne
@JoinColumn(nullable=true)
public EventEntity event;
@ManyToMany(mappedBy = "dates")
public Set<AttendeeEntity> attendees;
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) return true; if (this == obj) return true;

View File

@@ -2,7 +2,7 @@ package be.naaturel.letsmeet.dto.database;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.*; import java.util.Set;
@Entity(name = "Events") @Entity(name = "Events")
public class EventEntity { public class EventEntity {
@@ -17,36 +17,9 @@ public class EventEntity {
@Column(unique = true) @Column(unique = true)
public String token; public String token;
@Column @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@OneToMany(targetEntity=EventDateEntity.class, cascade=CascadeType.ALL, mappedBy="event") @JoinColumn(name = "event_id")
public Set<EventDateEntity> dates; public Set<AttendanceEntity> attendances;
@Column
@OneToMany(targetEntity=AttendeeEntity.class, cascade=CascadeType.ALL, mappedBy="event")
public Set<AttendeeEntity> attendees;
public void linkDates(){
for (EventDateEntity date : this.dates) {
date.event = this;
}
}
public void linkAttendees(){
for (AttendeeEntity attendee : this.attendees) {
attendee.event = this;
}
}
public void removeDuplicatedDates(){
for (EventDateEntity ede: dates) {
for (AttendeeEntity pe : attendees) {
if(pe.dates.contains(ede)){
pe.dates.remove(ede);
pe.dates.add(ede);
}
}
}
}
} }

View File

@@ -1,42 +0,0 @@
package be.naaturel.letsmeet.dto.database.factories;
import be.naaturel.letsmeet.core.helpers.TokenGenerator;
import be.naaturel.letsmeet.dto.database.EventDateEntity;
import be.naaturel.letsmeet.dto.database.EventEntity;
import be.naaturel.letsmeet.dto.database.AttendeeEntity;
import java.util.HashSet;
import java.util.Set;
public class DatabasePropsFactory {
public static EventEntity createEvent(String name, Set<AttendeeEntity> attendees){
EventEntity entity = new EventEntity();
entity.name = name;
entity.token = TokenGenerator.generate();
entity.attendees = attendees;
entity.dates = new HashSet<>();
for (AttendeeEntity pe : entity.attendees) {
entity.dates.addAll(pe.dates);
}
entity.linkDates();
entity.linkAttendees();
entity.removeDuplicatedDates();
return entity;
}
public static AttendeeEntity createAttendee(String name, Set<EventDateEntity> dates){
AttendeeEntity entity = new AttendeeEntity();
entity.name = name;
entity.dates = dates;
return entity;
}
public static EventDateEntity createDate(long timestamp){
EventDateEntity entity = new EventDateEntity();
entity.timeStamp = timestamp;
return entity;
}
}

View File

@@ -0,0 +1,21 @@
package be.naaturel.letsmeet.dto.httpRequest;
import java.util.Objects;
public class AttendanceDTO {
public EventDateDTO date;
public AttendeeDTO attendee;
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
AttendanceDTO a = (AttendanceDTO) obj;
return a.date.timestamp == ((AttendanceDTO) obj).date.timestamp &&
a.attendee.name == ((AttendanceDTO) obj).attendee.name;
}
@Override
public int hashCode() {
return Objects.hash(date, attendee);
}
}

View File

@@ -1,7 +1,5 @@
package be.naaturel.letsmeet.dto.httpRequest; package be.naaturel.letsmeet.dto.httpRequest;
import java.util.Set;
public class AttendeeDTO { public class AttendeeDTO {
public String name; public String name;

View File

@@ -1,12 +1,11 @@
package be.naaturel.letsmeet.dto.httpRequest; package be.naaturel.letsmeet.dto.httpRequest;
import java.util.Map;
import java.util.Set; import java.util.Set;
public class EventDTO { public class EventDTO {
public String name; public String name;
public String token; public String token;
public Map<String, Set<AttendeeDTO>> dates; public Set<AttendanceDTO> attendances;
} }

View File

@@ -3,7 +3,7 @@ package be.naaturel.letsmeet.mappers;
import java.util.Collection; import java.util.Collection;
import java.util.function.Supplier; import java.util.function.Supplier;
public interface Mapper<T, T_ENTITY> { public interface Mapper<T, T_ENTITY> {
T_ENTITY toEntity(T d); T_ENTITY toEntity(T d);

View File

@@ -0,0 +1,51 @@
package be.naaturel.letsmeet.mappers.database;
import be.naaturel.letsmeet.core.models.Attendance;
import be.naaturel.letsmeet.core.models.Attendee;
import be.naaturel.letsmeet.core.models.EventDate;
import be.naaturel.letsmeet.dto.database.AttendanceEntity;
import be.naaturel.letsmeet.dto.database.AttendeeEntity;
import be.naaturel.letsmeet.dto.database.EventDateEntity;
import be.naaturel.letsmeet.mappers.Mapper;
import java.util.Collection;
import java.util.function.Supplier;
public class AttendanceMapper implements Mapper<Attendance, AttendanceEntity> {
private final Mapper<Attendee, AttendeeEntity> attendeeMapper;
private final Mapper<EventDate, EventDateEntity> dateMapper;
public AttendanceMapper() {
this.attendeeMapper = new AttendeeMapper();
this.dateMapper = new EventDateMapper();
}
@Override
public AttendanceEntity toEntity(Attendance d) {
AttendanceEntity dto = new AttendanceEntity();
dto.attendee = attendeeMapper.toEntity(d.getAttendee());
dto.date = dateMapper.toEntity(d.getDate());
return dto;
}
@Override
public Attendance toModel(AttendanceEntity d) {
Attendee attendee = attendeeMapper.toModel(d.attendee);
EventDate date = dateMapper.toModel(d.date);
return new Attendance(date, attendee);
}
@Override
public <C extends Collection<AttendanceEntity>> C toEntities(Collection<Attendance> attendances, Supplier<C> collectionSupplier) {
C result = collectionSupplier.get();
attendances.forEach(p -> result.add(toEntity(p)));
return result;
}
@Override
public <C extends Collection<Attendance>> C toModels(Collection<AttendanceEntity> attendances, Supplier<C> collectionSupplier) {
C result = collectionSupplier.get();
attendances.forEach(p -> result.add(toModel(p)));
return result;
}
}

View File

@@ -1,14 +1,12 @@
package be.naaturel.letsmeet.mappers.database; package be.naaturel.letsmeet.mappers.database;
import be.naaturel.letsmeet.dto.database.EventDateEntity;
import be.naaturel.letsmeet.dto.database.AttendeeEntity;
import be.naaturel.letsmeet.dto.database.factories.DatabasePropsFactory;
import be.naaturel.letsmeet.mappers.Mapper;
import be.naaturel.letsmeet.core.models.EventDate;
import be.naaturel.letsmeet.core.models.Attendee; import be.naaturel.letsmeet.core.models.Attendee;
import be.naaturel.letsmeet.core.models.EventDate;
import be.naaturel.letsmeet.dto.database.AttendeeEntity;
import be.naaturel.letsmeet.dto.database.EventDateEntity;
import be.naaturel.letsmeet.mappers.Mapper;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.function.Supplier; import java.util.function.Supplier;
public class AttendeeMapper implements Mapper<Attendee, AttendeeEntity> { public class AttendeeMapper implements Mapper<Attendee, AttendeeEntity> {
@@ -16,18 +14,21 @@ public class AttendeeMapper implements Mapper<Attendee, AttendeeEntity> {
private final Mapper<EventDate, EventDateEntity> dateMapper; private final Mapper<EventDate, EventDateEntity> dateMapper;
public AttendeeMapper(){ public AttendeeMapper() {
dateMapper = new EventDateMapper(); dateMapper = new EventDateMapper();
} }
@Override @Override
public AttendeeEntity toEntity(Attendee d) { public AttendeeEntity toEntity(Attendee a) {
return DatabasePropsFactory.createAttendee(d.getName(), dateMapper.toEntities(d.getDates(), HashSet::new)); //return DatabasePropsFactory.createAttendee(d.getName(), dateMapper.toEntities(d.getDates(), HashSet::new));
AttendeeEntity ae = new AttendeeEntity();
ae.name = a.getName();
return ae;
} }
@Override @Override
public Attendee toModel(AttendeeEntity d) { public Attendee toModel(AttendeeEntity d) {
return new Attendee(d.name, dateMapper.toModels(d.dates, HashSet::new)); return new Attendee(d.name);
} }
@Override @Override

View File

@@ -1,9 +1,8 @@
package be.naaturel.letsmeet.mappers.database; package be.naaturel.letsmeet.mappers.database;
import be.naaturel.letsmeet.dto.database.EventDateEntity;
import be.naaturel.letsmeet.dto.database.factories.DatabasePropsFactory;
import be.naaturel.letsmeet.mappers.Mapper;
import be.naaturel.letsmeet.core.models.EventDate; import be.naaturel.letsmeet.core.models.EventDate;
import be.naaturel.letsmeet.dto.database.EventDateEntity;
import be.naaturel.letsmeet.mappers.Mapper;
import java.util.Collection; import java.util.Collection;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -13,7 +12,9 @@ public class EventDateMapper implements Mapper<EventDate, EventDateEntity> {
@Override @Override
public EventDateEntity toEntity(EventDate d) { public EventDateEntity toEntity(EventDate d) {
return DatabasePropsFactory.createDate(d.getTimeStamp()); EventDateEntity ede = new EventDateEntity();
ede.timeStamp = d.getTimeStamp();
return ede;
} }
@Override @Override

View File

@@ -1,33 +1,41 @@
package be.naaturel.letsmeet.mappers.database; package be.naaturel.letsmeet.mappers.database;
import be.naaturel.letsmeet.dto.database.EventEntity; import be.naaturel.letsmeet.core.helpers.TokenGenerator;
import be.naaturel.letsmeet.dto.database.AttendeeEntity; import be.naaturel.letsmeet.core.models.Attendance;
import be.naaturel.letsmeet.dto.database.factories.DatabasePropsFactory;
import be.naaturel.letsmeet.mappers.Mapper;
import be.naaturel.letsmeet.core.models.Event; import be.naaturel.letsmeet.core.models.Event;
import be.naaturel.letsmeet.core.models.Attendee; import be.naaturel.letsmeet.dto.database.AttendanceEntity;
import be.naaturel.letsmeet.dto.database.EventEntity;
import be.naaturel.letsmeet.mappers.Mapper;
import java.util.*; import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
public class EventMapper implements Mapper<Event, EventEntity> { public class EventMapper implements Mapper<Event, EventEntity> {
private final Mapper<Attendee, AttendeeEntity> attendeeMapper; private final Mapper<Attendance, AttendanceEntity> attendanceMapper;
public EventMapper(){ public EventMapper() {
this.attendeeMapper = new AttendeeMapper(); this.attendanceMapper = new AttendanceMapper();
} }
@Override @Override
public EventEntity toEntity(Event event) { public EventEntity toEntity(Event event) {
return DatabasePropsFactory.createEvent(event.getName(), attendeeMapper.toEntities(event.getAttendees(), HashSet::new)); EventEntity entity = new EventEntity();
entity.name = event.getName();
entity.token = TokenGenerator.generate();
entity.attendances = attendanceMapper.toEntities(event.getAttendances(), HashSet::new);
return entity;
} }
@Override @Override
public Event toModel(EventEntity eventEntity) { public Event toModel(EventEntity eventEntity) {
return new Event(eventEntity.name, attendeeMapper.toModels(eventEntity.attendees, HashSet::new)); Set<Attendance> attendances = attendanceMapper.toModels(eventEntity.attendances, HashSet::new);
return new Event(eventEntity.name, attendances);
} }
@Override @Override
public <C extends Collection<EventEntity>> C toEntities(Collection<Event> events, Supplier<C> collectionSupplier) { public <C extends Collection<EventEntity>> C toEntities(Collection<Event> events, Supplier<C> collectionSupplier) {
C result = collectionSupplier.get(); C result = collectionSupplier.get();

View File

@@ -0,0 +1,52 @@
package be.naaturel.letsmeet.mappers.requests;
import be.naaturel.letsmeet.core.models.Attendance;
import be.naaturel.letsmeet.core.models.Attendee;
import be.naaturel.letsmeet.core.models.EventDate;
import be.naaturel.letsmeet.dto.httpRequest.AttendanceDTO;
import be.naaturel.letsmeet.dto.httpRequest.AttendeeDTO;
import be.naaturel.letsmeet.dto.httpRequest.EventDateDTO;
import be.naaturel.letsmeet.mappers.Mapper;
import java.util.Collection;
import java.util.function.Supplier;
public class AttendanceDTOMapper implements Mapper<Attendance, AttendanceDTO> {
private final Mapper<Attendee, AttendeeDTO> attendeeMapper;
private final Mapper<EventDate, EventDateDTO> dateMapper;
public AttendanceDTOMapper() {
this.attendeeMapper = new AttendeeDTOMapper();
this.dateMapper = new EventDateDTOMapper();
}
@Override
public AttendanceDTO toEntity(Attendance d) {
AttendanceDTO dto = new AttendanceDTO();
dto.attendee = attendeeMapper.toEntity(d.getAttendee());
dto.date = dateMapper.toEntity(d.getDate());
return dto;
}
@Override
public Attendance toModel(AttendanceDTO d) {
Attendee attendee = attendeeMapper.toModel(d.attendee);
EventDate date = dateMapper.toModel(d.date);
return new Attendance(date, attendee);
}
@Override
public <C extends Collection<AttendanceDTO>> C toEntities(Collection<Attendance> attendances, Supplier<C> collectionSupplier) {
C result = collectionSupplier.get();
attendances.forEach(p -> result.add(toEntity(p)));
return result;
}
@Override
public <C extends Collection<Attendance>> C toModels(Collection<AttendanceDTO> attendances, Supplier<C> collectionSupplier) {
C result = collectionSupplier.get();
attendances.forEach(p -> result.add(toModel(p)));
return result;
}
}

View File

@@ -1,20 +1,19 @@
package be.naaturel.letsmeet.mappers.requests; package be.naaturel.letsmeet.mappers.requests;
import be.naaturel.letsmeet.dto.httpRequest.EventDateDTO;
import be.naaturel.letsmeet.dto.httpRequest.AttendeeDTO;
import be.naaturel.letsmeet.mappers.Mapper;
import be.naaturel.letsmeet.core.models.EventDate;
import be.naaturel.letsmeet.core.models.Attendee; import be.naaturel.letsmeet.core.models.Attendee;
import be.naaturel.letsmeet.core.models.EventDate;
import be.naaturel.letsmeet.dto.httpRequest.AttendeeDTO;
import be.naaturel.letsmeet.dto.httpRequest.EventDateDTO;
import be.naaturel.letsmeet.mappers.Mapper;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.function.Supplier; import java.util.function.Supplier;
public class AttendeeDTOMapper implements Mapper<Attendee, AttendeeDTO> { public class AttendeeDTOMapper implements Mapper<Attendee, AttendeeDTO> {
private final Mapper<EventDate, EventDateDTO> dateMapper; private final Mapper<EventDate, EventDateDTO> dateMapper;
public AttendeeDTOMapper(){ public AttendeeDTOMapper() {
this.dateMapper = new EventDateDTOMapper(); this.dateMapper = new EventDateDTOMapper();
} }
@@ -27,7 +26,7 @@ public class AttendeeDTOMapper implements Mapper<Attendee, AttendeeDTO> {
@Override @Override
public Attendee toModel(AttendeeDTO d) { public Attendee toModel(AttendeeDTO d) {
return null; return new Attendee(d.name);
} }
@Override @Override

View File

@@ -1,58 +1,36 @@
package be.naaturel.letsmeet.mappers.requests; package be.naaturel.letsmeet.mappers.requests;
import be.naaturel.letsmeet.core.models.EventDate; import be.naaturel.letsmeet.core.models.Attendance;
import be.naaturel.letsmeet.dto.httpRequest.EventDTO;
import be.naaturel.letsmeet.dto.httpRequest.AttendeeDTO;
import be.naaturel.letsmeet.dto.httpRequest.EventDateDTO;
import be.naaturel.letsmeet.mappers.Mapper;
import be.naaturel.letsmeet.core.models.Event; import be.naaturel.letsmeet.core.models.Event;
import be.naaturel.letsmeet.core.models.Attendee; import be.naaturel.letsmeet.dto.httpRequest.AttendanceDTO;
import be.naaturel.letsmeet.dto.httpRequest.EventDTO;
import be.naaturel.letsmeet.mappers.Mapper;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
public class EventDTOMapper implements Mapper<Event, EventDTO> { public class EventDTOMapper implements Mapper<Event, EventDTO> {
private final Mapper<Attendee, AttendeeDTO> attendeeMapper = new AttendeeDTOMapper(); private final Mapper<Attendance, AttendanceDTO> attendanceMapper;
private final Mapper<EventDate, EventDateDTO> dateMapper = new EventDateDTOMapper();
public EventDTOMapper() {
this.attendanceMapper = new AttendanceDTOMapper();
}
@Override @Override
public EventDTO toEntity(Event event) { public EventDTO toEntity(Event event) {
EventDTO eventDTO = new EventDTO(); EventDTO eventDTO = new EventDTO();
eventDTO.name = event.getName(); eventDTO.name = event.getName();
//eventDTO.dates = attendeeMapper.toEntities(event.getAttendees(), HashSet::new); eventDTO.attendances = attendanceMapper.toEntities(event.getAttendances(), HashSet::new);
//TODO : MUST BE CLEANED
eventDTO.dates = new HashMap<>();
event.getAttendees().forEach(a -> {
a.getDates().forEach(d -> {
EventDateDTO dtoD = dateMapper.toEntity(d);
AttendeeDTO dtoA = attendeeMapper.toEntity(a);
String key = String.valueOf(d.getTimeStamp());
if(eventDTO.dates.containsKey(key)){
eventDTO.dates.get(key).add(dtoA);
} else {
Set<AttendeeDTO> s = new HashSet<>();
s.add(dtoA);
eventDTO.dates.put(key, s);
}
});
});
return eventDTO; return eventDTO;
} }
@Override @Override
public Event toModel(EventDTO dto) { public Event toModel(EventDTO dto) {
//return new Event(dto.name, attendeeMapper.toModels(dto.dates, HashSet::new)); Set<Attendance> attendances = attendanceMapper.toModels(dto.attendances, HashSet::new);
Set<Attendee> set = new HashSet<>(); return new Event(dto.name, attendances);
for (String key : dto.dates.keySet()) {
set.addAll(attendeeMapper.toModels(dto.dates.get(key), HashSet::new));
}
return new Event(dto.name, set);
} }
@Override @Override

View File

@@ -1,8 +1,8 @@
package be.naaturel.letsmeet.mappers.requests; package be.naaturel.letsmeet.mappers.requests;
import be.naaturel.letsmeet.core.models.EventDate;
import be.naaturel.letsmeet.dto.httpRequest.EventDateDTO; import be.naaturel.letsmeet.dto.httpRequest.EventDateDTO;
import be.naaturel.letsmeet.mappers.Mapper; import be.naaturel.letsmeet.mappers.Mapper;
import be.naaturel.letsmeet.core.models.EventDate;
import java.util.Collection; import java.util.Collection;
import java.util.function.Supplier; import java.util.function.Supplier;

View File

@@ -1,15 +1,88 @@
package be.naaturel.letsmeet.repositories; package be.naaturel.letsmeet.repositories;
import be.naaturel.letsmeet.core.models.Event;
import be.naaturel.letsmeet.dto.database.AttendanceEntity;
import be.naaturel.letsmeet.dto.database.AttendeeEntity;
import be.naaturel.letsmeet.dto.database.EventDateEntity;
import be.naaturel.letsmeet.dto.database.EventEntity; import be.naaturel.letsmeet.dto.database.EventEntity;
import org.springframework.data.jpa.repository.JpaRepository; import be.naaturel.letsmeet.mappers.Mapper;
import org.springframework.data.jpa.repository.Query; import be.naaturel.letsmeet.mappers.database.EventMapper;
import org.springframework.data.repository.query.Param; import be.naaturel.letsmeet.repositories.interfaces.AttendanceEntityRepo;
import be.naaturel.letsmeet.repositories.interfaces.AttendeeEntityRepo;
import be.naaturel.letsmeet.repositories.interfaces.EventDateEntityRepo;
import be.naaturel.letsmeet.repositories.interfaces.EventEntityRepo;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List; import java.util.HashMap;
import java.util.Map;
@Component
public class EventRepo {
private final EventEntityRepo eventRepo;
private final AttendeeEntityRepo attendeeRepo;
private final EventDateEntityRepo dateRepo;
private final AttendanceEntityRepo attendanceRepo;
private final Mapper<Event, EventEntity> mapper;
@Autowired
public EventRepo(EventEntityRepo eventRepo, AttendeeEntityRepo attendeeRepo,
EventDateEntityRepo dateRepo, AttendanceEntityRepo attendanceRepo) {
this.eventRepo = eventRepo;
this.attendeeRepo = attendeeRepo;
this.dateRepo = dateRepo;
this.attendanceRepo = attendanceRepo;
this.mapper = new EventMapper();
}
@Transactional
public String save(Event e) {
//Hacky way to avoid duplication through relationship insertion.
//I didn't want to do that, but after going through the five stages
// of grief and spending many hours (like way too much) looking for a cleaner
// solution, it seems JPA has no built-in way to achieve it
EventEntity event = getAsEntity(e);
Map<AttendeeEntity, AttendeeEntity> insertedAttendees = new HashMap<>();
Map<EventDateEntity, EventDateEntity> insertedDates = new HashMap<>();
for (AttendanceEntity attendance : event.attendances) {
AttendeeEntity attendee = attendance.attendee;
EventDateEntity date = attendance.date;
if (!insertedAttendees.containsKey(attendee)) {
insertedAttendees.put(attendee, attendee);
attendeeRepo.save(attendee);
} else {
attendance.attendee = insertedAttendees.get(attendee);
}
if (!insertedDates.containsKey(date)) {
insertedDates.put(date, date);
dateRepo.save(date);
} else {
attendance.date = insertedDates.get(date);
}
}
EventEntity result = eventRepo.save(event);
return result.token;
}
public Event getByToken(String token) {
EventEntity entity = this.eventRepo.findEventEntityByToken(token);
return mapper.toModel(entity);
}
public void delete(Event event) {
}
private EventEntity getAsEntity(Event e) {
return mapper.toEntity(e);
}
public interface EventRepo
extends JpaRepository<EventEntity, String> {
@Query(value = "SELECT * FROM events e WHERE e.token = :#{#token};", nativeQuery = true)
EventEntity findEventEntityByToken(@Param("token") String token);
} }

View File

@@ -0,0 +1,13 @@
package be.naaturel.letsmeet.repositories.interfaces;
import be.naaturel.letsmeet.dto.database.AttendanceEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AttendanceEntityRepo extends JpaRepository<AttendanceEntity, String> {
//@Query(value = "SELECT * FROM events e WHERE e.token = :#{#token};", nativeQuery = true)
//EventEntity findEventEntityByToken(@Param("token") String token);
}

View File

@@ -0,0 +1,13 @@
package be.naaturel.letsmeet.repositories.interfaces;
import be.naaturel.letsmeet.dto.database.AttendeeEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AttendeeEntityRepo extends JpaRepository<AttendeeEntity, String> {
//@Query(value = "SELECT * FROM events e WHERE e.token = :#{#token};", nativeQuery = true)
//EventEntity findEventEntityByToken(@Param("token") String token);
}

View File

@@ -0,0 +1,13 @@
package be.naaturel.letsmeet.repositories.interfaces;
import be.naaturel.letsmeet.dto.database.EventDateEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface EventDateEntityRepo extends JpaRepository<EventDateEntity, String> {
//@Query(value = "SELECT * FROM events e WHERE e.token = :#{#token};", nativeQuery = true)
//EventEntity findEventEntityByToken(@Param("token") String token);
}

View File

@@ -0,0 +1,15 @@
package be.naaturel.letsmeet.repositories.interfaces;
import be.naaturel.letsmeet.dto.database.EventEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface EventEntityRepo extends JpaRepository<EventEntity, String> {
@Query(value = "SELECT * FROM events e WHERE e.token = :#{#token};", nativeQuery = true)
EventEntity findEventEntityByToken(@Param("token") String token);
}

View File

@@ -8,12 +8,13 @@ public abstract class AbstractService<T, T_ENTITY, T_DTO> {
protected Mapper<T, T_ENTITY> dataBaseMapper; protected Mapper<T, T_ENTITY> dataBaseMapper;
protected Mapper<T, T_DTO> requestMapper; protected Mapper<T, T_DTO> requestMapper;
AbstractService(Mapper<T, T_ENTITY> dataBaseMapper, Mapper<T, T_DTO> requestMapper){ AbstractService(Mapper<T, T_ENTITY> dataBaseMapper, Mapper<T, T_DTO> requestMapper) {
this.dataBaseMapper = dataBaseMapper; this.dataBaseMapper = dataBaseMapper;
this.requestMapper = requestMapper; this.requestMapper = requestMapper;
} }
public abstract Result<?> save(T_DTO prop) throws Exception; public abstract Result<?> save(T_DTO prop) throws Exception;
public abstract boolean delete(T_DTO prop); public abstract boolean delete(T_DTO prop);

View File

@@ -1,11 +1,11 @@
package be.naaturel.letsmeet.services; package be.naaturel.letsmeet.services;
import be.naaturel.letsmeet.core.Result; import be.naaturel.letsmeet.core.Result;
import be.naaturel.letsmeet.core.models.Event;
import be.naaturel.letsmeet.dto.database.EventEntity; import be.naaturel.letsmeet.dto.database.EventEntity;
import be.naaturel.letsmeet.dto.httpRequest.EventDTO; import be.naaturel.letsmeet.dto.httpRequest.EventDTO;
import be.naaturel.letsmeet.mappers.database.EventMapper; import be.naaturel.letsmeet.mappers.database.EventMapper;
import be.naaturel.letsmeet.mappers.requests.EventDTOMapper; import be.naaturel.letsmeet.mappers.requests.EventDTOMapper;
import be.naaturel.letsmeet.core.models.Event;
import be.naaturel.letsmeet.repositories.EventRepo; import be.naaturel.letsmeet.repositories.EventRepo;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.dao.OptimisticLockingFailureException;
@@ -17,7 +17,7 @@ public class EventService extends AbstractService<Event, EventEntity, EventDTO>
protected EventRepo repo; protected EventRepo repo;
@Autowired @Autowired
public EventService(EventRepo eventRepo){ public EventService(EventRepo eventRepo) {
super(new EventMapper(), new EventDTOMapper()); super(new EventMapper(), new EventDTOMapper());
this.repo = eventRepo; this.repo = eventRepo;
} }
@@ -25,36 +25,32 @@ public class EventService extends AbstractService<Event, EventEntity, EventDTO>
@Override @Override
public Result<String> save(EventDTO dto) throws Exception { public Result<String> save(EventDTO dto) throws Exception {
Event event = this.requestMapper.toModel(dto); Event event = this.requestMapper.toModel(dto);
EventEntity entity = this.dataBaseMapper.toEntity(event); try {
try{ String res = this.repo.save(event);
this.repo.save(entity); return new Result<>(res);
return new Result<>(entity.token);
} catch (Exception e) { } catch (Exception e) {
throw new Exception("Something went wrong"); throw new Exception("Something went wrong : " + e.getMessage());
} }
} }
@Override @Override
public boolean delete(EventDTO dto) { public boolean delete(EventDTO dto) {
Event event = this.requestMapper.toModel(dto); Event event = this.requestMapper.toModel(dto);
EventEntity entity = this.dataBaseMapper.toEntity(event); try {
try{ this.repo.delete(event);
this.repo.delete(entity);
return true; return true;
} catch (IllegalArgumentException iae){ } catch (IllegalArgumentException iae) {
return false; return false;
} catch (OptimisticLockingFailureException olfe){ } catch (OptimisticLockingFailureException olfe) {
return false; return false;
} }
} }
public EventDTO getEvent(String token){ public EventDTO getEvent(String token) {
try{ try {
EventEntity entity = this.repo.findEventEntityByToken(token); Event event = this.repo.getByToken(token);
Event model = dataBaseMapper.toModel(entity); return requestMapper.toEntity(event);
EventDTO dto = requestMapper.toEntity(model); } catch (Exception e) {
return dto;
} catch (Exception e){
return null; return null;
} }
} }

View File

@@ -1,19 +1,15 @@
#=============MAIN============= #=============MAIN=============
spring.application.name=letsmeet spring.application.name=letsmeet
#=============SERVER============= #=============SERVER=============
server.port=5001 server.port=5001
#=============SECURITY============= #=============SECURITY=============
sec.cors.authorizedHots=* sec.cors.authorizedHots=*
sec.cors.authorizedMethods=GET,POST,PUT,DELETE,OPTION sec.cors.authorizedMethods=GET,POST,PUT,DELETE,OPTION
sec.cors.authorizedHeader=Authorization,Content-type sec.cors.authorizedHeader=Authorization,Content-type
#=============DATABASE============= #=============DATABASE=============
spring.datasource.url=jdbc:${DB_URL} spring.datasource.url=jdbc:${DB_URL}
spring.datasource.username=${DB_USER} spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD} spring.datasource.password=${DB_PASSWORD}
spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
spring.jpa.show-sql=true spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.ddl-auto=update

View File

@@ -1,56 +0,0 @@
package be.naaturel.letsmeet.mappers;
import be.naaturel.letsmeet.dto.database.EventDateEntity;
import be.naaturel.letsmeet.mappers.Mapper;
import be.naaturel.letsmeet.mappers.database.EventDateMapper;
import be.naaturel.letsmeet.core.models.EventDate;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class DateMapperTest {
private static final Mapper<EventDate, EventDateEntity> mapper = new EventDateMapper();
private static final EventDate model = new EventDate(0);
private static final Set<EventDate> models = Set.of(new EventDate(0), new EventDate(1), new EventDate(2));
private static final EventDateEntity entity = new EventDateEntity();
private static final Set<EventDateEntity> entities = new HashSet<>();
@BeforeAll
static void setup(){
entity.timeStamp = 0;
entities.add(new EventDateEntity());
entities.add(new EventDateEntity());
entities.add(new EventDateEntity());
}
@Test
void single_model_to_entity() {
EventDateEntity converted = mapper.toEntity(model);
assertEquals(converted.timeStamp, model.getTimeStamp());
}
@Test
void single_entity_to_model() {
EventDate converted = mapper.toModel(entity);
assertEquals(converted.getTimeStamp(), entity.timeStamp);
}
@Test
void multiple_entities_to_models() {
Set<EventDate> dates = mapper.toModels(entities, HashSet::new);
assertEquals(dates.size(), entities.size());
}
@Test
void multiple_models_to_entities() {
Set<EventDateEntity> dates = mapper.toEntities(models, HashSet::new);
assertEquals(dates.size(), models.size());
}
}

View File

@@ -1,68 +0,0 @@
package be.naaturel.letsmeet.mappers;
import be.naaturel.letsmeet.core.models.Event;
import be.naaturel.letsmeet.core.models.EventDate;
import be.naaturel.letsmeet.dto.database.EventDateEntity;
import be.naaturel.letsmeet.dto.database.EventEntity;
import be.naaturel.letsmeet.mappers.database.EventMapper;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class EventMapperTest {
/*private static final Mapper<Event, EventEntity> mapper = new EventMapper();
private static final Set<EventDate> dates = Set.of(new EventDate(0, new HashSet<>()));
private static final Event model = new Event("Event for tests", dates);
private static final List<Event> models = List.of(model, model, model);
private static final EventEntity entity = new EventEntity();
private static final List<EventEntity> entities = new ArrayList<>();
@BeforeAll
static void setup(){
entity.id = "BLA BLA BLA FAKE ID";
entity.name = "Event entity for tests";
entity.dates = Set.of(new EventDateEntity(), new EventDateEntity(), new EventDateEntity());
entities.add(entity);
entities.add(entity);
entities.add(entity);
}
@Test
void single_model_to_entity() {
EventEntity converted = mapper.toEntity(model);
assertEquals(converted.name, model.getName());
assertEquals(converted.dates.size(), model.getDates().size());
}
@Test
void single_entity_to_model() {
Event converted = mapper.toModel(entity);
assertEquals(converted.getName(), entity.name);
//assertEquals(converted.getDates().size(), entity.dates.size());
}
@Test
void multiple_entities_to_models() {
List<Event> events = mapper.toModels(entities, ArrayList::new);
assertEquals(events.size(), entities.size());
}
@Test
void multiple_models_to_entities() {
List<EventEntity> eventEntities = mapper.toEntities(models, ArrayList::new);
assertEquals(eventEntities.size(), models.size());
}*/
}

View File

@@ -1,56 +0,0 @@
package be.naaturel.letsmeet.mappers.dto;
import be.naaturel.letsmeet.core.models.Event;
import be.naaturel.letsmeet.dto.httpRequest.EventDTO;
import be.naaturel.letsmeet.mappers.Mapper;
import be.naaturel.letsmeet.mappers.requests.EventDTOMapper;
import be.naaturel.letsmeet.mock.dto.MockEventDTO;
import be.naaturel.letsmeet.mock.models.MockEventModel;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class EventDTOMapperTest {
private static MockEventDTO mockDto;
private static MockEventModel mockModel;
private static Mapper<Event, EventDTO> mapper;
@BeforeAll
static void setup(){
mockDto = new MockEventDTO();
mockModel = new MockEventModel();
mapper = new EventDTOMapper();
}
@Test
void single_dto_to_model_test(){
Event res = mapper.toModel(mockDto);
assertEquals(res.getName(), mockDto.name);
}
@Test
void single_model_to_dto_test(){
EventDTO res = mapper.toEntity(mockModel);
assertEquals(res.name, mockModel.getName());
}
@Test
void multiple_dto_to_models_test(){
List<Event> res = mapper.toModels(Set.of(mockDto), ArrayList::new);
assertEquals(res.size(), 1);
assertEquals(res.getFirst().getName(), mockDto.name);
}
@Test
void mulitple_models_to_dto_test(){
List<EventDTO> res = mapper.toEntities(Set.of(mockModel), ArrayList::new);
assertEquals(res.size(), 1);
assertEquals(res.getFirst().name, mockModel.getName());
}
}

View File

@@ -1,11 +0,0 @@
package be.naaturel.letsmeet.mock.dto;
import be.naaturel.letsmeet.dto.httpRequest.AttendeeDTO;
public class MockAttendeeDTO extends AttendeeDTO {
public MockAttendeeDTO(){
}
}

View File

@@ -1,24 +0,0 @@
package be.naaturel.letsmeet.mock.dto;
import be.naaturel.letsmeet.core.models.Attendee;
import be.naaturel.letsmeet.dto.httpRequest.AttendeeDTO;
import be.naaturel.letsmeet.dto.httpRequest.EventDTO;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
public class MockEventDTO extends EventDTO {
public MockEventDTO() {
this.name = "Mocked event";
this.dates = new HashMap<>();
populate(0, new HashSet<>());
populate(1, new HashSet<>());
populate(2, new HashSet<>());
}
private void populate(long timestamp, Set<AttendeeDTO> attendees){
this.dates.put(String.valueOf(timestamp), attendees);
}
}

View File

@@ -1,11 +0,0 @@
package be.naaturel.letsmeet.mock.jpa;
import be.naaturel.letsmeet.dto.database.EventEntity;
public class MockEventEntity extends EventEntity {
public MockEventEntity(){
}
}

View File

@@ -1,13 +0,0 @@
package be.naaturel.letsmeet.mock.models;
import be.naaturel.letsmeet.core.models.Attendee;
import be.naaturel.letsmeet.core.models.Event;
import java.util.HashSet;
import java.util.Set;
public class MockEventModel extends Event {
public MockEventModel() {
super("Mock event model", new HashSet<>());
}
}

View File

@@ -1,4 +0,0 @@
package be.naaturel.letsmeet.repositories;
public class EventRepoTest {
}

View File

@@ -1,20 +0,0 @@
package be.naaturel.letsmeet.repositories;
import be.naaturel.letsmeet.dto.database.EventEntity;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
@TestConfiguration
public class Seeder {
@Bean
public CommandLineRunner seedDatabase(EventRepo eventRepo) {
return args -> {
if (eventRepo.count() == 0) {
eventRepo.save(new EventEntity());
eventRepo.save(new EventEntity());
}
};
}
}

View File

@@ -8,6 +8,7 @@
"name": "letsmeet", "name": "letsmeet",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"chart.js": "^4.4.8",
"express-requests-logger": "^4.0.2", "express-requests-logger": "^4.0.2",
"pinia": "^3.0.1", "pinia": "^3.0.1",
"vite-plugin-svgr": "^4.3.0", "vite-plugin-svgr": "^4.3.0",
@@ -1367,6 +1368,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3173,6 +3180,18 @@
"url": "https://github.com/chalk/chalk?sponsor=1" "url": "https://github.com/chalk/chalk?sponsor=1"
} }
}, },
"node_modules/chart.js": {
"version": "4.4.8",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz",
"integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/check-error": { "node_modules/check-error": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",

View File

@@ -13,6 +13,7 @@
"lint": "eslint . --fix" "lint": "eslint . --fix"
}, },
"dependencies": { "dependencies": {
"chart.js": "^4.4.8",
"express-requests-logger": "^4.0.2", "express-requests-logger": "^4.0.2",
"pinia": "^3.0.1", "pinia": "^3.0.1",
"vite-plugin-svgr": "^4.3.0", "vite-plugin-svgr": "^4.3.0",

View File

@@ -0,0 +1,107 @@
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {Event} from "@/models/Event.ts";
import {AttendanceGraph} from "@/models/AttendanceGraph.ts";
import type {Attendee} from "@/models/Attendee.ts";
import {DateHelper} from "@/helpers/DateHelper.ts";
const props = defineProps<{
event: Event;
}>();
let graph = new AttendanceGraph(props.event);
onMounted(() => {
render();
})
function render() : void{
let container = queryContainer()
let dates = graph.getDates();
for (let date of dates) {
console.log(date);
let attendees: Attendee[] = graph.getAttendees(date);
let bar = addBar(container);
setBarSize(bar, graph.getRatio(date));
setBarTooltip(bar, `${DateHelper.formatDate(date)} \n Attendees : ${graph.getAttendees(date).length}`);
}
}
function addBar(container : HTMLElement) : HTMLElement {
let bar = document.createElement("div");
bar.classList.add("bar");
container.appendChild(bar);
return bar;
}
function setBarSize(bar : HTMLElement, ratio : number) : void{
console.log(ratio);
bar.style.width = `${ratio*100}%`;
}
function setBarTooltip(bar : HTMLElement, info : string) : void{
let tooltip = document.createElement('div');
tooltip.innerText = info;
tooltip.style.position = 'absolute';
tooltip.style.visibility = 'hidden';
tooltip.style.backgroundColor = 'black';
tooltip.style.color = 'white';
tooltip.style.padding = '5px';
tooltip.style.borderRadius = '5px';
bar.addEventListener('mouseenter', function() {
document.body.appendChild(tooltip);
const rect = bar.getBoundingClientRect();
tooltip.style.left = `${rect.left + window.scrollX}px`;
tooltip.style.top = `${rect.top + window.scrollY - tooltip.offsetHeight - 5}px`;
tooltip.style.visibility = 'visible';
});
bar.addEventListener('mouseleave', function() {
tooltip.style.visibility = 'hidden';
tooltip.remove();
});
}
function queryContainer() : HTMLElement {
return document.getElementsByClassName("graph")[0] as HTMLElement;
}
</script>
<template>
<div class="attendance-graph">
<h2>{{event.getName()}}</h2>
<div class="graph"></div>
</div>
</template>
<style scoped>
.graph {
display: flex;
flex-direction: column;
gap: 10px;
border-left: solid 1px black;
border-bottom: solid 1px black;
}
::v-deep(.bar)
{
height : 30px;
border : solid 1px black;
background-color: var(--secondary-color);
flex-grow: 1;
}
::v-deep(.bar):hover
{
transform: scale(1.1);
}
.tooltip {
transition: opacity 0.3s;
}
</style>

View File

@@ -0,0 +1,15 @@
import type {AttendeeDto} from "@/dto/AttendeeDto.ts";
import type {TimeStampDto} from "@/dto/TimeStampDto.ts";
export class AttendanceDto {
public date : TimeStampDto
public attendee : AttendeeDto
public constructor(date : TimeStampDto, attendee : AttendeeDto) {
this.date = date;
this.attendee = attendee;
}
}

View File

@@ -1,17 +1,18 @@
import type {Attendee} from "@/models/Attendee.ts"; import type {Attendee} from "@/models/Attendee.ts";
import type {EventDate} from "@/models/EventDate.ts"; import type {EventDate} from "@/models/EventDate.ts";
import type {AttendeeDto} from "@/dto/AttendeeDto.ts"; import type {AttendeeDto} from "@/dto/AttendeeDto.ts";
import { AttendanceDto } from "./AttendanceDto.ts";
export class EventDto { export class EventDto {
public name: string; public name: string;
public token: string; public token: string;
public dates : Map<number, AttendeeDto[]> public attendances : AttendanceDto[];
public constructor(name: string, token: string, dates: Map<number, AttendeeDto[]>) { public constructor(name: string, token: string, attendances : AttendanceDto[]) {
this.name = name; this.name = name;
this.token = token; this.token = token;
this.dates = dates; this.attendances = attendances;
} }
} }

View File

@@ -4,4 +4,9 @@ export abstract class DateHelper {
return new Date(timestamp); return new Date(timestamp);
} }
public static formatDate(timestamp : number) : string{
let date = DateHelper.toDate(timestamp);
return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`;
}
} }

View File

@@ -0,0 +1,12 @@
import type {EventDate} from "@/models/EventDate.ts";
import type {Attendee} from "@/models/Attendee.ts";
export class Attendance {
public date : EventDate
public attendee : Attendee
public constructor(date : EventDate, attendee : Attendee) {
this.date = date;
this.attendee = attendee;
}
}

View File

@@ -0,0 +1,26 @@
import type {Event} from "@/models/Event.ts";
import type {Attendee} from "@/models/Attendee.ts";
export class AttendanceGraph {
private event: Event;
private readonly maxAttendees: number;
public constructor(event : Event) {
this.event = event;
this.maxAttendees = event.getMaxAttendees();
}
public getDates() : number[] {
return Array.from(this.event.getDates());
}
public getAttendees(date : number): Attendee[] {
return this.event.getAttendees(date);
}
public getRatio(date : number) : number{
return (this.getAttendees(date).length / this.maxAttendees);
}
}

View File

@@ -1,16 +1,15 @@
import type {Attendee, AttendeeState} from "@/models/Attendee.ts"; import type {Attendee, AttendeeState} from "@/models/Attendee.ts";
import type {EventDate, EventDateState} from "@/models/EventDate.ts";
export class Event { export class Event {
private name: string; private name: string;
private token: string; private token: string;
private dates: Map<number, Attendee[]>; private attendances: Map<number, Attendee[]>;
public constructor(name: string, token: string, dates : Map<number, Attendee[]>) { public constructor(name: string, token: string, dates : Map<number, Attendee[]>) {
this.name = name; this.name = name;
this.token = token; this.token = token;
this.dates = dates; this.attendances = dates;
} }
public getName() : string { public getName() : string {
@@ -21,9 +20,25 @@ export class Event {
return this.token; return this.token;
} }
public getDates() : Map<number, Attendee[]> { public getDates() : number[] {
return this.dates; return Array.from(this.attendances.keys()).sort();
} }
public getAttendees(date : number) : Attendee[] {
if(this.attendances.has(date)){
return this.attendances.get(date)!; //Fuck TS
}
return [];
}
public getMaxAttendees(){
let max = 0;
this.attendances.forEach(attendee => {
if(attendee.length > max) max = attendee.length;
})
return max;
}
} }
export interface EventState { export interface EventState {

View File

@@ -25,7 +25,8 @@ export class EventRequests {
return fetch(url) return fetch(url)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
return new EventDto(data.name, data.token, data.dates) console.log(data);
return new EventDto(data.name, data.token, data.attendances);
}) })
.catch(error => console.error(error)); .catch(error => console.error(error));
} }

View File

@@ -3,14 +3,17 @@ import {EventRequests} from "@/requests/EventRequests.ts";
import {EventDto} from "@/dto/EventDto.ts"; import {EventDto} from "@/dto/EventDto.ts";
import {Event, type EventState} from "@/models/Event.ts"; import {Event, type EventState} from "@/models/Event.ts";
import {Attendee, type AttendeeState} from "@/models/Attendee.ts"; import {Attendee, type AttendeeState} from "@/models/Attendee.ts";
import type {AttendeeDto} from "@/dto/AttendeeDto.ts"; import {AttendanceDto} from "@/dto/AttendanceDto.ts";
import {TimeStampDto} from "@/dto/TimeStampDto.ts";
import {AttendeeDto} from "@/dto/AttendeeDto.ts";
const requests = new EventRequests(); const requests = new EventRequests();
function mapToDto(state : EventState) : EventDto{ function mapToDto(state : EventState) : EventDto{
let dates : Map<number, AttendeeDto[]> = new Map<number, AttendeeDto[]>(); let dates : AttendanceDto[] = [];
for (let [date, attendeesState] of state.dates.entries()) { for (let [date, attendeesState] of state.dates.entries()) {
dates.set(date, []); let attendance : AttendanceDto = new AttendanceDto(new TimeStampDto(new Date(date)), new AttendeeDto(""));
dates.push(attendance);
} }
return new EventDto(state.name, state.token, dates); return new EventDto(state.name, state.token, dates);
} }
@@ -27,12 +30,18 @@ function mapToModel(state : EventState) : Event{
} }
function mapToState(dto : EventDto) : EventState { function mapToState(dto : EventDto) : EventState {
let event : EventState = {name : "", token : "", dates: new Map<number, AttendeeState[]>()}; let event : EventState = { name : "", token : "", dates: new Map<number, AttendeeState[]>() };
event.name = dto.name; event.name = dto.name;
event.token = dto.token; event.token = dto.token;
Object.entries(dto.dates).forEach(([key, attendees]) => { dto.attendances.forEach(attendance => {
event.dates.set(Number(key), attendees); let timestamp = attendance.date.timestamp;
}); let attendee = attendance.attendee;
if(event.dates.has(timestamp)) {
event.dates.get(timestamp)?.push({name : attendee.name});
} else {
event.dates.set(timestamp, [{name : attendee.name}]);
}
})
return event; return event;
} }
@@ -97,7 +106,6 @@ export const eventCreationStore = defineStore('eventStore', {
console.error(error); console.error(error);
throw new Error("Unable to post. " + error); throw new Error("Unable to post. " + error);
} }
} }
}, },
}); });

View File

@@ -37,22 +37,22 @@ function updateDates(value: number) {
store.toggleDate(value) store.toggleDate(value)
} }
function displayError(message: string, target : Element) { function displayError(message: string, target : HTMLElement) {
target.style.border = "2px solid red"; target.style.border = "2px solid red";
errorMessage.value = message; errorMessage.value = message;
} }
function resetError(target : Element){ function resetError(target : HTMLElement){
target.style.border = "2px solid black"; target.style.border = "2px solid black";
errorMessage.value = ""; errorMessage.value = "";
} }
function getNameField() : Element { function getNameField() : HTMLElement {
return document.getElementsByClassName("input-field")[0]; return document.getElementsByClassName("input-field")[0] as HTMLElement;
} }
function getCalendar() : Element { function getCalendar() : HTMLElement {
return document.getElementsByClassName("calendar-container")[0]; return document.getElementsByClassName("calendar-container")[0] as HTMLElement;
} }
</script> </script>
@@ -121,6 +121,7 @@ function getCalendar() : Element {
.title h1 .title h1
{ {
font-size: 4rem; font-size: 4rem;
margin: 0 5px 5px 5px;
} }
.calendar { .calendar {
@@ -144,6 +145,13 @@ function getCalendar() : Element {
.title { .title {
height: fit-content; height: fit-content;
} }
.text-block h1
{
font-size: 2rem;
margin: 0 5px 5px 5px;
}
} }
</style> </style>

View File

@@ -5,6 +5,8 @@ import {useRoute} from "vue-router";
import {DateHelper} from "@/helpers/DateHelper.ts"; import {DateHelper} from "@/helpers/DateHelper.ts";
import {Event} from "@/models/Event.ts"; import {Event} from "@/models/Event.ts";
import ErrorBlock from "@/components/ErrorBlock.vue"; import ErrorBlock from "@/components/ErrorBlock.vue";
import AttendanceGraph from "@/components/AttendanceGraph.vue";
import Calendar from "@/components/Calendar.vue";
const route = useRoute(); const route = useRoute();
const store = eventCreationStore(); const store = eventCreationStore();
@@ -25,38 +27,47 @@ function extractToken() : string {
return Array.isArray(route.params.token) ? route.params.token[0] : route.params.token; return Array.isArray(route.params.token) ? route.params.token[0] : route.params.token;
} }
function formatDate(timestamp : number) : String{
let date = DateHelper.toDate(timestamp);
return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`;
}
</script> </script>
<template> <template>
<div class="container"> <div v-if="!event" class="container error-block">
<div v-if="!event" class="error-block">
<ErrorBlock> <ErrorBlock>
<h1>This event does not exist</h1> <h1>This event does not exist</h1>
</ErrorBlock> </ErrorBlock>
</div> </div>
<div v-else class="container">
<div v-else> <AttendanceGraph :event="event" />
Name : {{ event.getName() }} <br> <Calendar class="calendar"/>
<div v-for="(date) in event.getDates().keys()">
{{formatDate(date)}}
<div v-for="(attendee) in event.getDates().get(date)">
{{attendee.getName()}}
</div>
</div>
</div> </div>
</div>
</template> </template>
<style scoped> <style scoped>
.error-block{ .error-block{
width: 100%; width: 100%;
} }
@media screen and (min-width: 1001px) {
.attendance-graph, .calendar
{
width: 50%;
height: fit-content;
}
}
@media screen and (max-width: 1000px) {
.attendance-graph, .calendar
{
width: 100%;
}
.attendance-graph, .calendar
{
height: fit-content;
}
}
</style> </style>

View File

@@ -130,7 +130,7 @@ import TextBlock from "@/components/TextBlock.vue";
.text-block h1 .text-block h1
{ {
font-size: 2.5rem; font-size: 2rem;
margin: 0 5px 5px 5px; margin: 0 5px 5px 5px;
} }

View File

@@ -32,7 +32,6 @@ const token = ref("");
min-width: 300px; min-width: 300px;
} }
.actions-group { .actions-group {
min-height: 250px; min-height: 250px;
} }
@@ -131,7 +130,7 @@ const token = ref("");
.text-block h1 .text-block h1
{ {
font-size: 2.5rem; font-size: 2rem;
margin: 0 5px 5px 5px; margin: 0 5px 5px 5px;
} }