/* * Copyright (C) 2008-2017, Juick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package com.juick.service; import com.juick.Message; import com.juick.User; import com.juick.service.data.UsersRepository; import com.juick.service.data.entities.EmailEntity; import com.juick.service.data.entities.LoginEntity; import com.juick.service.data.entities.UserEntity; import com.juick.server.helpers.AnonymousUser; import com.juick.server.helpers.Auth; import com.juick.server.helpers.UserInfo; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; import javax.inject.Inject; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; /** * Created by aalexeev on 11/13/16. */ @Repository public class UserServiceImpl extends BaseJdbcService implements UserService { @Inject private UsersRepository usersRepository; private class UserMapper implements RowMapper { @Override public User mapRow(ResultSet rs, int rowNum) throws SQLException { User user = new User(); user.setUid(rs.getInt(1)); user.setName(rs.getString(2)); user.setBanned(rs.getBoolean(3)); user.setLang(rs.getString(4)); return user; } } @Transactional @Override public String getSignUpHashByJID(final String jid) { List list = getJdbcTemplate().queryForList( "SELECT loginhash FROM jids WHERE jid = ? AND user_id IS NULL", String.class, jid); if (list.isEmpty()) { String hash = UUID.randomUUID().toString(); getJdbcTemplate().update("INSERT INTO jids(jid, loginhash) VALUES (?, ?)", jid, hash); return hash; } return list.get(0); } @Transactional @Override public String getSignUpHashByTelegramID(final Long telegramId, final String username) { List list = getJdbcTemplate().queryForList( "SELECT loginhash FROM telegram WHERE tg_id = ? AND user_id IS NULL", String.class, telegramId); if (list.isEmpty()) { String hash = UUID.randomUUID().toString(); getJdbcTemplate().update( "INSERT INTO telegram(tg_id, loginhash, tg_name) VALUES (?, ?, ?)", telegramId, hash, username); return hash; } return list.get(0); } @Transactional(rollbackFor = Exception.class) @Override public User createUser(final String username, final String password) { UserEntity newUser = new UserEntity(); newUser.setName(username); newUser.setPassword(password); try { UserEntity created = usersRepository.save(newUser); getJdbcTemplate().update("INSERT INTO useroptions(user_id) VALUES (?)", created.getId()); getJdbcTemplate().update("INSERT INTO subscr_users(user_id, suser_id) VALUES (2, ?)", created.getId()); return EntityUtils.entityToUser(created); } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return AnonymousUser.INSTANCE; } } @Transactional(readOnly = true) @Override public Optional getUserByUID(final int uid) { return usersRepository.findById(uid).map(EntityUtils::entityToUser); } @Transactional(readOnly = true) @Override public User getUserByName(final String username) { Optional e = usersRepository.findByName(username); if (e.isPresent()) { return EntityUtils.entityToUser(e.get()); } return AnonymousUser.INSTANCE; } @Override @Transactional(readOnly = true) public User getFullyUserByName(final String username) { return usersRepository.findByName(username).map(EntityUtils::entityToSecurityUser).orElse(AnonymousUser.INSTANCE); } @Override @Transactional(readOnly = true) public User getUserByEmail(String email) { Optional e = usersRepository.findByEmailsEmail(email); if (e.isPresent()) { return EntityUtils.entityToUser(e.get()); } return AnonymousUser.INSTANCE; } @Transactional(readOnly = true) @Override public User getUserByJID(final String jid) { User result = null; if (StringUtils.isNotBlank(jid)) { List list = getJdbcTemplate().query( "SELECT id, nick, banned, lang FROM users WHERE id = (SELECT user_id FROM jids WHERE jid = ?)", new UserMapper(), jid); if (!list.isEmpty()) result = list.get(0); } return result; } @Transactional(readOnly = true) @Override public List getUsersByName(final Collection unames) { return usersRepository.findAllByNameIn(unames).map(EntityUtils::entityToUser).collect(Collectors.toList()); } @Transactional(readOnly = true) @Override public List getUsersByID(final Collection uids) { return usersRepository.findAllByIdIn(uids).map(EntityUtils::entityToUser).collect(Collectors.toList()); } @Transactional(readOnly = true) @Override public List getJIDsbyUID(final int uid) { return getJdbcTemplate().queryForList("SELECT jid FROM jids WHERE user_id = ? AND active = 1", String.class, uid); } @Transactional(readOnly = true) @Override public com.juick.User getUserByHash(final String hash) { if (StringUtils.isNotBlank(hash)) { Optional entityUser = usersRepository.findByLogins_Hash(hash); if (entityUser.isPresent()) { return EntityUtils.entityToUser(entityUser.get()); } } return AnonymousUser.INSTANCE; } @Transactional @Override public String getHashForUser(final User user) { UserEntity entityUser = usersRepository.findById(user.getUid()).orElseThrow(IllegalStateException::new); if (entityUser.getLogins().size() > 0) { return entityUser.getLogins().stream().findFirst().orElseThrow(IllegalStateException::new).getHash(); } LoginEntity newHash = new LoginEntity(); newHash.setUser(entityUser); newHash.setHash(RandomStringUtils.randomAlphanumeric(16).toUpperCase()); entityUser.getLogins().add(newHash); return getHashForUser(EntityUtils.entityToUser(usersRepository.save(entityUser))); } @Transactional(readOnly = true) @Override public User checkPassword(final String username, final String password) { Optional userEntity = usersRepository.findByNameAndPassword(username, password); if (userEntity.isPresent()) { return EntityUtils.entityToUser(userEntity.get()); }; return AnonymousUser.INSTANCE; } @Transactional @Override public boolean updatePassword(final User user, final String newPassword) { UserEntity entityUser = usersRepository.findById(user.getUid()).orElseThrow(IllegalStateException::new); entityUser.setPassword(newPassword); usersRepository.save(entityUser); return true; } @Transactional(readOnly = true) @Override public int getUserOptionInt(final int uid, final String option, final int defaultValue) { if (StringUtils.isBlank(option)) return defaultValue; List list = getJdbcTemplate().queryForList( "SELECT " + option + " FROM useroptions WHERE user_id = ?", Integer.class, uid); return list.isEmpty() ? defaultValue : list.get(0); } @Transactional @Override public int setUserOptionInt(final int uid, final String option, final int value) { if (StringUtils.isBlank(option)) return 0; return getJdbcTemplate().update("UPDATE useroptions SET " + option + "= ? WHERE user_id = ?", value, uid); } @Transactional(readOnly = true) @Override public UserInfo getUserInfo(final User user) { List list = getJdbcTemplate().query( "SELECT fullname, country, url, descr FROM usersinfo WHERE user_id = ?", ((rs, rowNum) -> { UserInfo info = new UserInfo(); info.setFullName(rs.getString(1)); info.setCountry(rs.getString(2)); info.setUrl(rs.getString(3)); info.setDescription(rs.getString(4)); return info; }), user.getUid()); return list.isEmpty() ? new UserInfo() : list.get(0); } @Transactional @Override public boolean updateUserInfo(final User user, final UserInfo info) { return getJdbcTemplate().update( "INSERT INTO usersinfo(user_id, fullname, country, url, descr) VALUES (?, ?, ?, ?, ?) " + "ON DUPLICATE KEY UPDATE fullname = ?, country = ?, url = ?, descr = ?", user.getUid(), info.getFullName(), info.getCountry(), info.getUrl(), info.getDescription(), info.getFullName(), info.getCountry(), info.getUrl(), info.getDescription()) > 0; } @Transactional(readOnly = true) @Override public boolean isInWL(final int uid, final int check) { List list = getJdbcTemplate().queryForList( "SELECT 1 FROM wl_users WHERE user_id = ? AND wl_user_id = ?", Integer.class, uid, check); return !list.isEmpty() && list.get(0) == 1; } @Transactional(readOnly = true) @Override public boolean isInBL(final int uid, final int check) { List list = getJdbcTemplate().queryForList( "SELECT 1 FROM bl_users WHERE user_id = ? AND bl_user_id = ?", Integer.class, uid, check); return !list.isEmpty() && list.get(0) == 1; } @Transactional(readOnly = true) @Override public boolean isInBLAny(final int uid, final int uid2) { List list = getJdbcTemplate().queryForList( "SELECT 1 FROM bl_users WHERE (user_id = ? AND bl_user_id = ?) " + "OR (user_id = ? AND bl_user_id = ?)", new Object[]{uid, uid2, uid2, uid}, Integer.class); return !list.isEmpty() && list.get(0) == 1; } @Transactional(readOnly = true) @Override public boolean isReplyToBL(final User user, final Message reply) { return getNamedParameterJdbcTemplate().queryForObject("WITH RECURSIVE banned(reply_id, user_id) AS (" + "SELECT reply_id, user_id FROM replies " + "WHERE replies.message_id = :mid " + "AND EXISTS (SELECT 1 FROM bl_users b WHERE b.user_id = :uid AND b.bl_user_id = replies.user_id) " + "UNION ALL SELECT replies.reply_id, replies.user_id FROM replies " + "INNER JOIN banned ON banned.reply_id = replies.replyto " + "WHERE replies.message_id = :mid) " + "SELECT COUNT(reply_id) from replies " + "INNER JOIN messages m ON m.message_id = replies.message_id " + "WHERE replies.message_id = :mid " + "AND replies.reply_id = :rid " + "AND (EXISTS (SELECT 1 FROM banned WHERE banned.reply_id = replies.reply_id) " + "OR EXISTS (SELECT 1 FROM bl_users b WHERE b.user_id = :uid AND b.bl_user_id = m.user_id)" + "OR EXISTS (SELECT 1 FROM bl_users b WHERE b.bl_user_id = :uid AND b.user_id = m.user_id))", new MapSqlParameterSource("uid", user.getUid()) .addValue("mid", reply.getMid()) .addValue("rid", reply.getRid()), Integer.class) > 0; } @Transactional(readOnly = true) @Override public List checkBL(final int visitor, final Collection uids) { if (CollectionUtils.isEmpty(uids)) return Collections.emptyList(); return getNamedParameterJdbcTemplate().queryForList( "SELECT user_id FROM bl_users WHERE bl_user_id = :visitor and user_id IN (:ids)", new MapSqlParameterSource() .addValue("visitor", visitor) .addValue("ids", uids), Integer.class); } @Transactional(readOnly = true) @Override public boolean isSubscribed(final int uid, final int check) { List list = getJdbcTemplate().queryForList( "SELECT 1 FROM subscr_users WHERE suser_id = ? AND user_id = ?", Integer.class, uid, check); return !list.isEmpty() && list.get(0) == 1; } @Transactional(readOnly = true) @Override public List getUserReadLeastPopular(final int uid, final int cnt) { return getJdbcTemplate().query( "SELECT users.id,users.nick FROM (subscr_users " + "INNER JOIN users_subscr ON (subscr_users.suser_id=? " + "AND subscr_users.user_id=users_subscr.user_id)) INNER JOIN users " + "ON subscr_users.user_id=users.id ORDER BY cnt LIMIT ?", (rs, num) -> { com.juick.User u = new com.juick.User(); u.setUid(rs.getInt(1)); u.setName(rs.getString(2)); return u; }, uid, cnt); } @Transactional(readOnly = true) @Override public List getUserReaders(final int uid) { return getJdbcTemplate().query( "SELECT users.id, users.nick FROM subscr_users " + "INNER JOIN users ON subscr_users.suser_id=users.id " + "WHERE subscr_users.user_id=? ORDER BY users.nick", (rs, num) -> { com.juick.User u = new com.juick.User(); u.setUid(rs.getInt(1)); u.setName(rs.getString(2)); return u; }, uid); } @Transactional(readOnly = true) @Override public List getUserFriends(final int uid) { return getJdbcTemplate().query( "SELECT users.id,users.nick FROM subscr_users " + "INNER JOIN users ON subscr_users.user_id=users.id " + "WHERE subscr_users.suser_id=? AND users.id!=? " + "ORDER BY users.nick", (rs, num) -> { com.juick.User u = new com.juick.User(); u.setUid(rs.getInt(1)); u.setName(rs.getString(2)); return u; }, uid, uid); } @Transactional(readOnly = true) @Override public List getUserBLUsers(final int uid) { return getJdbcTemplate().query("SELECT users.id,users.nick FROM users INNER JOIN bl_users " + "ON(bl_users.bl_user_id=users.id) WHERE bl_users.user_id=? ORDER BY users.nick", (rs, num) -> { com.juick.User u = new com.juick.User(); u.setUid(rs.getInt(1)); u.setName(rs.getString(2)); return u; }, uid); } @Transactional @Override public boolean linkTwitterAccount( final User user, final String accessToken, final String accessTokenSecret, final String screenName) { if (getJdbcTemplate().update("INSERT INTO twitter(user_id,access_token,access_token_secret,uname) " + "VALUES (?,?,?,?)" + " ON DUPLICATE KEY UPDATE access_token=?,access_token_secret=?,uname=?", user.getUid(), accessToken, accessTokenSecret, screenName, accessToken, accessTokenSecret, screenName) > 0) { return getJdbcTemplate().update("INSERT INTO subscr_users(user_id,suser_id,jid) " + "VALUES (?,1741,'juick\\@twitter.juick.com')", user.getUid()) > 0; } return false; } @Transactional(readOnly = true) @Override public int getStatsMyReaders(final int uid) { List list = getJdbcTemplate().queryForList("SELECT COUNT(*) FROM subscr_users WHERE user_id = ?", Integer.class, uid); return list.isEmpty() ? 0 : list.get(0); } @Transactional(readOnly = true) @Override public int getStatsMessages(final int uid) { List list = getJdbcTemplate().queryForList("SELECT COUNT(*) FROM messages WHERE user_id = ?", Integer.class, uid); return list.isEmpty() ? 0 : list.get(0); } @Transactional(readOnly = true) @Override public int getStatsReplies(final int uid) { List list = getJdbcTemplate().queryForList("SELECT COUNT(*) FROM replies WHERE user_id = ?", Integer.class, uid); return list.isEmpty() ? 0 : list.get(0); } @Transactional @Override public boolean setActiveStatusForJID(final String JID, final UserService.ActiveStatus jidStatus) { User user = getUserByJID(JID); if (user != null) { int newStatus = jidStatus == UserService.ActiveStatus.Active ? 1 : 0; return getJdbcTemplate().update( "UPDATE jids SET active = ? WHERE user_id = ? AND jid = ?", newStatus, user.getUid(), JID) >= 0; } return false; } @Transactional(readOnly = true) @Override public List getAllJIDs(final User user) { return getJdbcTemplate().queryForList( "SELECT jid FROM jids WHERE user_id=?", String.class, user.getUid()); } @Transactional(readOnly = true) @Override public List getAuthCodes(final User user) { return getJdbcTemplate().query( "SELECT account,authcode FROM auth WHERE user_id=? AND protocol='xmpp'", (rs, num) -> new Auth(rs.getString(1), rs.getString(2)), user.getUid()); } @Transactional(readOnly = true) @Override public Collection getEmails(final User user) { Optional entityUser = usersRepository.findById(user.getUid()); if (entityUser.isPresent()) { return entityUser.get().getEmails().stream().map(EmailEntity::getEmail).collect(Collectors.toList()); } return Collections.emptyList(); } @Transactional(readOnly = true) @Override public String getEmailHash(final User user) { List list = getJdbcTemplate().queryForList( "SELECT hash FROM mail WHERE user_id = ?", String.class, user.getUid()); return list.isEmpty() ? StringUtils.EMPTY : list.get(0) + "@mail.juick.com"; } @Transactional @Override public int deleteLoginForUser(final String name) { if (StringUtils.isBlank(name)) return 0; return getJdbcTemplate().update( "delete from logins where user_id in (select id from users where nick = ?)", name); } @Transactional @Override public int setLoginForUser(final int uid, final String loginHash) { if (StringUtils.isEmpty(loginHash)) return 0; return getNamedParameterJdbcTemplate().update( "INSERT INTO logins (user_id, hash) VALUES(:uid, :hash) ON DUPLICATE KEY UPDATE hash = :hash", new MapSqlParameterSource() .addValue("hash", loginHash) .addValue("uid", uid)); } @Transactional @Override public void logout(int uid) { getJdbcTemplate().update("DELETE FROM logins WHERE user_id=?", uid); } @Transactional @Override public boolean deleteJID(int uid, String jid) { return getNamedParameterJdbcTemplate().update("DELETE FROM jids " + "WHERE (SELECT COUNT(*) cnt FROM (select user_id, jid FROM jids j) c WHERE user_id=:uid) > 1 " + "AND user_id=:uid AND jid=:jid", new MapSqlParameterSource() .addValue("uid", uid) .addValue("jid", jid)) > 0; } @Transactional @Override public boolean unauthJID(int uid, String jid) { return getJdbcTemplate() .update("DELETE FROM auth WHERE user_id=? AND protocol='xmpp' AND account=?", uid, jid) > 0; } @Transactional(readOnly = true) @Override public List getActiveJIDs() { return getJdbcTemplate().queryForList("SELECT jid FROM jids WHERE active=1 AND loginhash IS NULL", String.class); } }