Compare commits
10 Commits
4bbbd94ad2
...
c27c525cda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c27c525cda | ||
|
|
bacdc94b1e | ||
|
|
577603df64 | ||
|
|
ba31dd3d05 | ||
|
|
546506cf59 | ||
|
|
4ef9d588f6 | ||
|
|
0c5851b146 | ||
|
|
be5e86c10d | ||
|
|
02ce43e7b7 | ||
|
|
b31884e626 |
@@ -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.LogoutConfigurer;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
@@ -16,6 +17,7 @@ import java.util.Arrays;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableTransactionManagement
|
||||
public class AppSecurity {
|
||||
|
||||
private final AppConfigurations conf;
|
||||
@@ -29,7 +31,8 @@ public class AppSecurity {
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
return http
|
||||
.cors(cors -> {})
|
||||
.cors(cors -> {
|
||||
})
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.requestMatchers("/**").permitAll()
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package be.naaturel.letsmeet.configurations;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
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.WebMvcConfigurer;
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.io.Console;
|
||||
|
||||
public class Interceptor implements HandlerInterceptor {
|
||||
|
||||
// Request is intercepted by this method before reaching the Controller
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,19 @@
|
||||
package be.naaturel.letsmeet.core.models;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class Attendee {
|
||||
|
||||
private String name;
|
||||
|
||||
private Set<EventDate> dates;
|
||||
|
||||
public Attendee(String name) {
|
||||
this(name, new HashSet<>());
|
||||
}
|
||||
|
||||
public Attendee(String name, Set<EventDate> dates){
|
||||
this.name = name;
|
||||
this.dates = dates;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
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
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
package be.naaturel.letsmeet.core.models;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Set;
|
||||
|
||||
public class Event {
|
||||
|
||||
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.attendees = attendees;
|
||||
this.attendances = attendances;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Set<Attendee> getAttendees(){
|
||||
return new HashSet<>(this.attendees);
|
||||
public Set<Attendance> getAttendances() {
|
||||
return this.attendances;
|
||||
}
|
||||
|
||||
public Set<EventDate> getDates() {
|
||||
Set<EventDate> dates = new HashSet<>();
|
||||
for (Attendee p : this.attendees) {
|
||||
dates.addAll(p.getDates());
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package be.naaturel.letsmeet.core.models;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class EventDate {
|
||||
|
||||
private long timeStamp;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package be.naaturel.letsmeet.dto.database;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.Objects;
|
||||
|
||||
@Entity(name = "Attendees")
|
||||
public class AttendeeEntity {
|
||||
@@ -14,12 +14,17 @@ public class AttendeeEntity {
|
||||
@Column
|
||||
public String name;
|
||||
|
||||
@ManyToMany(cascade = {CascadeType.ALL})
|
||||
@JoinTable
|
||||
public Set<EventDateEntity> dates;
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
AttendeeEntity a = (AttendeeEntity) obj;
|
||||
return Objects.equals(name, a.name);
|
||||
}
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(nullable=true)
|
||||
public EventEntity event;
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package be.naaturel.letsmeet.dto.database;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity(name = "Dates")
|
||||
@Table(uniqueConstraints = {
|
||||
@@ -18,13 +17,6 @@ public class EventDateEntity {
|
||||
@Column
|
||||
public long timeStamp;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(nullable=true)
|
||||
public EventEntity event;
|
||||
|
||||
@ManyToMany(mappedBy = "dates")
|
||||
public Set<AttendeeEntity> attendees;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
|
||||
@@ -2,7 +2,7 @@ package be.naaturel.letsmeet.dto.database;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity(name = "Events")
|
||||
public class EventEntity {
|
||||
@@ -17,36 +17,9 @@ public class EventEntity {
|
||||
@Column(unique = true)
|
||||
public String token;
|
||||
|
||||
@Column
|
||||
@OneToMany(targetEntity=EventDateEntity.class, cascade=CascadeType.ALL, mappedBy="event")
|
||||
public Set<EventDateEntity> dates;
|
||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@JoinColumn(name = "event_id")
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package be.naaturel.letsmeet.dto.httpRequest;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class AttendeeDTO {
|
||||
|
||||
public String name;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package be.naaturel.letsmeet.dto.httpRequest;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class EventDTO {
|
||||
|
||||
public String name;
|
||||
public String token;
|
||||
public Map<String, Set<AttendeeDTO>> dates;
|
||||
public Set<AttendanceDTO> attendances;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
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.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.HashSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class AttendeeMapper implements Mapper<Attendee, AttendeeEntity> {
|
||||
@@ -21,13 +19,16 @@ public class AttendeeMapper implements Mapper<Attendee, AttendeeEntity> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttendeeEntity toEntity(Attendee d) {
|
||||
return DatabasePropsFactory.createAttendee(d.getName(), dateMapper.toEntities(d.getDates(), HashSet::new));
|
||||
public AttendeeEntity toEntity(Attendee a) {
|
||||
//return DatabasePropsFactory.createAttendee(d.getName(), dateMapper.toEntities(d.getDates(), HashSet::new));
|
||||
AttendeeEntity ae = new AttendeeEntity();
|
||||
ae.name = a.getName();
|
||||
return ae;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attendee toModel(AttendeeEntity d) {
|
||||
return new Attendee(d.name, dateMapper.toModels(d.dates, HashSet::new));
|
||||
return new Attendee(d.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
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.dto.database.EventDateEntity;
|
||||
import be.naaturel.letsmeet.mappers.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
@@ -13,7 +12,9 @@ public class EventDateMapper implements Mapper<EventDate, EventDateEntity> {
|
||||
|
||||
@Override
|
||||
public EventDateEntity toEntity(EventDate d) {
|
||||
return DatabasePropsFactory.createDate(d.getTimeStamp());
|
||||
EventDateEntity ede = new EventDateEntity();
|
||||
ede.timeStamp = d.getTimeStamp();
|
||||
return ede;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
package be.naaturel.letsmeet.mappers.database;
|
||||
|
||||
import be.naaturel.letsmeet.dto.database.EventEntity;
|
||||
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.helpers.TokenGenerator;
|
||||
import be.naaturel.letsmeet.core.models.Attendance;
|
||||
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;
|
||||
|
||||
public class EventMapper implements Mapper<Event, EventEntity> {
|
||||
|
||||
private final Mapper<Attendee, AttendeeEntity> attendeeMapper;
|
||||
private final Mapper<Attendance, AttendanceEntity> attendanceMapper;
|
||||
|
||||
public EventMapper() {
|
||||
this.attendeeMapper = new AttendeeMapper();
|
||||
this.attendanceMapper = new AttendanceMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
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
|
||||
public <C extends Collection<EventEntity>> C toEntities(Collection<Event> events, Supplier<C> collectionSupplier) {
|
||||
C result = collectionSupplier.get();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
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.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.HashSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class AttendeeDTOMapper implements Mapper<Attendee, AttendeeDTO> {
|
||||
@@ -27,7 +26,7 @@ public class AttendeeDTOMapper implements Mapper<Attendee, AttendeeDTO> {
|
||||
|
||||
@Override
|
||||
public Attendee toModel(AttendeeDTO d) {
|
||||
return null;
|
||||
return new Attendee(d.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,58 +1,36 @@
|
||||
package be.naaturel.letsmeet.mappers.requests;
|
||||
|
||||
import be.naaturel.letsmeet.core.models.EventDate;
|
||||
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.Attendance;
|
||||
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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class EventDTOMapper implements Mapper<Event, EventDTO> {
|
||||
|
||||
private final Mapper<Attendee, AttendeeDTO> attendeeMapper = new AttendeeDTOMapper();
|
||||
private final Mapper<EventDate, EventDateDTO> dateMapper = new EventDateDTOMapper();
|
||||
private final Mapper<Attendance, AttendanceDTO> attendanceMapper;
|
||||
|
||||
public EventDTOMapper() {
|
||||
this.attendanceMapper = new AttendanceDTOMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventDTO toEntity(Event event) {
|
||||
EventDTO eventDTO = new EventDTO();
|
||||
eventDTO.name = event.getName();
|
||||
//eventDTO.dates = attendeeMapper.toEntities(event.getAttendees(), 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
eventDTO.attendances = attendanceMapper.toEntities(event.getAttendances(), HashSet::new);
|
||||
return eventDTO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event toModel(EventDTO dto) {
|
||||
//return new Event(dto.name, attendeeMapper.toModels(dto.dates, HashSet::new));
|
||||
Set<Attendee> set = new HashSet<>();
|
||||
for (String key : dto.dates.keySet()) {
|
||||
set.addAll(attendeeMapper.toModels(dto.dates.get(key), HashSet::new));
|
||||
}
|
||||
return new Event(dto.name, set);
|
||||
Set<Attendance> attendances = attendanceMapper.toModels(dto.attendances, HashSet::new);
|
||||
return new Event(dto.name, attendances);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package be.naaturel.letsmeet.mappers.requests;
|
||||
|
||||
import be.naaturel.letsmeet.core.models.EventDate;
|
||||
import be.naaturel.letsmeet.dto.httpRequest.EventDateDTO;
|
||||
import be.naaturel.letsmeet.mappers.Mapper;
|
||||
import be.naaturel.letsmeet.core.models.EventDate;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -1,15 +1,88 @@
|
||||
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 org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import be.naaturel.letsmeet.mappers.Mapper;
|
||||
import be.naaturel.letsmeet.mappers.database.EventMapper;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ public abstract class AbstractService<T, T_ENTITY, T_DTO> {
|
||||
}
|
||||
|
||||
public abstract Result<?> save(T_DTO prop) throws Exception;
|
||||
|
||||
public abstract boolean delete(T_DTO prop);
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package be.naaturel.letsmeet.services;
|
||||
|
||||
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.httpRequest.EventDTO;
|
||||
import be.naaturel.letsmeet.mappers.database.EventMapper;
|
||||
import be.naaturel.letsmeet.mappers.requests.EventDTOMapper;
|
||||
import be.naaturel.letsmeet.core.models.Event;
|
||||
import be.naaturel.letsmeet.repositories.EventRepo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
@@ -25,21 +25,19 @@ public class EventService extends AbstractService<Event, EventEntity, EventDTO>
|
||||
@Override
|
||||
public Result<String> save(EventDTO dto) throws Exception {
|
||||
Event event = this.requestMapper.toModel(dto);
|
||||
EventEntity entity = this.dataBaseMapper.toEntity(event);
|
||||
try {
|
||||
this.repo.save(entity);
|
||||
return new Result<>(entity.token);
|
||||
String res = this.repo.save(event);
|
||||
return new Result<>(res);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Something went wrong");
|
||||
throw new Exception("Something went wrong : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(EventDTO dto) {
|
||||
Event event = this.requestMapper.toModel(dto);
|
||||
EventEntity entity = this.dataBaseMapper.toEntity(event);
|
||||
try {
|
||||
this.repo.delete(entity);
|
||||
this.repo.delete(event);
|
||||
return true;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
return false;
|
||||
@@ -50,10 +48,8 @@ public class EventService extends AbstractService<Event, EventEntity, EventDTO>
|
||||
|
||||
public EventDTO getEvent(String token) {
|
||||
try {
|
||||
EventEntity entity = this.repo.findEventEntityByToken(token);
|
||||
Event model = dataBaseMapper.toModel(entity);
|
||||
EventDTO dto = requestMapper.toEntity(model);
|
||||
return dto;
|
||||
Event event = this.repo.getByToken(token);
|
||||
return requestMapper.toEntity(event);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
#=============MAIN=============
|
||||
spring.application.name=letsmeet
|
||||
|
||||
#=============SERVER=============
|
||||
server.port=5001
|
||||
|
||||
#=============SECURITY=============
|
||||
sec.cors.authorizedHots=*
|
||||
sec.cors.authorizedMethods=GET,POST,PUT,DELETE,OPTION
|
||||
sec.cors.authorizedHeader=Authorization,Content-type
|
||||
|
||||
#=============DATABASE=============
|
||||
spring.datasource.url=jdbc:${DB_URL}
|
||||
spring.datasource.username=${DB_USER}
|
||||
spring.datasource.password=${DB_PASSWORD}
|
||||
|
||||
spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}*/
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package be.naaturel.letsmeet.mock.dto;
|
||||
|
||||
import be.naaturel.letsmeet.dto.httpRequest.AttendeeDTO;
|
||||
|
||||
public class MockAttendeeDTO extends AttendeeDTO {
|
||||
|
||||
public MockAttendeeDTO(){
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package be.naaturel.letsmeet.mock.jpa;
|
||||
|
||||
import be.naaturel.letsmeet.dto.database.EventEntity;
|
||||
|
||||
public class MockEventEntity extends EventEntity {
|
||||
|
||||
public MockEventEntity(){
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<>());
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package be.naaturel.letsmeet.repositories;
|
||||
|
||||
public class EventRepoTest {
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
19
front/package-lock.json
generated
19
front/package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "letsmeet",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"chart.js": "^4.4.8",
|
||||
"express-requests-logger": "^4.0.2",
|
||||
"pinia": "^3.0.1",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
@@ -1367,6 +1368,12 @@
|
||||
"@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": {
|
||||
"version": "2.1.5",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"lint": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"chart.js": "^4.4.8",
|
||||
"express-requests-logger": "^4.0.2",
|
||||
"pinia": "^3.0.1",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
|
||||
107
front/src/components/AttendanceGraph.vue
Normal file
107
front/src/components/AttendanceGraph.vue
Normal 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>
|
||||
15
front/src/dto/AttendanceDto.ts
Normal file
15
front/src/dto/AttendanceDto.ts
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
import type {Attendee} from "@/models/Attendee.ts";
|
||||
import type {EventDate} from "@/models/EventDate.ts";
|
||||
import type {AttendeeDto} from "@/dto/AttendeeDto.ts";
|
||||
import { AttendanceDto } from "./AttendanceDto.ts";
|
||||
|
||||
export class EventDto {
|
||||
|
||||
public name: 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.token = token;
|
||||
this.dates = dates;
|
||||
this.attendances = attendances;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,4 +4,9 @@ export abstract class DateHelper {
|
||||
return new Date(timestamp);
|
||||
}
|
||||
|
||||
public static formatDate(timestamp : number) : string{
|
||||
let date = DateHelper.toDate(timestamp);
|
||||
return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
12
front/src/models/Attendance.ts
Normal file
12
front/src/models/Attendance.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
26
front/src/models/AttendanceGraph.ts
Normal file
26
front/src/models/AttendanceGraph.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
import type {Attendee, AttendeeState} from "@/models/Attendee.ts";
|
||||
import type {EventDate, EventDateState} from "@/models/EventDate.ts";
|
||||
|
||||
export class Event {
|
||||
|
||||
private name: string;
|
||||
private token: string;
|
||||
private dates: Map<number, Attendee[]>;
|
||||
private attendances: Map<number, Attendee[]>;
|
||||
|
||||
public constructor(name: string, token: string, dates : Map<number, Attendee[]>) {
|
||||
this.name = name;
|
||||
this.token = token;
|
||||
this.dates = dates;
|
||||
this.attendances = dates;
|
||||
}
|
||||
|
||||
public getName() : string {
|
||||
@@ -21,9 +20,25 @@ export class Event {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
public getDates() : Map<number, Attendee[]> {
|
||||
return this.dates;
|
||||
public getDates() : number[] {
|
||||
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 {
|
||||
|
||||
@@ -25,7 +25,8 @@ export class EventRequests {
|
||||
return fetch(url)
|
||||
.then(response => response.json())
|
||||
.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));
|
||||
}
|
||||
|
||||
@@ -3,14 +3,17 @@ import {EventRequests} from "@/requests/EventRequests.ts";
|
||||
import {EventDto} from "@/dto/EventDto.ts";
|
||||
import {Event, type EventState} from "@/models/Event.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();
|
||||
|
||||
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()) {
|
||||
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);
|
||||
}
|
||||
@@ -30,9 +33,15 @@ function mapToState(dto : EventDto) : EventState {
|
||||
let event : EventState = { name : "", token : "", dates: new Map<number, AttendeeState[]>() };
|
||||
event.name = dto.name;
|
||||
event.token = dto.token;
|
||||
Object.entries(dto.dates).forEach(([key, attendees]) => {
|
||||
event.dates.set(Number(key), attendees);
|
||||
});
|
||||
dto.attendances.forEach(attendance => {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -97,7 +106,6 @@ export const eventCreationStore = defineStore('eventStore', {
|
||||
console.error(error);
|
||||
throw new Error("Unable to post. " + error);
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,22 +37,22 @@ function updateDates(value: number) {
|
||||
store.toggleDate(value)
|
||||
}
|
||||
|
||||
function displayError(message: string, target : Element) {
|
||||
function displayError(message: string, target : HTMLElement) {
|
||||
target.style.border = "2px solid red";
|
||||
errorMessage.value = message;
|
||||
}
|
||||
|
||||
function resetError(target : Element){
|
||||
function resetError(target : HTMLElement){
|
||||
target.style.border = "2px solid black";
|
||||
errorMessage.value = "";
|
||||
}
|
||||
|
||||
function getNameField() : Element {
|
||||
return document.getElementsByClassName("input-field")[0];
|
||||
function getNameField() : HTMLElement {
|
||||
return document.getElementsByClassName("input-field")[0] as HTMLElement;
|
||||
}
|
||||
|
||||
function getCalendar() : Element {
|
||||
return document.getElementsByClassName("calendar-container")[0];
|
||||
function getCalendar() : HTMLElement {
|
||||
return document.getElementsByClassName("calendar-container")[0] as HTMLElement;
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -121,6 +121,7 @@ function getCalendar() : Element {
|
||||
.title h1
|
||||
{
|
||||
font-size: 4rem;
|
||||
margin: 0 5px 5px 5px;
|
||||
}
|
||||
|
||||
.calendar {
|
||||
@@ -144,6 +145,13 @@ function getCalendar() : Element {
|
||||
.title {
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.text-block h1
|
||||
{
|
||||
font-size: 2rem;
|
||||
margin: 0 5px 5px 5px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -5,6 +5,8 @@ import {useRoute} from "vue-router";
|
||||
import {DateHelper} from "@/helpers/DateHelper.ts";
|
||||
import {Event} from "@/models/Event.ts";
|
||||
import ErrorBlock from "@/components/ErrorBlock.vue";
|
||||
import AttendanceGraph from "@/components/AttendanceGraph.vue";
|
||||
import Calendar from "@/components/Calendar.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const store = eventCreationStore();
|
||||
@@ -25,38 +27,47 @@ function extractToken() : string {
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
|
||||
<div v-if="!event" class="error-block">
|
||||
<div v-if="!event" class="container error-block">
|
||||
<ErrorBlock>
|
||||
<h1>This event does not exist</h1>
|
||||
</ErrorBlock>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
Name : {{ event.getName() }} <br>
|
||||
<div v-for="(date) in event.getDates().keys()">
|
||||
{{formatDate(date)}}
|
||||
<div v-for="(attendee) in event.getDates().get(date)">
|
||||
{{attendee.getName()}}
|
||||
<div v-else class="container">
|
||||
<AttendanceGraph :event="event" />
|
||||
<Calendar class="calendar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.error-block{
|
||||
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>
|
||||
|
||||
@@ -130,7 +130,7 @@ import TextBlock from "@/components/TextBlock.vue";
|
||||
|
||||
.text-block h1
|
||||
{
|
||||
font-size: 2.5rem;
|
||||
font-size: 2rem;
|
||||
margin: 0 5px 5px 5px;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ const token = ref("");
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
|
||||
.actions-group {
|
||||
min-height: 250px;
|
||||
}
|
||||
@@ -131,7 +130,7 @@ const token = ref("");
|
||||
|
||||
.text-block h1
|
||||
{
|
||||
font-size: 2.5rem;
|
||||
font-size: 2rem;
|
||||
margin: 0 5px 5px 5px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user