This commit is contained in:
Athou
2024-08-07 08:10:14 +02:00
parent 2f6ddf0e70
commit cc32f8ad16
164 changed files with 2011 additions and 3288 deletions

View File

@@ -21,12 +21,11 @@ import org.w3c.dom.css.CSSStyleDeclaration;
import com.steadystate.css.parser.CSSOMParser;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor
@Slf4j
@Singleton
public class FeedEntryContentCleaningService {

View File

@@ -12,11 +12,10 @@ import com.commafeed.backend.feed.parser.FeedParserResult.Enclosure;
import com.commafeed.backend.feed.parser.FeedParserResult.Media;
import com.commafeed.backend.model.FeedEntryContent;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor
@Singleton
public class FeedEntryContentService {

View File

@@ -25,11 +25,10 @@ import org.jsoup.Jsoup;
import com.commafeed.backend.model.FeedEntry;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor
@Singleton
public class FeedEntryFilteringService {

View File

@@ -18,13 +18,12 @@ import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User;
import com.commafeed.backend.service.FeedEntryFilteringService.FeedEntryFilterException;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor
@Singleton
public class FeedEntryService {

View File

@@ -10,11 +10,10 @@ import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryTag;
import com.commafeed.backend.model.User;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor
@Singleton
public class FeedEntryTagService {

View File

@@ -2,8 +2,8 @@ package com.commafeed.backend.service;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import com.commafeed.backend.Digests;
import com.commafeed.backend.dao.FeedDAO;
@@ -14,19 +14,18 @@ import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.Models;
import com.google.common.io.Resources;
import jakarta.inject.Inject;
import io.quarkus.arc.All;
import jakarta.inject.Singleton;
@Singleton
public class FeedService {
private final FeedDAO feedDAO;
private final Set<AbstractFaviconFetcher> faviconFetchers;
private final List<AbstractFaviconFetcher> faviconFetchers;
private final Favicon defaultFavicon;
@Inject
public FeedService(FeedDAO feedDAO, Set<AbstractFaviconFetcher> faviconFetchers) {
public FeedService(FeedDAO feedDAO, @All List<AbstractFaviconFetcher> faviconFetchers) {
this.feedDAO = feedDAO;
this.faviconFetchers = faviconFetchers;

View File

@@ -21,7 +21,6 @@ import com.commafeed.backend.model.Models;
import com.commafeed.backend.model.User;
import com.commafeed.frontend.model.UnreadCount;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
@@ -37,7 +36,6 @@ public class FeedSubscriptionService {
private final CacheService cache;
private final CommaFeedConfiguration config;
@Inject
public FeedSubscriptionService(FeedDAO feedDAO, FeedEntryStatusDAO feedEntryStatusDAO, FeedSubscriptionDAO feedSubscriptionDAO,
FeedService feedService, FeedRefreshEngine feedRefreshEngine, CacheService cache, CommaFeedConfiguration config) {
this.feedDAO = feedDAO;
@@ -63,7 +61,7 @@ public class FeedSubscriptionService {
public long subscribe(User user, String url, String title, FeedCategory category, int position) {
final String pubUrl = config.getApplicationSettings().getPublicUrl();
final String pubUrl = config.publicUrl();
if (StringUtils.isBlank(pubUrl)) {
throw new FeedSubscriptionException("Public URL of this CommaFeed instance is not set");
}
@@ -71,7 +69,7 @@ public class FeedSubscriptionService {
throw new FeedSubscriptionException("Could not subscribe to a feed from this CommaFeed instance");
}
Integer maxFeedsPerUser = config.getApplicationSettings().getMaxFeedsPerUser();
Integer maxFeedsPerUser = config.database().cleanup().maxFeedsPerUser();
if (maxFeedsPerUser > 0 && feedSubscriptionDAO.count(user) >= maxFeedsPerUser) {
String message = String.format("You cannot subscribe to more feeds on this CommaFeed instance (max %s feeds per user)",
maxFeedsPerUser);

View File

@@ -4,10 +4,9 @@ import java.util.Optional;
import java.util.Properties;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.CommaFeedConfiguration.ApplicationSettings;
import com.commafeed.CommaFeedConfiguration.Smtp;
import com.commafeed.backend.model.User;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import jakarta.mail.Authenticator;
import jakarta.mail.Message;
@@ -22,27 +21,29 @@ import lombok.RequiredArgsConstructor;
* Mailing service
*
*/
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor
@Singleton
public class MailService {
private final CommaFeedConfiguration config;
public void sendMail(User user, String subject, String content) throws Exception {
Optional<Smtp> settings = config.smtp();
if (settings.isEmpty()) {
throw new IllegalArgumentException("SMTP settings not configured");
}
ApplicationSettings settings = config.getApplicationSettings();
final String username = settings.getSmtpUserName();
final String password = settings.getSmtpPassword();
final String fromAddress = Optional.ofNullable(settings.getSmtpFromAddress()).orElse(settings.getSmtpUserName());
final String username = settings.get().userName();
final String password = settings.get().password();
final String fromAddress = Optional.ofNullable(settings.get().fromAddress()).orElse(settings.get().userName());
String dest = user.getEmail();
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", String.valueOf(settings.isSmtpTls()));
props.put("mail.smtp.host", settings.getSmtpHost());
props.put("mail.smtp.port", String.valueOf(settings.getSmtpPort()));
props.put("mail.smtp.starttls.enable", String.valueOf(settings.get().tls()));
props.put("mail.smtp.host", settings.get().host());
props.put("mail.smtp.port", String.valueOf(settings.get().port()));
Session session = Session.getInstance(props, new Authenticator() {
@Override

View File

@@ -12,7 +12,6 @@ import javax.crypto.spec.PBEKeySpec;
import org.apache.commons.lang3.StringUtils;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -20,7 +19,7 @@ import lombok.extern.slf4j.Slf4j;
// taken from http://www.javacodegeeks.com/2012/05/secure-password-storage-donts-dos-and.html
@SuppressWarnings("serial")
@Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor
@Singleton
public class PasswordEncryptionService implements Serializable {

View File

@@ -24,11 +24,10 @@ import com.commafeed.backend.model.UserRole.Role;
import com.commafeed.backend.service.internal.PostLoginActivities;
import com.google.common.base.Preconditions;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor
@Singleton
public class UserService {
@@ -117,8 +116,7 @@ public class UserService {
public User register(String name, String password, String email, Collection<Role> roles, boolean forceRegistration) {
if (!forceRegistration) {
Preconditions.checkState(config.getApplicationSettings().getAllowRegistrations(),
"Registrations are closed on this CommaFeed instance");
Preconditions.checkState(config.users().allowRegistrations(), "Registrations are closed on this CommaFeed instance");
}
Preconditions.checkArgument(userDAO.findByName(name) == null, "Name already taken");

View File

@@ -14,7 +14,6 @@ import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.model.Feed;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
@@ -35,7 +34,6 @@ public class DatabaseCleaningService {
private final FeedEntryStatusDAO feedEntryStatusDAO;
private final Meter entriesDeletedMeter;
@Inject
public DatabaseCleaningService(CommaFeedConfiguration config, UnitOfWork unitOfWork, FeedDAO feedDAO, FeedEntryDAO feedEntryDAO,
FeedEntryContentDAO feedEntryContentDAO, FeedEntryStatusDAO feedEntryStatusDAO, MetricRegistry metrics) {
this.unitOfWork = unitOfWork;
@@ -43,7 +41,7 @@ public class DatabaseCleaningService {
this.feedEntryDAO = feedEntryDAO;
this.feedEntryContentDAO = feedEntryContentDAO;
this.feedEntryStatusDAO = feedEntryStatusDAO;
this.batchSize = config.getApplicationSettings().getDatabaseCleanupBatchSize();
this.batchSize = config.database().cleanup().batchSize();
this.entriesDeletedMeter = metrics.meter(MetricRegistry.name(getClass(), "entriesDeleted"));
}

View File

@@ -1,109 +1,41 @@
package com.commafeed.backend.service.db;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.kohsuke.MetaInfServices;
import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.service.UserService;
import io.dropwizard.lifecycle.Managed;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import liquibase.Scope;
import liquibase.UpdateSummaryEnum;
import liquibase.changelog.ChangeLogParameters;
import liquibase.command.CommandScope;
import liquibase.command.core.UpdateCommandStep;
import liquibase.command.core.helpers.DatabaseChangelogCommandStep;
import liquibase.command.core.helpers.DbUrlConnectionArgumentsCommandStep;
import liquibase.command.core.helpers.ShowSummaryArgument;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.core.PostgresDatabase;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.structure.DatabaseObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor
@Singleton
public class DatabaseStartupService implements Managed {
public class DatabaseStartupService {
private final UnitOfWork unitOfWork;
private final SessionFactory sessionFactory;
private final UserDAO userDAO;
private final UserService userService;
private final CommaFeedConfiguration config;
@Override
public void start() {
updateSchema();
public void populateInitialData() {
long count = unitOfWork.call(userDAO::count);
if (count == 0) {
unitOfWork.run(this::initialData);
}
}
private void updateSchema() {
log.info("checking if database schema needs updating");
try (Session session = sessionFactory.openSession()) {
session.doWork(connection -> {
try {
JdbcConnection jdbcConnection = new JdbcConnection(connection);
Database database = getDatabase(jdbcConnection);
Map<String, Object> scopeObjects = new HashMap<>();
scopeObjects.put(Scope.Attr.database.name(), database);
scopeObjects.put(Scope.Attr.resourceAccessor.name(),
new ClassLoaderResourceAccessor(Thread.currentThread().getContextClassLoader()));
Scope.child(scopeObjects, () -> {
CommandScope command = new CommandScope(UpdateCommandStep.COMMAND_NAME);
command.addArgumentValue(DbUrlConnectionArgumentsCommandStep.DATABASE_ARG, database);
command.addArgumentValue(UpdateCommandStep.CHANGELOG_FILE_ARG, "migrations.xml");
command.addArgumentValue(DatabaseChangelogCommandStep.CHANGELOG_PARAMETERS, new ChangeLogParameters(database));
command.addArgumentValue(ShowSummaryArgument.SHOW_SUMMARY, UpdateSummaryEnum.OFF);
command.execute();
});
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
log.info("database schema is up to date");
}
private Database getDatabase(JdbcConnection connection) throws DatabaseException {
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection);
if (database instanceof PostgresDatabase) {
database = new PostgresDatabase() {
@Override
public String escapeObjectName(String objectName, Class<? extends DatabaseObject> objectType) {
return objectName;
}
};
database.setConnection(connection);
}
return database;
}
private void initialData() {
log.info("populating database with default values");
try {
userService.createAdminUser();
if (config.getApplicationSettings().getCreateDemoAccount()) {
if (config.users().createDemoAccount()) {
userService.createDemoUser();
}
} catch (Exception e) {
@@ -111,4 +43,20 @@ public class DatabaseStartupService implements Managed {
}
}
/**
* Register a postgresql database in liquibase that doesn't escape columns, so that we can use lower case columns
*/
@MetaInfServices(Database.class)
public static class LowerCaseColumnsPostgresDatabase extends PostgresDatabase {
@Override
public String escapeObjectName(String objectName, Class<? extends DatabaseObject> objectType) {
return objectName;
}
@Override
public int getPriority() {
return super.getPriority() + 1;
}
}
}

View File

@@ -1,87 +0,0 @@
package com.commafeed.backend.service.db;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import com.manticore.h2.H2MigrationTool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
@Slf4j
public class H2MigrationService {
private static final String H2_FILE_SUFFIX = ".mv.db";
public void migrateIfNeeded(Path path, String user, String password) {
if (Files.notExists(path)) {
return;
}
int format;
try {
format = getH2FileFormat(path);
} catch (IOException e) {
throw new RuntimeException("could not detect H2 format", e);
}
if (format == 2) {
try {
migrate(path, user, password, "2.1.214", "2.2.224");
} catch (Exception e) {
throw new RuntimeException("could not migrate H2 to format 3", e);
}
}
}
public int getH2FileFormat(Path path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path)) {
String headers = reader.readLine();
return Stream.of(headers.split(","))
.filter(h -> h.startsWith("format:"))
.map(h -> h.split(":")[1])
.map(Integer::parseInt)
.findFirst()
.orElseThrow(() -> new RuntimeException("could not find format in H2 file headers"));
}
}
private void migrate(Path path, String user, String password, String fromVersion, String toVersion) throws Exception {
log.info("migrating H2 database at {} from format {} to format {}", path, fromVersion, toVersion);
Path scriptPath = path.resolveSibling("script-%d.sql".formatted(System.currentTimeMillis()));
Path newVersionPath = path.resolveSibling("%s.%s%s".formatted(StringUtils.removeEnd(path.getFileName().toString(), H2_FILE_SUFFIX),
getPatchVersion(toVersion), H2_FILE_SUFFIX));
Path oldVersionBackupPath = path.resolveSibling("%s.%s.backup".formatted(path.getFileName(), getPatchVersion(fromVersion)));
Files.deleteIfExists(scriptPath);
Files.deleteIfExists(newVersionPath);
Files.deleteIfExists(oldVersionBackupPath);
H2MigrationTool.readDriverRecords();
new H2MigrationTool().migrate(fromVersion, toVersion, path.toAbsolutePath().toString(), user, password,
scriptPath.toAbsolutePath().toString(), "", "", false, false, "");
if (!Files.exists(newVersionPath)) {
throw new RuntimeException("H2 migration failed, new version file not found");
}
Files.move(path, oldVersionBackupPath);
Files.move(newVersionPath, path);
Files.delete(oldVersionBackupPath);
Files.delete(scriptPath);
log.info("migrated H2 database from format {} to format {}", fromVersion, toVersion);
}
private String getPatchVersion(String version) {
return StringUtils.substringAfterLast(version, ".");
}
}

View File

@@ -7,11 +7,10 @@ import com.commafeed.backend.dao.UnitOfWork;
import com.commafeed.backend.dao.UserDAO;
import com.commafeed.backend.model.User;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@RequiredArgsConstructor
@Singleton
public class PostLoginActivities {