Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce UserSettings #173

Merged
merged 5 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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