Added improved sample data generation.
This commit is contained in:
parent
54f17d4cec
commit
184491b9ea
|
@ -0,0 +1,27 @@
|
|||
package nl.andrewlalis.gymboard_api.util;
|
||||
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public final class CsvUtil {
|
||||
private CsvUtil() {}
|
||||
|
||||
public static void load(Path csvFile, ThrowableConsumer<CSVRecord> recordConsumer) throws IOException {
|
||||
var reader = new FileReader(csvFile.toFile());
|
||||
CSVFormat format = CSVFormat.DEFAULT.builder()
|
||||
.setHeader()
|
||||
.setSkipHeaderRecord(true)
|
||||
.build();
|
||||
for (var record : format.parse(reader)) {
|
||||
try {
|
||||
recordConsumer.accept(record);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
package nl.andrewlalis.gymboard_api.util;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dao.CityRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dao.CountryRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dao.GymRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dto.CompoundGymId;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionPayload;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.model.*;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.Exercise;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.CdnClient;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.service.submission.ExerciseSubmissionService;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.RoleRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserPersonalDetailsRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserPreferencesRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dto.UserCreationPayload;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.Role;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.UserPersonalDetails;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.service.UserService;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVRecord;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Simple component that loads sample data that's useful when testing the application.
|
||||
*/
|
||||
@Component
|
||||
public class SampleDataLoader implements ApplicationListener<ContextRefreshedEvent> {
|
||||
private static final Logger log = LoggerFactory.getLogger(SampleDataLoader.class);
|
||||
private final CountryRepository countryRepository;
|
||||
private final CityRepository cityRepository;
|
||||
private final GymRepository gymRepository;
|
||||
private final ExerciseRepository exerciseRepository;
|
||||
private final ExerciseSubmissionService submissionService;
|
||||
private final RoleRepository roleRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final UserPersonalDetailsRepository personalDetailsRepository;
|
||||
private final UserPreferencesRepository preferencesRepository;
|
||||
private final UserService userService;
|
||||
|
||||
@Value("${app.cdn-origin}")
|
||||
private String cdnOrigin;
|
||||
|
||||
public SampleDataLoader(
|
||||
CountryRepository countryRepository,
|
||||
CityRepository cityRepository,
|
||||
GymRepository gymRepository,
|
||||
ExerciseRepository exerciseRepository,
|
||||
ExerciseSubmissionService submissionService,
|
||||
RoleRepository roleRepository, UserRepository userRepository, UserPersonalDetailsRepository personalDetailsRepository, UserPreferencesRepository preferencesRepository, UserService userService) {
|
||||
this.countryRepository = countryRepository;
|
||||
this.cityRepository = cityRepository;
|
||||
this.gymRepository = gymRepository;
|
||||
this.exerciseRepository = exerciseRepository;
|
||||
this.submissionService = submissionService;
|
||||
this.roleRepository = roleRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.personalDetailsRepository = personalDetailsRepository;
|
||||
this.preferencesRepository = preferencesRepository;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
Path markerFile = Path.of(".sample_data");
|
||||
if (Files.exists(markerFile)) return;
|
||||
|
||||
log.info("Generating sample data.");
|
||||
try {
|
||||
generateSampleData();
|
||||
secondPassGenerateSampleData();
|
||||
Files.writeString(markerFile, "Yes");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
protected void generateSampleData() throws Exception {
|
||||
loadCsv("exercises", record -> {
|
||||
exerciseRepository.save(new Exercise(record.get("short-name"), record.get("name")));
|
||||
});
|
||||
loadCsv("countries", record -> {
|
||||
countryRepository.save(new Country(record.get("code"), record.get("name")));
|
||||
});
|
||||
loadCsv("cities", record -> {
|
||||
var country = countryRepository.findById(record.get("country-code")).orElseThrow();
|
||||
String shortName = record.get("short-name");
|
||||
String name = record.get("name");
|
||||
cityRepository.save(new City(shortName, name, country));
|
||||
});
|
||||
loadCsv("gyms", record -> {
|
||||
var city = cityRepository.findByShortNameAndCountryCode(
|
||||
record.get("city-short-name"),
|
||||
record.get("country-code")
|
||||
).orElseThrow();
|
||||
gymRepository.save(new Gym(
|
||||
city,
|
||||
record.get("short-name"),
|
||||
record.get("name"),
|
||||
record.get("website-url"),
|
||||
new GeoPoint(
|
||||
new BigDecimal(record.get("latitude")),
|
||||
new BigDecimal(record.get("longitude"))
|
||||
),
|
||||
record.get("street-address")
|
||||
));
|
||||
});
|
||||
|
||||
// Loading sample submissions involves sending content to the Gymboard CDN service.
|
||||
// We upload a video for each submission, and wait until all uploads are processed before continuing.
|
||||
|
||||
final CdnClient cdnClient = new CdnClient(cdnOrigin);
|
||||
|
||||
loadCsv("submissions", record -> {
|
||||
var exercise = exerciseRepository.findById(record.get("exercise-short-name")).orElseThrow();
|
||||
BigDecimal weight = new BigDecimal(record.get("raw-weight"));
|
||||
WeightUnit unit = WeightUnit.parse(record.get("weight-unit"));
|
||||
int reps = Integer.parseInt(record.get("reps"));
|
||||
String name = record.get("submitter-name");
|
||||
CompoundGymId gymId = CompoundGymId.parse(record.get("gym-id"));
|
||||
String videoFilename = record.get("video-filename");
|
||||
|
||||
log.info("Uploading video {} to CDN...", videoFilename);
|
||||
var video = cdnClient.uploads.uploadVideo(Path.of("sample_data", videoFilename), "video/mp4");
|
||||
submissionService.createSubmission(gymId, new ExerciseSubmissionPayload(
|
||||
name,
|
||||
exercise.getShortName(),
|
||||
weight.floatValue(),
|
||||
unit.name(),
|
||||
reps,
|
||||
video.id()
|
||||
));
|
||||
});
|
||||
|
||||
loadCsv("users", record -> {
|
||||
String email = record.get("email");
|
||||
String password = record.get("password");
|
||||
String name = record.get("name");
|
||||
String[] roleNames = record.get("roles").split("\\s*\\n\\s*");
|
||||
LocalDate birthDate = LocalDate.parse(record.get("birth-date"));
|
||||
BigDecimal currentWeight = new BigDecimal(record.get("current-weight"));
|
||||
WeightUnit currentWeightUnit = WeightUnit.parse(record.get("current-weight-unit"));
|
||||
BigDecimal metricWeight = new BigDecimal(currentWeight.toString());
|
||||
if (currentWeightUnit == WeightUnit.POUNDS) {
|
||||
metricWeight = WeightUnit.toKilograms(metricWeight);
|
||||
}
|
||||
UserPersonalDetails.PersonSex sex = UserPersonalDetails.PersonSex.parse(record.get("sex"));
|
||||
|
||||
UserCreationPayload payload = new UserCreationPayload(email, password, name);
|
||||
var resp = userService.createUser(payload, false);
|
||||
User user = userRepository.findByIdWithRoles(resp.id()).orElseThrow();
|
||||
for (var roleName : roleNames) {
|
||||
if (roleName.isBlank()) continue;
|
||||
Role role = roleRepository.findById(roleName.strip().toLowerCase())
|
||||
.orElseGet(() -> roleRepository.save(new Role(roleName.strip().toLowerCase())));
|
||||
user.getRoles().add(role);
|
||||
}
|
||||
userRepository.save(user);
|
||||
var pd = personalDetailsRepository.findById(user.getId()).orElseThrow();
|
||||
pd.setBirthDate(birthDate);
|
||||
pd.setCurrentWeight(currentWeight);
|
||||
pd.setCurrentWeightUnit(currentWeightUnit);
|
||||
pd.setCurrentMetricWeight(metricWeight);
|
||||
pd.setSex(sex);
|
||||
personalDetailsRepository.save(pd);
|
||||
var p = preferencesRepository.findById(user.getId()).orElseThrow();
|
||||
p.setLocale(record.get("locale"));
|
||||
p.setAccountPrivate(Boolean.parseBoolean(record.get("account-private")));
|
||||
preferencesRepository.save(p);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
protected void secondPassGenerateSampleData() throws Exception {
|
||||
loadCsv("users", record -> {
|
||||
String email = record.get("email");
|
||||
String[] followingEmails = record.get("following").split("\\s*\\n\\s*");
|
||||
User user = userRepository.findByEmail(email).orElseThrow();
|
||||
for (String followingEmail : followingEmails) {
|
||||
User userToFollow = userRepository.findByEmail(followingEmail).orElseThrow();
|
||||
userService.followUser(user.getId(), userToFollow.getId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ThrowableConsumer<T> {
|
||||
void accept(T item) throws Exception;
|
||||
}
|
||||
|
||||
private void loadCsv(String csvName, ThrowableConsumer<CSVRecord> recordConsumer) throws IOException {
|
||||
String path = "sample_data/" + csvName + ".csv";
|
||||
log.info("Loading data from {}...", path);
|
||||
var reader = new FileReader(path);
|
||||
CSVFormat format = CSVFormat.DEFAULT.builder()
|
||||
.setHeader()
|
||||
.setSkipHeaderRecord(true)
|
||||
.build();
|
||||
for (var record : format.parse(reader)) {
|
||||
try {
|
||||
recordConsumer.accept(record);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package nl.andrewlalis.gymboard_api.util;
|
||||
|
||||
public interface ThrowableConsumer<T> {
|
||||
void accept(T item) throws Exception;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package nl.andrewlalis.gymboard_api.util.sample_data;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Interface that defines a component that can generate sample data for testing
|
||||
* the application. It must define a method to generate data, and optionally, it
|
||||
* can specify a collection of <em>other</em> sample data generator classes that
|
||||
* it depends on. For example, a gym generator might depend on a generator that
|
||||
* creates countries and cities.
|
||||
* <p>
|
||||
* Note that all classes which implement this interface should be annotated
|
||||
* as a <code>@Component</code> and <code>@Profile("development")</code> to
|
||||
* ensure that we keep sample data generation away from production.
|
||||
* </p>
|
||||
*/
|
||||
public interface SampleDataGenerator {
|
||||
void generate() throws Exception;
|
||||
|
||||
default Collection<Class<? extends SampleDataGenerator>> dependencies() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package nl.andrewlalis.gymboard_api.util.sample_data;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Simple component that loads sample data that's useful when testing the application.
|
||||
*/
|
||||
@Component
|
||||
@Profile("development")
|
||||
public class SampleDataLoader implements ApplicationListener<ContextRefreshedEvent> {
|
||||
private static final Logger log = LoggerFactory.getLogger(SampleDataLoader.class);
|
||||
|
||||
/**
|
||||
* The list of all sample data generators that the application has loaded.
|
||||
*/
|
||||
private final List<SampleDataGenerator> generators;
|
||||
|
||||
public SampleDataLoader(List<SampleDataGenerator> generators) {
|
||||
this.generators = generators;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
Path markerFile = Path.of(".sample_data");
|
||||
if (Files.exists(markerFile)) return;
|
||||
log.info("Generating sample data.");
|
||||
try {
|
||||
Set<SampleDataGenerator> completedGenerators = new HashSet<>();
|
||||
for (var gen : generators) {
|
||||
runGenerator(gen, completedGenerators);
|
||||
}
|
||||
Files.writeString(markerFile, "Yes");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void runGenerator(SampleDataGenerator gen, Set<SampleDataGenerator> completed) {
|
||||
for (var dep : gen.dependencies()) {
|
||||
runGenerator(getGeneratorByClass(dep), completed);
|
||||
}
|
||||
if (!completed.contains(gen)) {
|
||||
try {
|
||||
log.info("Running sample data generator: {}", gen.getClass().getSimpleName());
|
||||
gen.generate();
|
||||
completed.add(gen);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Generator failed: " + gen.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SampleDataGenerator getGeneratorByClass(Class<? extends SampleDataGenerator> c) {
|
||||
for (var gen : generators) {
|
||||
if (gen.getClass().equals(c)) return gen;
|
||||
}
|
||||
throw new RuntimeException("Missing generator: " + c.getSimpleName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package nl.andrewlalis.gymboard_api.util.sample_data;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.model.exercise.Exercise;
|
||||
import nl.andrewlalis.gymboard_api.util.CsvUtil;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Component
|
||||
@Profile("development")
|
||||
public class SampleExerciseGenerator implements SampleDataGenerator {
|
||||
private final ExerciseRepository exerciseRepository;
|
||||
|
||||
public SampleExerciseGenerator(ExerciseRepository exerciseRepository) {
|
||||
this.exerciseRepository = exerciseRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate() throws Exception {
|
||||
CsvUtil.load(Path.of("sample_data", "exercises.csv"), r -> {
|
||||
exerciseRepository.save(new Exercise(r.get("short-name"), r.get("name")));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package nl.andrewlalis.gymboard_api.util.sample_data;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dao.CityRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dao.CountryRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.model.City;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.model.Country;
|
||||
import nl.andrewlalis.gymboard_api.util.CsvUtil;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Component
|
||||
@Profile("development")
|
||||
public class SampleGeoDataGenerator implements SampleDataGenerator {
|
||||
private final CityRepository cityRepository;
|
||||
private final CountryRepository countryRepository;
|
||||
|
||||
public SampleGeoDataGenerator(CityRepository cityRepository, CountryRepository countryRepository) {
|
||||
this.cityRepository = cityRepository;
|
||||
this.countryRepository = countryRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate() throws Exception {
|
||||
CsvUtil.load(Path.of("sample_data", "countries.csv"), r -> {
|
||||
countryRepository.save(new Country(r.get("code"), r.get("name")));
|
||||
});
|
||||
CsvUtil.load(Path.of("sample_data", "cities.csv"), r -> {
|
||||
var country = countryRepository.findById(r.get("country-code")).orElseThrow();
|
||||
cityRepository.save(new City(r.get("short-name"), r.get("name"), country));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package nl.andrewlalis.gymboard_api.util.sample_data;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dao.CityRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dao.GymRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.model.GeoPoint;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.model.Gym;
|
||||
import nl.andrewlalis.gymboard_api.util.CsvUtil;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
@Profile("development")
|
||||
public class SampleGymGenerator implements SampleDataGenerator {
|
||||
private final CityRepository cityRepository;
|
||||
private final GymRepository gymRepository;
|
||||
|
||||
public SampleGymGenerator(CityRepository cityRepository, GymRepository gymRepository) {
|
||||
this.cityRepository = cityRepository;
|
||||
this.gymRepository = gymRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate() throws Exception {
|
||||
CsvUtil.load(Path.of("sample_data", "gyms.csv"), r -> {
|
||||
var city = cityRepository.findByShortNameAndCountryCode(
|
||||
r.get("city-short-name"),
|
||||
r.get("country-code")
|
||||
).orElseThrow();
|
||||
gymRepository.save(new Gym(
|
||||
city,
|
||||
r.get("short-name"),
|
||||
r.get("name"),
|
||||
r.get("website-url"),
|
||||
new GeoPoint(
|
||||
new BigDecimal(r.get("latitude")),
|
||||
new BigDecimal(r.get("longitude"))
|
||||
),
|
||||
r.get("street-address")
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends SampleDataGenerator>> dependencies() {
|
||||
return Set.of(SampleGeoDataGenerator.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package nl.andrewlalis.gymboard_api.util.sample_data;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dao.exercise.ExerciseRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dto.CompoundGymId;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.dto.ExerciseSubmissionPayload;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.service.cdn_client.CdnClient;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.service.submission.ExerciseSubmissionService;
|
||||
import nl.andrewlalis.gymboard_api.util.CsvUtil;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
@Profile("development")
|
||||
public class SampleSubmissionGenerator implements SampleDataGenerator {
|
||||
private final ExerciseRepository exerciseRepository;
|
||||
private final ExerciseSubmissionService submissionService;
|
||||
|
||||
@Value("${app.cdn-origin}")
|
||||
private String cdnOrigin;
|
||||
|
||||
public SampleSubmissionGenerator(ExerciseRepository exerciseRepository, ExerciseSubmissionService submissionService) {
|
||||
this.exerciseRepository = exerciseRepository;
|
||||
this.submissionService = submissionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate() throws Exception {
|
||||
final CdnClient cdnClient = new CdnClient(cdnOrigin);
|
||||
CsvUtil.load(Path.of("sample_data", "submissions.csv"), r -> {
|
||||
var exercise = exerciseRepository.findById(r.get("exercise-short-name")).orElseThrow();
|
||||
BigDecimal weight = new BigDecimal(r.get("raw-weight"));
|
||||
WeightUnit unit = WeightUnit.parse(r.get("weight-unit"));
|
||||
int reps = Integer.parseInt(r.get("reps"));
|
||||
String name = r.get("submitter-name");
|
||||
CompoundGymId gymId = CompoundGymId.parse(r.get("gym-id"));
|
||||
String videoFilename = r.get("video-filename");
|
||||
|
||||
var video = cdnClient.uploads.uploadVideo(Path.of("sample_data", videoFilename), "video/mp4");
|
||||
submissionService.createSubmission(gymId, new ExerciseSubmissionPayload(
|
||||
name,
|
||||
exercise.getShortName(),
|
||||
weight.floatValue(),
|
||||
unit.name(),
|
||||
reps,
|
||||
video.id()
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends SampleDataGenerator>> dependencies() {
|
||||
return Set.of(SampleExerciseGenerator.class, SampleUserGenerator.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package nl.andrewlalis.gymboard_api.util.sample_data;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.RoleRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserFollowingRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.Role;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.UserFollowing;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.UserPersonalDetails;
|
||||
import nl.andrewlalis.gymboard_api.util.CsvUtil;
|
||||
import nl.andrewlalis.gymboard_api.util.ULID;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Component
|
||||
@Profile("development")
|
||||
public class SampleUserGenerator implements SampleDataGenerator {
|
||||
private final ULID ulid;
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final RoleRepository roleRepository;
|
||||
private final UserFollowingRepository followingRepository;
|
||||
|
||||
public SampleUserGenerator(ULID ulid, UserRepository userRepository, PasswordEncoder passwordEncoder, RoleRepository roleRepository, UserFollowingRepository followingRepository) {
|
||||
this.ulid = ulid;
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.roleRepository = roleRepository;
|
||||
this.followingRepository = followingRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate() throws Exception {
|
||||
Path usersCsvPath = Path.of("sample_data", "users.csv");
|
||||
CsvUtil.load(usersCsvPath, r -> {
|
||||
User user = new User(
|
||||
ulid.nextULID(),
|
||||
true,
|
||||
r.get("email"),
|
||||
passwordEncoder.encode(r.get("password")),
|
||||
r.get("name")
|
||||
);
|
||||
String[] roleNames = r.get("roles").split("\\s*\\n\\s*");
|
||||
for (var roleName : roleNames) {
|
||||
if (roleName.isBlank()) continue;
|
||||
Role role = roleRepository.findById(roleName.strip().toLowerCase())
|
||||
.orElseGet(() -> roleRepository.save(new Role(roleName.strip().toLowerCase())));
|
||||
user.getRoles().add(role);
|
||||
}
|
||||
|
||||
// Set up the user's personal details.
|
||||
var pd = user.getPersonalDetails();
|
||||
String birthDateStr = r.get("birth-date");
|
||||
if (birthDateStr != null && !birthDateStr.isBlank()) {
|
||||
pd.setBirthDate(LocalDate.parse(birthDateStr));
|
||||
}
|
||||
String currentWeightStr = r.get("current-weight");
|
||||
String currentWeightUnitStr = r.get("current-weight-unit");
|
||||
if (
|
||||
currentWeightStr != null && !currentWeightStr.isBlank() &&
|
||||
currentWeightUnitStr != null && !currentWeightUnitStr.isBlank()
|
||||
) {
|
||||
BigDecimal currentWeight = new BigDecimal(currentWeightStr);
|
||||
WeightUnit currentWeightUnit = WeightUnit.parse(currentWeightUnitStr);
|
||||
BigDecimal metricWeight = new BigDecimal(currentWeightStr);
|
||||
if (currentWeightUnit == WeightUnit.POUNDS) {
|
||||
metricWeight = WeightUnit.toKilograms(metricWeight);
|
||||
}
|
||||
pd.setCurrentWeight(currentWeight);
|
||||
pd.setCurrentWeightUnit(currentWeightUnit);
|
||||
pd.setCurrentMetricWeight(metricWeight);
|
||||
}
|
||||
pd.setSex(UserPersonalDetails.PersonSex.parse(r.get("sex")));
|
||||
|
||||
// Set up the user's preferences.
|
||||
var p = user.getPreferences();
|
||||
p.setLocale(r.get("locale"));
|
||||
p.setAccountPrivate(Boolean.parseBoolean(r.get("account-private")));
|
||||
userRepository.save(user);
|
||||
});
|
||||
|
||||
// Do a second pass to add follower information.
|
||||
CsvUtil.load(usersCsvPath, r -> {
|
||||
User user = userRepository.findByEmail(r.get("email")).orElseThrow();
|
||||
String[] followingEmails = r.get("following").split("\\s+");
|
||||
for (String followingEmail : followingEmails) {
|
||||
User userToFollow = userRepository.findByEmail(followingEmail).orElseThrow();
|
||||
followingRepository.save(new UserFollowing(userToFollow, user));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -10,3 +10,8 @@ export interface GymSearchResult {
|
|||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
|
||||
export interface UserSearchResult {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
package nl.andrewlalis.gymboardcdn.service;
|
||||
|
||||
import nl.andrewlalis.gymboardcdn.model.StoredFile;
|
||||
import nl.andrewlalis.gymboardcdn.model.StoredFileRepository;
|
||||
import nl.andrewlalis.gymboardcdn.util.ULID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* The service that manages storing and retrieving files from a base filesystem.
|
||||
|
@ -27,9 +34,11 @@ public class FileService {
|
|||
@Value("${app.files.temp-dir}")
|
||||
private String tempDir;
|
||||
|
||||
private final StoredFileRepository storedFileRepository;
|
||||
private final ULID ulid;
|
||||
|
||||
public FileService(ULID ulid) {
|
||||
public FileService(StoredFileRepository storedFileRepository, ULID ulid) {
|
||||
this.storedFileRepository = storedFileRepository;
|
||||
this.ulid = ulid;
|
||||
}
|
||||
|
||||
|
@ -78,4 +87,30 @@ public class FileService {
|
|||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduled task that removes any StoredFile entities for which no more
|
||||
* physical file exists.
|
||||
*/
|
||||
@Scheduled(fixedDelay = 1, timeUnit = TimeUnit.MINUTES)
|
||||
@Transactional
|
||||
public void removeOrphanedFiles() {
|
||||
Pageable pageable = PageRequest.of(0, 100, Sort.by(Sort.Direction.DESC, "createdAt"));
|
||||
Page<StoredFile> page = storedFileRepository.findAll(pageable);
|
||||
while (!page.isEmpty()) {
|
||||
for (var storedFile : page) {
|
||||
try {
|
||||
Path filePath = getStoragePathForFile(storedFile);
|
||||
if (Files.notExists(filePath)) {
|
||||
log.warn("Removing stored file {} because it no longer exists on the disk.", storedFile.getId());
|
||||
storedFileRepository.delete(storedFile);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Couldn't get storage path for stored file.", e);
|
||||
}
|
||||
}
|
||||
pageable = pageable.next();
|
||||
page = storedFileRepository.findAll(pageable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue