Initial commit, working server
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package no.asprusten.sykkelaksjon;
|
||||
|
||||
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.PropertiesPropertySource;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class PropertiesListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
|
||||
// Make this actually find the postgres password and run when not in a Docker container
|
||||
// Also make it find the static files
|
||||
ConfigurableEnvironment environment = event.getEnvironment();
|
||||
String passwordSetting = environment.getProperty("postgrespassword");
|
||||
if (passwordSetting == null) {
|
||||
File passwordFile = new File("../secrets/postgrespassword");
|
||||
if (passwordFile.canRead()) {
|
||||
try {
|
||||
Scanner reader = new Scanner(passwordFile);
|
||||
if (reader.hasNextLine()) {
|
||||
String password = reader.nextLine();
|
||||
Properties props = new Properties();
|
||||
props.put("postgrespassword", password);
|
||||
environment.getPropertySources().addFirst(new PropertiesPropertySource("passwordProps", props));
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// Do nothing, continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String staticResourcesPath = environment.getProperty("spring.web.resources.static-locations");
|
||||
File staticResources = new File(staticResourcesPath.replaceFirst("file:", ""));
|
||||
if (!staticResources.exists()) {
|
||||
// Look for static resources in the default location
|
||||
File nonDockerStatic = new File("../client/dist");
|
||||
if (nonDockerStatic.exists()) {
|
||||
Properties staticProps = new Properties();
|
||||
try {
|
||||
staticProps.put("spring.web.resources.static-locations", "file:" + nonDockerStatic.getCanonicalPath());
|
||||
environment.getPropertySources().addFirst(new PropertiesPropertySource("staticProps", staticProps));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAsyncExecution() {
|
||||
return ApplicationListener.super.supportsAsyncExecution();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package no.asprusten.sykkelaksjon;
|
||||
|
||||
import no.asprusten.sykkelaksjon.db.services.ActivityService;
|
||||
import no.asprusten.sykkelaksjon.db.services.ActivityTemplateService;
|
||||
import no.asprusten.sykkelaksjon.db.services.ActivityTypeService;
|
||||
import no.asprusten.sykkelaksjon.db.services.UserService;
|
||||
import no.asprusten.sykkelaksjon.messages.*;
|
||||
import no.asprusten.sykkelaksjon.security.ServerExceptionHandler;
|
||||
import org.pac4j.core.profile.ProfileManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hello world!
|
||||
*/
|
||||
@RestController
|
||||
@SpringBootApplication
|
||||
public class Server {
|
||||
@Autowired
|
||||
private ProfileManager profileManager;
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
@Autowired
|
||||
private ActivityTypeService activityTypeService;
|
||||
@Autowired
|
||||
private ActivityService activityService;
|
||||
@Autowired
|
||||
private ActivityTemplateService activityTemplateService;
|
||||
|
||||
@Value("${sykkelaksjon.openid.discoveryURI}")
|
||||
private String discoveryURI;
|
||||
|
||||
@Value("${sykkelaksjon.openid.clientId}")
|
||||
private String clientId;
|
||||
|
||||
private ActivityType getActivityTypeMessage(no.asprusten.sykkelaksjon.db.datatypes.ActivityType activityType) {
|
||||
ActivityType activityTypeMessage = new ActivityType();
|
||||
activityTypeMessage.setId(activityType.getId());
|
||||
activityTypeMessage.setName(activityType.getActivityType());
|
||||
activityTypeMessage.setConversionFactor(activityType.getConversion());
|
||||
activityTypeMessage.setUnit(activityType.getUnit());
|
||||
return activityTypeMessage;
|
||||
}
|
||||
|
||||
private ActivityTemplate getActivityTemplateMessage(no.asprusten.sykkelaksjon.db.datatypes.ActivityTemplate activityTemplate) {
|
||||
ActivityTemplate templateMessage = new ActivityTemplate();
|
||||
templateMessage.setId(activityTemplate.getId());
|
||||
templateMessage.setActivityType(getActivityTypeMessage(activityTemplate.getActivityType()));
|
||||
templateMessage.setName(activityTemplate.getName());
|
||||
templateMessage.setNumberOfUnits(activityTemplate.getNumberOfUnits());
|
||||
return templateMessage;
|
||||
}
|
||||
|
||||
private Activity getActivityMessage(no.asprusten.sykkelaksjon.db.datatypes.Activity activity) {
|
||||
Activity activityMessage = new Activity();
|
||||
activityMessage.setId(activity.getId());
|
||||
activityMessage.setActivityType(getActivityTypeMessage(activity.getActivityType()));
|
||||
activityMessage.setDescription(activity.getDescription());
|
||||
activityMessage.setNumberOfUnits(activity.getNumberOfUnits());
|
||||
activityMessage.setDate(activity.getDate().format(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
return activityMessage;
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@GetMapping("/api")
|
||||
public ServerMessageSchema respondToRequest() throws ServerExceptionHandler.InvalidUserException {
|
||||
var optionalUserProfile = profileManager.getProfile();
|
||||
if (optionalUserProfile.isEmpty()) {
|
||||
throw new ServerExceptionHandler.InvalidUserException();
|
||||
}
|
||||
|
||||
var userProfile = optionalUserProfile.get();
|
||||
|
||||
var optionalUser = userService.getUser(userProfile.getUsername());
|
||||
var user = optionalUser
|
||||
.orElseGet(
|
||||
() -> userService.createUser(
|
||||
userProfile.getUsername(),
|
||||
userProfile.getAttribute("name").toString()
|
||||
)
|
||||
);
|
||||
|
||||
var allUsers = userService.list();
|
||||
ServerMessageSchema serverMessage = new ServerMessageSchema();
|
||||
serverMessage.setName(user.getFullName());
|
||||
serverMessage.setIsAdmin(user.isAdmin());
|
||||
|
||||
boolean unitConversionNecessary = false;
|
||||
for (var activityType : activityTypeService.list()) {
|
||||
serverMessage.getActivityTypes().add(getActivityTypeMessage(activityType));
|
||||
unitConversionNecessary |= activityType.getConversion() != 1.0;
|
||||
}
|
||||
serverMessage.setUnitConversionNecessary(unitConversionNecessary);
|
||||
|
||||
for (var activityTemplate : user.getTemplates()) {
|
||||
serverMessage.getActivityTemplates().add(getActivityTemplateMessage(activityTemplate));
|
||||
}
|
||||
|
||||
List<Activity> unsortedActivities = new ArrayList<>();
|
||||
|
||||
for (var activity : user.getActivities()) {
|
||||
unsortedActivities.add(getActivityMessage(activity));
|
||||
}
|
||||
unsortedActivities.sort(Comparator.comparing(Activity::getDate));
|
||||
serverMessage.getActivities().addAll(unsortedActivities.reversed());
|
||||
|
||||
List<OtherUser> unsortedOtherUsers = new ArrayList<>();
|
||||
for (var otherUser : allUsers) {
|
||||
// Don't describe the current user
|
||||
if (otherUser.getId().equals(user.getId())) {
|
||||
continue;
|
||||
}
|
||||
// If this is not an active user, and the requesting user is not an admin, skip
|
||||
if (!otherUser.isActive() && !user.isAdmin()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the user has not registered anything yet, also skip it (unless admin)
|
||||
if (otherUser.getActivities().isEmpty() && !user.isAdmin()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
OtherUser otherUserMessage = new OtherUser();
|
||||
otherUserMessage.setName(otherUser.getFullName());
|
||||
|
||||
double totalKilometers = otherUser.getActivities()
|
||||
.stream()
|
||||
.mapToDouble(activity -> activity.getActivityType().getConversion() * activity.getNumberOfUnits())
|
||||
.sum();
|
||||
otherUserMessage.setTotalKilometers(totalKilometers);
|
||||
|
||||
otherUser.getActivities().stream()
|
||||
.map(no.asprusten.sykkelaksjon.db.datatypes.Activity::getDate)
|
||||
.min(LocalDate::compareTo)
|
||||
.ifPresent(earliestDate ->
|
||||
otherUserMessage.setEarliestActivity(earliestDate.format(DateTimeFormatter.ISO_LOCAL_DATE))
|
||||
);
|
||||
|
||||
otherUser.getActivities().stream()
|
||||
.map(no.asprusten.sykkelaksjon.db.datatypes.Activity::getDate)
|
||||
.max(LocalDate::compareTo)
|
||||
.ifPresent(latestDate ->
|
||||
otherUserMessage.setLatestActivity(latestDate.format(DateTimeFormatter.ISO_LOCAL_DATE))
|
||||
);
|
||||
|
||||
// Additional info is only for administrators
|
||||
if (user.isAdmin()) {
|
||||
otherUserMessage.setUserId(otherUser.getId());
|
||||
otherUserMessage.setUserName(otherUser.getUsername());
|
||||
otherUserMessage.setIsActive(otherUser.isActive());
|
||||
otherUserMessage.setIsAdmin(otherUser.isAdmin());
|
||||
List<Activity> userUnsortedActivities = new ArrayList<>();
|
||||
for (var activity : otherUser.getActivities()) {
|
||||
userUnsortedActivities.add(getActivityMessage(activity));
|
||||
}
|
||||
userUnsortedActivities.sort(Comparator.comparing(Activity::getDate));
|
||||
otherUserMessage.getActivities().addAll(userUnsortedActivities.reversed());
|
||||
|
||||
for (var activityTemplate : otherUser.getTemplates()) {
|
||||
otherUserMessage.getActivityTemplates().add(getActivityTemplateMessage(activityTemplate));
|
||||
}
|
||||
}
|
||||
|
||||
unsortedOtherUsers.add(otherUserMessage);
|
||||
}
|
||||
|
||||
unsortedOtherUsers.sort(Comparator.comparing(OtherUser::getTotalKilometers));
|
||||
serverMessage.getOtherUsers().addAll(unsortedOtherUsers.reversed());
|
||||
return serverMessage;
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@PostMapping(path = "/api/submitActivity", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void submitActivity(
|
||||
@RequestParam("activity-type") Long activityTypeId,
|
||||
@RequestParam("activity-distance") double distance,
|
||||
@RequestParam("activity-description") String description,
|
||||
@RequestParam("activity-date") String date
|
||||
) {
|
||||
profileManager.getProfile().ifPresent(userProfile -> {
|
||||
String username = userProfile.getUsername();
|
||||
userService.getUser(username).ifPresent(dbUser -> {
|
||||
activityTypeService.getById(activityTypeId).ifPresent(activityType -> {
|
||||
no.asprusten.sykkelaksjon.db.datatypes.Activity activity = new no.asprusten.sykkelaksjon.db.datatypes.Activity(
|
||||
activityType,
|
||||
dbUser,
|
||||
distance,
|
||||
description,
|
||||
LocalDate.parse(date)
|
||||
);
|
||||
activityService.saveActivity(activity);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@PostMapping(path = "/api/submitActivityTemplate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void submitActivityTemplate(
|
||||
@RequestParam("activity-type") Long activityTypeId,
|
||||
@RequestParam("activity-distance") double distance,
|
||||
@RequestParam("activity-description") String description,
|
||||
@RequestParam("activity-date") String date
|
||||
) {
|
||||
profileManager.getProfile().ifPresent(userProfile -> {
|
||||
String username = userProfile.getUsername();
|
||||
userService.getUser(username).ifPresent(dbUser -> {
|
||||
activityTypeService.getById(activityTypeId).ifPresent(activityType -> {
|
||||
no.asprusten.sykkelaksjon.db.datatypes.ActivityTemplate activityTemplate = new no.asprusten.sykkelaksjon.db.datatypes.ActivityTemplate(
|
||||
dbUser,
|
||||
activityType,
|
||||
description,
|
||||
distance
|
||||
);
|
||||
|
||||
activityTemplateService.saveActivityTemplate(activityTemplate);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@DeleteMapping(path = "/api/deleteActivityTemplate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void deleteActivityTemplate(@RequestParam("activity-template-id") Long activityTemplateId) {
|
||||
profileManager.getProfile().ifPresent(userProfile -> {
|
||||
String username = userProfile.getUsername();
|
||||
userService.getUser(username).ifPresent(dbUser -> {
|
||||
activityTemplateService.findById(activityTemplateId).ifPresent(activityTemplate -> {
|
||||
if (activityTemplate.getOwner().equals(dbUser)) {
|
||||
activityTemplateService.deleteActivityTemplate(activityTemplate.getId());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@DeleteMapping(path = "/api/deleteActivity", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void deleteActivity(@RequestParam("activity-id") Long activityId) {
|
||||
profileManager.getProfile().ifPresent(userProfile -> {
|
||||
String username = userProfile.getUsername();
|
||||
userService.getUser(username).ifPresent(dbUser -> {
|
||||
activityService.findById(activityId).ifPresent(activity -> {
|
||||
if (activity.getActivityOwner().equals(dbUser) || dbUser.isAdmin()) {
|
||||
activityService.deleteActivity(activityId);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@PostMapping(path = "/api/addActivityType", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void createActivityType(
|
||||
@RequestParam("activity-type-name") String name,
|
||||
@RequestParam("activity-type-unit") String unit,
|
||||
@RequestParam("activity-type-conversion-factor") double conversionFactor
|
||||
) {
|
||||
profileManager.getProfile().ifPresent(userProfile -> {
|
||||
String username = userProfile.getUsername();
|
||||
userService.getUser(username).ifPresent(dbUser -> {
|
||||
if (dbUser.isAdmin()) {
|
||||
no.asprusten.sykkelaksjon.db.datatypes.ActivityType activityType = new no.asprusten.sykkelaksjon.db.datatypes.ActivityType(
|
||||
name, unit, conversionFactor
|
||||
);
|
||||
activityTypeService.saveActivityType(activityType);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@DeleteMapping(path = "/api/deleteActivityType", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void deleteActivityType(@RequestParam("activity-type-id") Long activityTypeId) {
|
||||
profileManager.getProfile().ifPresent(userProfile -> {
|
||||
String username = userProfile.getUsername();
|
||||
userService.getUser(username).ifPresent(dbUser -> {
|
||||
if (dbUser.isAdmin()) {
|
||||
activityTypeService.deleteActivityType(activityTypeId);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@PutMapping(path = "/api/makeAdmin", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void makeAdmin(@RequestParam("user-id") Long userId) {
|
||||
profileManager.getProfile().ifPresent(userProfile -> {
|
||||
String username = userProfile.getUsername();
|
||||
userService.getUser(username).ifPresent(dbUser -> {
|
||||
if (dbUser.isAdmin()) {
|
||||
userService.getUserById(userId).ifPresent(elevatedUser -> {
|
||||
elevatedUser.setAdmin(true);
|
||||
userService.saveUser(elevatedUser);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@PutMapping(path = "/api/removeAdmin", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void removeAdmin(@RequestParam("user-id") Long userId) {
|
||||
profileManager.getProfile().ifPresent(userProfile -> {
|
||||
String username = userProfile.getUsername();
|
||||
userService.getUser(username).ifPresent(dbUser -> {
|
||||
if (dbUser.isAdmin()) {
|
||||
userService.getUserById(userId).ifPresent(elevatedUser -> {
|
||||
elevatedUser.setAdmin(false);
|
||||
userService.saveUser(elevatedUser);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@DeleteMapping(path = "/api/deleteUser", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public void deleteUser(@RequestParam("user-id") Long userId) {
|
||||
profileManager.getProfile().ifPresent(userProfile -> {
|
||||
String username = userProfile.getUsername();
|
||||
userService.getUser(username).ifPresent(dbUser -> {
|
||||
if (dbUser.isAdmin()) {
|
||||
userService.deleteUserById(userId);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@CrossOrigin(allowCredentials = "true", origins = {"http://localhost:5173"})
|
||||
@GetMapping(path = "/api/openid")
|
||||
public OpenidSchema provideOpenidConfig() {
|
||||
OpenidSchema openidSchema = new OpenidSchema();
|
||||
openidSchema.setClientId(clientId);
|
||||
openidSchema.setOpenidDiscoveryUri(discoveryURI);
|
||||
return openidSchema;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication application = new SpringApplication(Server.class);
|
||||
application.addListeners(new PropertiesListener());
|
||||
application.run(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package no.asprusten.sykkelaksjon.db.datatypes;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity
|
||||
public class Activity {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private long id;
|
||||
|
||||
@ManyToOne( optional = false)
|
||||
private ActivityType activityType;
|
||||
@ManyToOne(optional = false)
|
||||
private WebUser activityOwner;
|
||||
@Column(nullable = false)
|
||||
private Double numberOfUnits;
|
||||
@Column(nullable = false)
|
||||
private String description;
|
||||
@Column(nullable = false)
|
||||
private LocalDate date;
|
||||
|
||||
public Activity() {
|
||||
|
||||
}
|
||||
|
||||
public Activity(ActivityType activityType, WebUser activityOwner, Double numberOfUnits, String description, LocalDate date) {
|
||||
this.activityType = activityType;
|
||||
this.activityOwner = activityOwner;
|
||||
this.numberOfUnits = numberOfUnits;
|
||||
this.description = description;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public ActivityType getActivityType() {
|
||||
return activityType;
|
||||
}
|
||||
|
||||
public void setActivityType(ActivityType activityType) {
|
||||
this.activityType = activityType;
|
||||
}
|
||||
|
||||
public Double getNumberOfUnits() {
|
||||
return numberOfUnits;
|
||||
}
|
||||
|
||||
public void setNumberOfUnits(Double numberOfUnits) {
|
||||
this.numberOfUnits = numberOfUnits;
|
||||
}
|
||||
|
||||
public WebUser getActivityOwner() {
|
||||
return activityOwner;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public LocalDate getDate() {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package no.asprusten.sykkelaksjon.db.datatypes;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(uniqueConstraints = {
|
||||
@UniqueConstraint(name = "UniqueNamesPerUser", columnNames = { "owner", "name" })
|
||||
})
|
||||
public class ActivityTemplate {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private long id;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "owner", nullable = false)
|
||||
private WebUser owner;
|
||||
@ManyToOne(optional = false)
|
||||
private ActivityType activityType;
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
@Column(nullable = false)
|
||||
private Double numberOfUnits;
|
||||
|
||||
public ActivityTemplate() {
|
||||
|
||||
}
|
||||
|
||||
public ActivityTemplate(WebUser owner, ActivityType activityType, String name, Double numberOfUnits) {
|
||||
this.owner = owner;
|
||||
this.activityType = activityType;
|
||||
this.name = name;
|
||||
this.numberOfUnits = numberOfUnits;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public WebUser getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(WebUser owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public ActivityType getActivityType() {
|
||||
return activityType;
|
||||
}
|
||||
|
||||
public void setActivityType(ActivityType activityType) {
|
||||
this.activityType = activityType;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Double getNumberOfUnits() {
|
||||
return numberOfUnits;
|
||||
}
|
||||
|
||||
public void setNumberOfUnits(Double numberOfUnits) {
|
||||
this.numberOfUnits = numberOfUnits;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package no.asprusten.sykkelaksjon.db.datatypes;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class ActivityType {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private long id;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
private String activityType;
|
||||
@Column(nullable = false)
|
||||
private String unit;
|
||||
@Column(nullable = false)
|
||||
private Double conversion;
|
||||
|
||||
public ActivityType() {
|
||||
|
||||
}
|
||||
|
||||
public ActivityType(String activityType, String unit, Double conversion) {
|
||||
this.activityType = activityType;
|
||||
this.unit = unit;
|
||||
this.conversion = conversion;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getActivityType() {
|
||||
return activityType;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public Double getConversion() {
|
||||
return conversion;
|
||||
}
|
||||
|
||||
public void setActivityType(String activityType) {
|
||||
this.activityType = activityType;
|
||||
}
|
||||
|
||||
public void setUnit(String unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public void setConversion(Double conversion) {
|
||||
this.conversion = conversion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package no.asprusten.sykkelaksjon.db.datatypes;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(uniqueConstraints = {
|
||||
@UniqueConstraint(name = "OnlyOneActiveWithUsername", columnNames = { "username", "zeroIfActive" })
|
||||
})
|
||||
public class WebUser {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
@Column(nullable = false)
|
||||
private String username;
|
||||
@Column(nullable = false)
|
||||
private String fullName;
|
||||
private boolean isAdmin;
|
||||
@Column(nullable = false)
|
||||
private Long zeroIfActive;
|
||||
|
||||
@OneToMany(mappedBy = "activityOwner", fetch = FetchType.EAGER)
|
||||
private List<Activity> activities = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
|
||||
private List<ActivityTemplate> templates = new ArrayList<>();
|
||||
|
||||
public WebUser() {
|
||||
|
||||
}
|
||||
|
||||
public WebUser(String username, String fullName, boolean isAdmin) {
|
||||
this.username = username;
|
||||
this.fullName = fullName;
|
||||
this.isAdmin = isAdmin;
|
||||
this.zeroIfActive = 0L;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public boolean isAdmin() {
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return zeroIfActive == 0L;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void setFullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
public void setAdmin(boolean admin) {
|
||||
isAdmin = admin;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
if (active) {
|
||||
zeroIfActive = 0L;
|
||||
} else {
|
||||
zeroIfActive = id;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Activity> getActivities() {
|
||||
return activities;
|
||||
}
|
||||
|
||||
public List<ActivityTemplate> getTemplates() {
|
||||
return templates;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package no.asprusten.sykkelaksjon.db.repositories;
|
||||
|
||||
import no.asprusten.sykkelaksjon.db.datatypes.Activity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface ActivityRepository extends JpaRepository<Activity, Long> {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package no.asprusten.sykkelaksjon.db.repositories;
|
||||
|
||||
import no.asprusten.sykkelaksjon.db.datatypes.ActivityTemplate;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface ActivityTemplateRepository extends JpaRepository<ActivityTemplate, Long> {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package no.asprusten.sykkelaksjon.db.repositories;
|
||||
|
||||
import no.asprusten.sykkelaksjon.db.datatypes.ActivityType;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface ActivityTypeRepository extends JpaRepository<ActivityType, Long> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package no.asprusten.sykkelaksjon.db.repositories;
|
||||
|
||||
import no.asprusten.sykkelaksjon.db.datatypes.WebUser;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface WebUserRepository extends JpaRepository<WebUser, Long> {
|
||||
List<WebUser> findByUsername(String username);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package no.asprusten.sykkelaksjon.db.services;
|
||||
|
||||
import no.asprusten.sykkelaksjon.db.datatypes.Activity;
|
||||
import no.asprusten.sykkelaksjon.db.repositories.ActivityRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class ActivityService {
|
||||
@Autowired
|
||||
ActivityRepository activityRepository;
|
||||
|
||||
public void saveActivity(Activity activity) {
|
||||
activityRepository.save(activity);
|
||||
}
|
||||
|
||||
public void deleteActivity(Long id) {
|
||||
activityRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public Optional<Activity> findById(long id) {
|
||||
return activityRepository.findById(id);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package no.asprusten.sykkelaksjon.db.services;
|
||||
|
||||
import no.asprusten.sykkelaksjon.db.datatypes.ActivityTemplate;
|
||||
import no.asprusten.sykkelaksjon.db.repositories.ActivityTemplateRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class ActivityTemplateService {
|
||||
@Autowired
|
||||
private ActivityTemplateRepository activityTemplateRepository;
|
||||
|
||||
public Optional<ActivityTemplate> findById(Long id) {
|
||||
return activityTemplateRepository.findById(id);
|
||||
}
|
||||
|
||||
public void saveActivityTemplate(ActivityTemplate template) {
|
||||
activityTemplateRepository.save(template);
|
||||
}
|
||||
|
||||
public void deleteActivityTemplate(Long id) {
|
||||
activityTemplateRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package no.asprusten.sykkelaksjon.db.services;
|
||||
|
||||
import no.asprusten.sykkelaksjon.db.datatypes.ActivityType;
|
||||
import no.asprusten.sykkelaksjon.db.repositories.ActivityTypeRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class ActivityTypeService {
|
||||
@Autowired
|
||||
private ActivityTypeRepository activityTypeRepository;
|
||||
|
||||
public List<ActivityType> list() {
|
||||
return activityTypeRepository.findAll();
|
||||
}
|
||||
|
||||
public Optional<ActivityType> getById(Long id) {
|
||||
return activityTypeRepository.findById(id);
|
||||
}
|
||||
|
||||
public ActivityType saveActivityType(ActivityType activityType) {
|
||||
return activityTypeRepository.save(activityType);
|
||||
}
|
||||
|
||||
public void deleteActivityType(Long id) {
|
||||
activityTypeRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package no.asprusten.sykkelaksjon.db.services;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import no.asprusten.sykkelaksjon.db.datatypes.WebUser;
|
||||
import no.asprusten.sykkelaksjon.db.repositories.WebUserRepository;
|
||||
import org.apache.catalina.User;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
@Autowired
|
||||
private WebUserRepository webUserRepository;
|
||||
@Autowired
|
||||
@Value("${sykkelaksjon.initial-admin}")
|
||||
private String initialAdmin;
|
||||
|
||||
public List<WebUser> list() {
|
||||
return webUserRepository.findAll();
|
||||
}
|
||||
|
||||
public long getUserCount() {
|
||||
return webUserRepository.count();
|
||||
}
|
||||
|
||||
public Optional<WebUser> getUser(String username) {
|
||||
List<WebUser> users = webUserRepository.findByUsername(username);
|
||||
for (var user : users) {
|
||||
if (user.isActive()) {
|
||||
return Optional.of(user);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<WebUser> getUserById(Long id) {
|
||||
return webUserRepository.findById(id);
|
||||
}
|
||||
|
||||
public WebUser createUser(String username, String fullname) {
|
||||
boolean isAdmin = username.equals(initialAdmin);
|
||||
WebUser newUser = new WebUser(username, fullname, isAdmin);
|
||||
newUser = webUserRepository.save(newUser);
|
||||
return newUser;
|
||||
}
|
||||
|
||||
public WebUser saveUser(WebUser user) {
|
||||
return webUserRepository.save(user);
|
||||
}
|
||||
|
||||
public void deleteUserById(Long id) {
|
||||
webUserRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package no.asprusten.sykkelaksjon.security;
|
||||
|
||||
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
|
||||
import org.pac4j.core.authorization.authorizer.DefaultAuthorizers;
|
||||
import org.pac4j.core.config.Config;
|
||||
import org.pac4j.http.client.direct.HeaderClient;
|
||||
import org.pac4j.oidc.client.OidcClient;
|
||||
import org.pac4j.oidc.config.OidcConfiguration;
|
||||
import org.pac4j.springframework.config.Pac4jSecurityConfig;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig extends Pac4jSecurityConfig {
|
||||
|
||||
@Value("${sykkelaksjon.openid.discoveryURI}")
|
||||
private String discoveryURI;
|
||||
|
||||
@Value("${sykkelaksjon.openid.clientId}")
|
||||
private String clientId;
|
||||
|
||||
@Bean
|
||||
public Config config() {
|
||||
final var config = new OidcConfiguration()
|
||||
.setDiscoveryURI(discoveryURI)
|
||||
.setClientId(clientId);
|
||||
|
||||
OidcClient client = new OidcClient(config);
|
||||
client.setCallbackUrl("notused");
|
||||
client.init();
|
||||
|
||||
HeaderClient headerClient = new HeaderClient("Authorization", "Bearer ", client.getProfileCreator());
|
||||
return new Config(headerClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(final InterceptorRegistry registry) {
|
||||
addSecurity(registry, "HeaderClient").addPathPatterns("/api/**").excludePathPatterns("/api/openid").excludeHttpMethods(List.of(HttpMethod.OPTIONS));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package no.asprusten.sykkelaksjon.security;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
@ControllerAdvice
|
||||
public class ServerExceptionHandler {
|
||||
public static class InvalidUserException extends Exception {}
|
||||
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
@ExceptionHandler(InvalidUserException.class)
|
||||
public void handleInvalidUser() {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:9876/sykkelaksjon}
|
||||
spring.config.import=optional:configtree:/run/secrets/
|
||||
spring.datasource.username=sykkelaksjon
|
||||
spring.datasource.password=${postgrespassword}
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
|
||||
sykkelaksjon.initial-admin=${SYKKELAKSJON_INITIAL_ADMIN:martin}
|
||||
spring.web.resources.static-locations=./static
|
||||
|
||||
sykkelaksjon.openid.discoveryURI = ${OPENID_DISCOVERY_URI}
|
||||
sykkelaksjon.openid.clientId = ${OPENID_CLIENT_ID}
|
||||
Reference in New Issue
Block a user