package io.kaif.model.account;
import java.sql.Array;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import io.kaif.database.DaoOperations;
@Repository
public class AccountDao implements DaoOperations {
private final RowMapper<Account> accountMapper = (rs, rowNum) -> {
Set<Authority> authorities = convertVarcharArray(rs.getArray("authorities")).map(Authority::valueOf)
.collect(Collectors.toSet());
return new Account(//
UUID.fromString(rs.getString("accountId")),
rs.getString("username"),
rs.getString("email"),
rs.getString("passwordHash"),
rs.getString("description"),
rs.getTimestamp("createTime").toInstant(),
authorities);
};
private final RowMapper<AccountOnceToken> tokenMapper = (rs, rowNum) -> {
AccountOnceToken.Type tokenType = AccountOnceToken.Type.valueOf(rs.getString("tokenType"));
return new AccountOnceToken(//
rs.getString("token"),
UUID.fromString(rs.getString("accountId")),
tokenType,
rs.getBoolean("complete"),
rs.getTimestamp("createTime").toInstant());
};
private final RowMapper<AccountStats> statsMapper = (rs, rowNum) -> {
return new AccountStats(//
UUID.fromString(rs.getString("accountId")),
rs.getLong("debateCount"),
rs.getLong("articleCount"),
rs.getLong("articleUpVoted"),
rs.getLong("debateUpVoted"),
rs.getLong("debateDownVoted"));
};
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public NamedParameterJdbcTemplate namedJdbc() {
return namedParameterJdbcTemplate;
}
public Account create(String username, String email, String passwordHash, Instant now) {
final Account account = Account.create(username, email, passwordHash, now);
Preconditions.checkArgument(!account.getAuthorities().contains(Authority.FORBIDDEN));
jdbc().update(""
+ " INSERT "
+ " INTO Account "
+ " (accountId, email, passwordHash, username, "
+ " createTime, authorities, description) "
+ " VALUES "
+ questions(7),
account.getAccountId(),
account.getEmail().toLowerCase(),
account.getPasswordHash(),
account.getUsername(),
Timestamp.from(account.getCreateTime()),
authoritiesToVarcharArray(account.getAuthorities()),
account.getDescription());
createStats(account);
return account;
}
private void createStats(Account account) {
AccountStats stats = AccountStats.zero(account.getAccountId());
jdbc().update(""
+ " INSERT "
+ " INTO AccountStats "
+ " (accountId, debateCount, articleCount, articleUpVoted, "
+ " debateUpVoted, debateDownVoted) "
+ " VALUES "
+ questions(6),
stats.getAccountId(),
stats.getDebateCount(),
stats.getArticleCount(),
stats.getArticleUpVoted(),
stats.getDebateUpVoted(),
stats.getDebateDownVoted());
}
private Array authoritiesToVarcharArray(Set<Authority> authorities) {
return createVarcharArray(authorities.stream().map(Authority::name));
}
public Optional<Account> strongVerifyAccount(Authorization authorization) {
return findById(authorization.authenticatedId()).filter(authorization::matches);
}
public Optional<Account> findById(UUID accountId) {
final String sql = " SELECT * FROM Account WHERE accountId = ? LIMIT 1 ";
return jdbc().query(sql, accountMapper, accountId).stream().findAny();
}
public Optional<Account> findByUsername(String username) {
return jdbc().query(" SELECT * FROM Account WHERE lower(username) = lower(?) LIMIT 1 ",
accountMapper,
username).stream().findAny();
}
public Account loadByUsername(String username) {
return jdbc().queryForObject(" SELECT * FROM Account WHERE lower(username) = lower(?) ",
accountMapper,
username);
}
public void updateAuthorities(Account account, EnumSet<Authority> authorities) {
Account updated = account.withAuthorities(authorities);
jdbc().update(" UPDATE Account SET authorities = ? WHERE accountId = ? ",
authoritiesToVarcharArray(updated.getAuthorities()),
updated.getAccountId());
}
public void updatePasswordHash(UUID accountId, String passwordHash) {
jdbc().update(" UPDATE Account SET passwordHash = ? WHERE accountId = ? ",
passwordHash,
accountId);
}
public boolean isEmailAvailable(String email) {
final String sql = " SELECT count(*) FROM Account WHERE email = ? LIMIT 1 ";
return jdbc().queryForObject(sql, Number.class, email.toLowerCase()).intValue() == 0;
}
@VisibleForTesting
public List<AccountOnceToken> listOnceTokens() {
final String sql = " SELECT * FROM AccountOnceToken ";
return jdbc().query(sql, tokenMapper);
}
public AccountOnceToken createOnceToken(Account account,
AccountOnceToken.Type tokenType,
Instant now) {
AccountOnceToken onceToken = AccountOnceToken.create(account.getAccountId(), tokenType, now);
jdbc().update(""
+ " INSERT "
+ " INTO AccountOnceToken "
+ " (token, accountId, tokenType, "
+ " complete, createTime ) "
+ " VALUES "
+ questions(5),
onceToken.getToken(),
onceToken.getAccountId(),
onceToken.getTokenType().name(),
onceToken.isComplete(),
Timestamp.from(onceToken.getCreateTime()));
return onceToken;
}
public Optional<AccountOnceToken> findOnceToken(String token, AccountOnceToken.Type tokenType) {
final String sql = " SELECT * FROM AccountOnceToken WHERE token = ? AND tokenType = ? LIMIT 1 ";
return jdbc().query(sql, tokenMapper, token, tokenType.name()).stream().findFirst();
}
public void completeOnceToken(AccountOnceToken onceToken) {
jdbc().update(" UPDATE AccountOnceToken SET complete = ? WHERE token = ? ",
true,
onceToken.getToken());
}
public AccountStats loadStats(String username) {
return jdbc().queryForObject(""
+ " SELECT ass.* "
+ " FROM AccountStats ass "
+ " JOIN Account a ON (ass.accountId = a.accountId) "
+ " WHERE a.username = ? ", statsMapper, username);
}
public void increaseArticleCount(Account author) {
jdbc().update(" UPDATE AccountStats SET articleCount = articleCount + 1 WHERE accountId = ? ",
author.getAccountId());
}
public void increaseDebateCount(Account debater) {
jdbc().update(" UPDATE AccountStats SET debateCount = debateCount + 1 WHERE accountId = ? ",
debater.getAccountId());
}
public void changeTotalVotedDebate(UUID accountId, long upVoteDelta, long downVoteDelta) {
if (upVoteDelta == 0 && downVoteDelta == 0) {
return;
}
jdbc().update(""
+ " UPDATE AccountStats "
+ " SET debateUpVoted = debateUpVoted + (?) "
+ " , debateDownVoted = debateDownVoted + (?) "
+ " WHERE accountId = ? ", upVoteDelta, downVoteDelta, accountId);
}
public void changeTotalVotedArticle(UUID accountId, long upVoteDelta, long downVoteDelta) {
if (upVoteDelta == 0 && downVoteDelta == 0) {
return;
}
jdbc().update(""
+ " UPDATE AccountStats "
+ " SET articleUpVoted = articleUpVoted + (?) "
+ " WHERE accountId = ? ", upVoteDelta, accountId);
}
public void updateDescription(UUID accountId, String description) {
jdbc().update(" UPDATE Account SET description = ? WHERE accountId = ? ",
description,
accountId);
}
}