Skip to content

Commit

Permalink
feat: Introduce UserSettings (#173)
Browse files Browse the repository at this point in the history
* feat: Introduce `UserSettings`

Signed-off-by: jaivalis <[email protected]>

* test: added some UTs

Signed-off-by: jaivalis <[email protected]>

* test: added UT

Signed-off-by: jaivalis <[email protected]>

* fix: smells

Signed-off-by: jaivalis <[email protected]>

* test: added moderately useful UT

Signed-off-by: jaivalis <[email protected]>

---------

Signed-off-by: jaivalis <[email protected]>
  • Loading branch information
jaivalis authored Oct 16, 2024
1 parent 9356837 commit 891118b
Show file tree
Hide file tree
Showing 23 changed files with 778 additions and 30 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## ⚙️ All important changes to this project are tracked here:

## [Unreleased](https://github.com/jaivalis/release-raccoon/compare/0.3.12...jdevelop)
## [0.4.0](https://github.com/jaivalis/release-raccoon/compare/0.3.14...0.4.0) - 15/10/2024

- Feat: Introduce `UserSettings`

## [0.3.14](https://github.com/jaivalis/release-raccoon/compare/0.3.13...0.3.14) - 05/10/2024

Expand Down
11 changes: 9 additions & 2 deletions docker/db_init/postgres/postgres.sql
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ create table Artist (
create_date timestamp(6),
name varchar(300) unique,
lastfmUri varchar(255),
musicbrainzId varchar(255) unique,
spotifyUri varchar(255) unique,
musicbrainzId varchar(255),
spotifyUri varchar(255),
primary key (artistId)
);

Expand Down Expand Up @@ -87,6 +87,13 @@ create table UserArtist (
primary key (artist_id, user_id)
);

create table UserSettings (
user_id bigint not null,
emailDisabled boolean,
id bigint generated by default as identity,
primary key (id)
);

create index ArtistSpotifyUri_idx
on Artist (spotifyUri);

Expand Down
4 changes: 2 additions & 2 deletions parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@

<!-- plugins -->
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
<surefire-plugin.version>3.5.0</surefire-plugin.version>
<failsafe-plugin.version>3.5.0</failsafe-plugin.version>
<surefire-plugin.version>3.5.1</surefire-plugin.version>
<failsafe-plugin.version>3.5.1</failsafe-plugin.version>
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
<jacoco-maven-plugin.version>0.8.12</jacoco-maven-plugin.version>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.raccoon.entity;

import java.time.LocalDate;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;

import static java.util.Objects.isNull;

@Data
@NoArgsConstructor
@Entity
@Table(name = "UserSettings")
public class UserSettings {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userSettingId;

@OneToOne
@JoinColumn(name = "user_id", nullable = false)
private RaccoonUser user;

@Column(nullable = false)
private Integer notifyIntervalDays = 1;

private Boolean unsubscribed = Boolean.FALSE;

public void updateFrom(UserSettings other) {
this.notifyIntervalDays = other.getNotifyIntervalDays();
this.unsubscribed = other.getUnsubscribed();
}

public boolean shouldNotify(LocalDate lastNotified) {
if (unsubscribed) {
return false;
}
if (isNull(lastNotified)) {
return true;
}
return LocalDate.now().isAfter(lastNotified.plusDays(notifyIntervalDays));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.raccoon.entity.repository;

import com.raccoon.entity.UserSettings;

import java.util.Optional;

import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class UserSettingsRepository implements PanacheRepository<UserSettings> {

public Optional<UserSettings> findByUserId(Long userId) {
return find("user.id", userId).stream().findFirst();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.raccoon.entity;

import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;

import static org.assertj.core.api.Assertions.assertThat;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class UserSettingsTest {

@Test
void updateFrom_should_updateNotifyIntervalDays() {
UserSettings userSettings = new UserSettings();
userSettings.setNotifyIntervalDays(5);
UserSettings other = new UserSettings();
other.setNotifyIntervalDays(10);
other.setUnsubscribed(true);

userSettings.updateFrom(other);

assertThat(userSettings.getNotifyIntervalDays()).isEqualTo(10);
assertThat(userSettings.getUnsubscribed()).isTrue();
}

@Test
void shouldNotify_should_returnTrue_when_lastNotifiedNull() {
UserSettings userSettings = new UserSettings();
userSettings.setNotifyIntervalDays(1);
userSettings.setUnsubscribed(false);

assertThat(userSettings.shouldNotify(null)).isTrue();
}

@Test
void shouldNotify_should_returnTrue_when_withinInterval() {
UserSettings userSettings = new UserSettings();
userSettings.setNotifyIntervalDays(1);
userSettings.setUnsubscribed(false);
LocalDate lastNotified = LocalDate.now().minusDays(2);

assertThat(userSettings.shouldNotify(lastNotified)).isTrue();
}

@Test
void shouldNotify_should_returnTrue_when_outsideInterval() {
UserSettings userSettings = new UserSettings();
userSettings.setNotifyIntervalDays(1);
userSettings.setUnsubscribed(false);
LocalDate lastNotified = LocalDate.now();

assertThat(userSettings.shouldNotify(lastNotified))
.isFalse();
}

@Test
void shouldNotify_should_returnTrue_when_outsideInterval2() {
UserSettings userSettings = new UserSettings();
userSettings.setNotifyIntervalDays(3);
userSettings.setUnsubscribed(false);
LocalDate lastNotified = LocalDate.now().minusDays(1);

boolean result = userSettings.shouldNotify(lastNotified);

assertThat(result).isFalse();
}

@Test
void shouldNotify_should_returnFalse_when_unsubscribed() {
UserSettings userSettings = new UserSettings();
userSettings.setNotifyIntervalDays(1);
userSettings.setUnsubscribed(true);
LocalDate lastNotified = LocalDate.now().minusDays(1);

boolean result = userSettings.shouldNotify(lastNotified);

assertThat(result).isFalse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.raccoon.entity.repository;

import com.raccoon.entity.RaccoonUser;
import com.raccoon.entity.UserSettings;
import com.raccoon.entity.factory.UserFactory;

import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Optional;

import io.quarkus.test.TestTransaction;
import io.quarkus.test.common.WithTestResource;
import io.quarkus.test.h2.H2DatabaseTestResource;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;

import static org.assertj.core.api.Assertions.assertThat;

@QuarkusTest
@WithTestResource(H2DatabaseTestResource.class)
@TestTransaction
class UserSettingsRepositoryTest {

@Inject
UserRepository userRepository;
@Inject
UserSettingsRepository userSettingsRepository;
@Inject
UserFactory factory;

@Test
void findByUserId_should_returnUserSettings_when_userExists() {
RaccoonUser user = factory.createUser("[email protected]");
UserSettings settings = new UserSettings();
settings.setUser(user);
userSettingsRepository.persist(settings);

Optional<UserSettings> result = userSettingsRepository.findByUserId(user.id);

assertThat(result).isPresent();
assertThat(result.get().getUser())
.isEqualTo(user);
}

@Test
void findByUserId_should_returnEmpty_when_userDoesNotExist() {
RaccoonUser user = factory.createUser("[email protected]");
userRepository.persist(List.of(user));
UserSettings settings = new UserSettings();
settings.setUser(user);
userSettingsRepository.persist(settings);

Optional<UserSettings> result = userSettingsRepository.findByUserId(user.id + 1);

assertThat(result).isNotPresent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
import com.raccoon.entity.RaccoonUser;
import com.raccoon.entity.Release;
import com.raccoon.entity.UserArtist;
import com.raccoon.entity.UserSettings;
import com.raccoon.entity.repository.ReleaseRepository;
import com.raccoon.entity.repository.UserArtistRepository;
import com.raccoon.entity.repository.UserSettingsRepository;
import com.raccoon.mail.RaccoonMailer;

import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import io.quarkus.qute.TemplateException;
Expand All @@ -26,16 +30,19 @@
@ApplicationScoped
public class NotifyService {

RaccoonMailer raccoonMailer;
ReleaseRepository releaseRepository;
UserArtistRepository userArtistRepository;
private final RaccoonMailer raccoonMailer;
private final ReleaseRepository releaseRepository;
private final UserArtistRepository userArtistRepository;
private final UserSettingsRepository userSettingsRepository;

@Inject
public NotifyService(final ReleaseRepository releaseRepository,
final UserArtistRepository userArtistRepository,
final UserSettingsRepository userSettingsRepository,
final RaccoonMailer raccoonMailer) {
this.userArtistRepository = userArtistRepository;
this.releaseRepository = releaseRepository;
this.userSettingsRepository = userSettingsRepository;
this.raccoonMailer = raccoonMailer;
}

Expand All @@ -55,9 +62,14 @@ public Uni<Boolean> notifyUsers() {
.map(entry -> {
var user = entry.getKey();
var userArtistList = entry.getValue();
return notifyUser(user, getLatestReleases(userArtistList), userArtistList);
})
.toList();

if (shouldNotify(user)) {
return notifyUser(user, getLatestReleases(userArtistList), userArtistList);
} else {
log.info("Skipping over user {} because of user settings", user.id);
return Uni.createFrom().voidItem();
}
}).toList();

if (unis.isEmpty()) {
log.info("Nobody to notify");
Expand All @@ -71,6 +83,16 @@ public Uni<Boolean> notifyUsers() {
.recoverWithUni(failure -> Uni.createFrom().item(false));
}

private boolean shouldNotify(RaccoonUser user) {
Optional<UserSettings> settings = userSettingsRepository.findByUserId(user.id);
if (settings.isPresent()) {
LocalDate lastNotified = user.getLastNotified();
return settings.get().shouldNotify(lastNotified);
} else {
return true;
}
}

/**
* Can be broken into `boolean canNotifyUser` & `Uni<Void> notifyUser`
* @param raccoonUser the raccoonUser to notify
Expand Down Expand Up @@ -137,13 +159,14 @@ private Uni<Void> notifyUser(final RaccoonUser raccoonUser,
*/
void mailSuccessCallback(RaccoonUser raccoonUser, Collection<UserArtist> userArtistList) {
log.info("Notified raccoonUser {}", raccoonUser.getId());
raccoonUser.setLastNotified(LocalDate.now());
userArtistList.forEach(userArtist -> userArtist.setHasNewRelease(false));

userArtistRepository.persist(userArtistList);
}

void mailFailureCallback(RaccoonUser raccoonUser) {
log.warn("Failed to notify raccoonUser {}", raccoonUser.id);
log.warn("Failed to deliver mail to raccoonUser {}", raccoonUser.id);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public QuteTemplateLoader(Engine engine) {
final String profileContents =
new Scanner(requireNonNull(this.getClass().getResourceAsStream("/templates/profile.html")), UTF_8)
.useDelimiter(EOF_DELIMITER).next();
final String userSettingsContents =
new Scanner(requireNonNull(this.getClass().getResourceAsStream("/templates/profile-settings.html")), UTF_8)
.useDelimiter(EOF_DELIMITER).next();
final String digestEmailContents =
new Scanner(requireNonNull(this.getClass().getResourceAsStream("/templates/mail-digest.html")), UTF_8)
.useDelimiter(EOF_DELIMITER).next();
Expand All @@ -46,13 +49,15 @@ public QuteTemplateLoader(Engine engine) {
public static final String DIGEST_EMAIL_TEMPLATE_ID = "digest";
public static final String INDEX_TEMPLATE_ID = "index";
public static final String PROFILE_TEMPLATE_ID = "profile";
public static final String USER_SETTINGS_TEMPLATE_ID = "profile-settings";
public static final String WELCOME_EMAIL_TEMPLATE_ID = "welcome";

@Startup
void onStart() {
engine.putTemplate(DIGEST_EMAIL_TEMPLATE_ID, engine.parse(digestEmailContents));
engine.putTemplate(INDEX_TEMPLATE_ID, engine.parse(indexContents));
engine.putTemplate(PROFILE_TEMPLATE_ID, engine.parse(profileContents));
engine.putTemplate(USER_SETTINGS_TEMPLATE_ID, engine.parse(userSettingsContents));
engine.putTemplate(WELCOME_EMAIL_TEMPLATE_ID, engine.parse(welcomeEmailContents));
}

Expand Down
Loading

0 comments on commit 891118b

Please sign in to comment.