// Copyright © 2015 HSL <https://www.hsl.fi> // This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses. package fi.hsl.parkandride.back; import org.joda.time.DateTime; import com.querydsl.core.Tuple; import com.querydsl.sql.SQLExpressions; import com.querydsl.sql.dml.SQLDeleteClause; import com.querydsl.sql.dml.SQLInsertClause; import com.querydsl.sql.postgresql.PostgreSQLQuery; import com.querydsl.sql.postgresql.PostgreSQLQueryFactory; import com.querydsl.core.types.MappingProjection; import com.querydsl.core.types.dsl.DateTimeExpression; import com.querydsl.core.types.dsl.SimpleExpression; import fi.hsl.parkandride.back.sql.QAppUser; import fi.hsl.parkandride.core.back.UserRepository; import fi.hsl.parkandride.core.domain.NotFoundException; import fi.hsl.parkandride.core.domain.SearchResults; import fi.hsl.parkandride.core.domain.User; import fi.hsl.parkandride.core.domain.UserSearch; import fi.hsl.parkandride.core.domain.UserSecret; import fi.hsl.parkandride.core.service.TransactionalRead; import fi.hsl.parkandride.core.service.TransactionalWrite; public class UserDao implements UserRepository { public static final String USER_ID_SEQ = "user_id_seq"; private static final SimpleExpression<Long> nextUserId = SQLExpressions.nextval(USER_ID_SEQ); private static final DateTimeExpression<DateTime> currentTime = DateTimeExpression.currentTimestamp(DateTime.class); private static final QAppUser qUser = QAppUser.appUser; private static final MappingProjection<User> userMapping = new MappingProjection<User>(User.class, qUser.id, qUser.username, qUser.role, qUser.operatorId) { @Override protected User map(Tuple row) { User user = new User(); user.id = row.get(qUser.id); user.username = row.get(qUser.username); user.role = row.get(qUser.role); user.operatorId = row.get(qUser.operatorId); return user; } }; private static final MappingProjection<UserSecret> userSecretMapping = new MappingProjection<UserSecret>(UserSecret.class, qUser.password, qUser.minTokenTimestamp, qUser.passwordUpdatedTimestamp, userMapping) { @Override protected UserSecret map(Tuple row) { UserSecret userSecret = new UserSecret(); userSecret.password = row.get(qUser.password); userSecret.passwordUpdatedTimestamp = row.get(qUser.passwordUpdatedTimestamp); userSecret.minTokenTimestamp = row.get(qUser.minTokenTimestamp); // userSecret.secret = row.get(qUser.secret); userSecret.user = row.get(userMapping); return userSecret; } }; private final PostgreSQLQueryFactory queryFactory; public UserDao(PostgreSQLQueryFactory queryFactory) { this.queryFactory = queryFactory; } @Override @TransactionalWrite public long insertUser(UserSecret userSecret) { return insertUser(userSecret, queryFactory.query().select(nextUserId).fetchOne()); } @TransactionalWrite public long insertUser(UserSecret userSecret, long userId) { SQLInsertClause insert = queryFactory.insert(qUser); insert.set(qUser.id, userId) .set(qUser.password, userSecret.password) .set(qUser.passwordUpdatedTimestamp, currentTime) .set(qUser.minTokenTimestamp, currentTime) .set(qUser.username, userSecret.user.username.toLowerCase()) .set(qUser.role, userSecret.user.role) .set(qUser.operatorId, userSecret.user.operatorId); insert.execute(); return userId; } @TransactionalRead @Override public DateTime getCurrentTime() { return queryFactory.query().select(currentTime).fetchOne(); } @TransactionalWrite @Override public void revokeTokens(long userId, DateTime asOf) { if (queryFactory.update(qUser) .where(qUser.id.eq(userId)) .set(qUser.minTokenTimestamp, asOf) .execute() != 1) { notFound(userId); } } @TransactionalWrite @Override public void updatePassword(long userId, String password) { if (queryFactory.update(qUser) .where(qUser.id.eq(userId)) .set(qUser.password, password) .set(qUser.passwordUpdatedTimestamp, currentTime) .set(qUser.minTokenTimestamp, currentTime) .execute() != 1) { notFound(userId); } } @TransactionalWrite @Override public void updateUser(long userId, User user) { if (queryFactory.update(qUser) .where(qUser.id.eq(userId)) .set(qUser.username, user.username.toLowerCase()) .set(qUser.role, user.role) .execute() != 1) { notFound(userId); } } @TransactionalWrite @Override public void deleteUser(long userId) { SQLDeleteClause clause = queryFactory.delete(qUser).where(qUser.id.eq(userId)); if (clause.execute() != 1) { notFound(userId); }; } @TransactionalRead @Override public UserSecret getUser(String username) { UserSecret userSecret = queryFactory .from(qUser) .where(qUser.username.eq(username.toLowerCase())) .select(userSecretMapping).fetchOne(); if (userSecret == null) { notFound(username); } return userSecret; } private void notFound(String username) { throw new NotFoundException("User by username '%s'", username); } private void notFound(long userId) { throw new NotFoundException("User by id '%s'", userId); } @TransactionalRead @Override public UserSecret getUser(long userId) { UserSecret userSecret = queryFactory.from(qUser).where(qUser.id.eq(userId)).select(userSecretMapping).fetchOne(); if (userSecret == null) { notFound(userId); } return userSecret; } @Override @TransactionalRead public SearchResults<User> findUsers(UserSearch search) { PostgreSQLQuery<User> qry = queryFactory.from(qUser).select(userMapping); qry.limit(search.getLimit() + 1); qry.offset(search.getOffset()); if (search.getOperatorId() != null) { qry.where(qUser.operatorId.eq(search.getOperatorId())); } qry.orderBy(qUser.username.asc()); return SearchResults.of(qry.fetch(), search.getLimit()); } }