IdDatabaseMaintenanceQueries.java
/*
* Copyright © 2023 Mark Raynsford <code@io7m.com> https://www.io7m.com
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package com.io7m.idstore.database.postgres.internal;
import com.io7m.idstore.database.api.IdDatabaseException;
import com.io7m.idstore.database.api.IdDatabaseMaintenanceQueriesType;
import com.io7m.idstore.model.IdAdminPermission;
import com.io7m.jaffirm.core.Invariants;
import com.io7m.jdeferthrow.core.ExceptionTracker;
import org.jooq.exception.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.EnumSet;
import java.util.Map;
import static com.io7m.idstore.database.postgres.internal.IdDatabaseAdminsQueries.permissionsSerialize;
import static com.io7m.idstore.database.postgres.internal.IdDatabaseExceptions.handleDatabaseException;
import static com.io7m.idstore.database.postgres.internal.Tables.ADMINS;
import static com.io7m.idstore.database.postgres.internal.Tables.BANS;
import static com.io7m.idstore.database.postgres.internal.Tables.EMAIL_VERIFICATIONS;
import static com.io7m.idstore.database.postgres.internal.Tables.USER_PASSWORD_RESETS;
import static java.lang.Integer.valueOf;
/**
* The maintenance queries.
*/
public final class IdDatabaseMaintenanceQueries
extends IdBaseQueries
implements IdDatabaseMaintenanceQueriesType
{
private static final Logger LOG =
LoggerFactory.getLogger(IdDatabaseMaintenanceQueries.class);
IdDatabaseMaintenanceQueries(
final IdDatabaseTransaction inTransaction)
{
super(inTransaction);
}
@Override
public void runMaintenance()
throws IdDatabaseException
{
final var exceptions =
new ExceptionTracker<IdDatabaseException>();
try {
this.runExpireEmailVerifications();
} catch (final IdDatabaseException e) {
exceptions.addException(e);
}
try {
this.runExpireBans();
} catch (final IdDatabaseException e) {
exceptions.addException(e);
}
try {
this.runExpirePasswordResets();
} catch (final IdDatabaseException e) {
exceptions.addException(e);
}
try {
this.runUpdateInitialAdminPermissions();
} catch (final IdDatabaseException e) {
exceptions.addException(e);
}
exceptions.throwIfNecessary();
}
private void runUpdateInitialAdminPermissions()
throws IdDatabaseException
{
final var transaction =
this.transaction();
final var context =
transaction.createContext();
final var querySpan =
transaction.createQuerySpan(
"IdDatabaseMaintenanceQueries.runUpdateInitialAdminPermissions");
try {
final var updated =
context.update(ADMINS)
.set(
ADMINS.PERMISSIONS,
permissionsSerialize(EnumSet.allOf(IdAdminPermission.class)))
.where(ADMINS.INITIAL.eq(Boolean.TRUE))
.execute();
Invariants.checkInvariantI(
updated,
updated <= 1,
x -> "At most one administrator must have been updated."
);
LOG.debug("updated permissions for {} initial admins", valueOf(updated));
} catch (final DataAccessException e) {
querySpan.recordException(e);
throw handleDatabaseException(transaction, e, Map.of());
} finally {
querySpan.end();
}
}
private void runExpirePasswordResets()
throws IdDatabaseException
{
final var transaction =
this.transaction();
final var context =
transaction.createContext();
final var querySpan =
transaction.createQuerySpan(
"IdDatabaseMaintenanceQueries.runExpirePasswordResets");
try {
final var deleted =
context.deleteFrom(USER_PASSWORD_RESETS)
.where(USER_PASSWORD_RESETS.EXPIRES.lt(this.currentTime()))
.execute();
LOG.debug("deleted {} expired password resets", valueOf(deleted));
} catch (final DataAccessException e) {
querySpan.recordException(e);
throw handleDatabaseException(transaction, e, Map.of());
} finally {
querySpan.end();
}
}
private void runExpireEmailVerifications()
throws IdDatabaseException
{
final var transaction =
this.transaction();
final var context =
transaction.createContext();
final var querySpan =
transaction.createQuerySpan(
"IdDatabaseMaintenanceQueries.runExpireEmailVerifications");
try {
final var deleted =
context.deleteFrom(EMAIL_VERIFICATIONS)
.where(EMAIL_VERIFICATIONS.EXPIRES.lt(this.currentTime()))
.execute();
LOG.debug("deleted {} expired email verifications", valueOf(deleted));
} catch (final DataAccessException e) {
querySpan.recordException(e);
throw handleDatabaseException(transaction, e, Map.of());
} finally {
querySpan.end();
}
}
private void runExpireBans()
throws IdDatabaseException
{
final var transaction =
this.transaction();
final var context =
transaction.createContext();
final var querySpan =
transaction.createQuerySpan("IdDatabaseMaintenanceQueries.runExpireBans");
try {
final var deleted =
context.deleteFrom(BANS)
.where(BANS.EXPIRES.lt(this.currentTime()))
.execute();
LOG.debug("deleted {} expired bans", valueOf(deleted));
} catch (final DataAccessException e) {
querySpan.recordException(e);
throw handleDatabaseException(transaction, e, Map.of());
} finally {
querySpan.end();
}
}
}