aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2023-04-10 22:53:40 +0300
committerGravatar Vitaly Takmazov2023-04-13 19:49:55 +0300
commit379f1a8661da46040332923a5ccfb555656b0a8b (patch)
tree12b3d6121d749c4dc942b10c733f77d7d6dbba94 /src
parent3a1e45d3015df62eb5768a8e6929cb41dcaf9913 (diff)
Premium users
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/juick/model/User.java9
-rw-r--r--src/main/java/com/juick/service/MessagesServiceImpl.java8
-rw-r--r--src/main/java/com/juick/service/UserService.java7
-rw-r--r--src/main/java/com/juick/service/UserServiceImpl.java57
-rw-r--r--src/main/java/com/juick/www/api/ApiSocialLogin.java4
-rw-r--r--src/main/java/com/juick/www/controllers/SocialLogin.java46
-rw-r--r--src/main/resources/db/migration/V1.46__premium_users.sql1
-rw-r--r--src/main/resources/schema-h2.sql1
-rw-r--r--src/main/resources/schema-mysql.sql1
-rw-r--r--src/main/resources/schema-sqlite.sql1
-rw-r--r--src/main/resources/schema-sqlserver.sql1
-rw-r--r--src/main/resources/templates/views/partial/message.html2
-rw-r--r--src/main/resources/templates/views/partial/navigation.html3
-rw-r--r--src/main/resources/templates/views/thread.html4
14 files changed, 111 insertions, 34 deletions
diff --git a/src/main/java/com/juick/model/User.java b/src/main/java/com/juick/model/User.java
index 131ec1b1..5f89ecfc 100644
--- a/src/main/java/com/juick/model/User.java
+++ b/src/main/java/com/juick/model/User.java
@@ -61,6 +61,7 @@ public class User implements Serializable {
private String url;
private String description;
private final List<TagStats> tagStats;
+ private boolean premium;
public User() {
tokens = new ArrayList<>();
@@ -279,4 +280,12 @@ public class User implements Serializable {
public List<TagStats> getTagStats() {
return tagStats;
}
+
+ public boolean isPremium() {
+ return premium;
+ }
+
+ public void setPremium(boolean premium) {
+ this.premium = premium;
+ }
}
diff --git a/src/main/java/com/juick/service/MessagesServiceImpl.java b/src/main/java/com/juick/service/MessagesServiceImpl.java
index 083524b1..53f2407c 100644
--- a/src/main/java/com/juick/service/MessagesServiceImpl.java
+++ b/src/main/java/com/juick/service/MessagesServiceImpl.java
@@ -83,6 +83,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
user.setName(Optional.ofNullable(rs.getString(5)).orElse(AnonymousUser.INSTANCE.getName()));
user.setBanned(rs.getBoolean(6));
user.setUri(URI.create(Optional.ofNullable(rs.getString(22)).orElse(StringUtils.EMPTY)));
+ user.setPremium(rs.getInt("premium") > 0);
msg.setUser(user);
msg.setCreated(MessagesServiceImpl.this.getOffsetDateTime(rs, 7).toInstant());
msg.ReadOnly = rs.getBoolean(8);
@@ -797,7 +798,8 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
COALESCE(messages.txt, '') txt, '' as q, messages.updated, 0 as to_uid, NULL as to_name,
messages.updated_at, '' as m_user_uri, '' as to_uri, '' as msg_reply_uri, 0 as html,
(1.*messages.replies - subscr_messages.last_read_rid) as unread,
- (SELECT CASE WHEN EXISTS(SELECT * from subscr_messages where message_id=messages.message_id and suser_id=:uid) THEN 1 ELSE 0 END) subscribed
+ (SELECT CASE WHEN EXISTS(SELECT * from subscr_messages where message_id=messages.message_id and suser_id=:uid) THEN 1 ELSE 0 END) subscribed,
+ users.premium
FROM messages INNER JOIN users ON messages.user_id=users.id LEFT JOIN subscr_messages
ON messages.message_id=subscr_messages.message_id AND subscr_messages.suser_id=:uid
LEFT JOIN favorites ON messages.message_id = favorites.message_id AND favorites.like_id=1
@@ -808,7 +810,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
messages.message_id, messages.user_id, users.nick, users.banned, messages.ts,
messages.readonly, messages.privacy, messages.attach, messages.hidden,
messages.repliesby, messages.txt, messages.updated, messages.replies, updated_at,
- subscr_messages.last_read_rid""";
+ subscr_messages.last_read_rid, users.premium""";
List<Message> msgs = getNamedParameterJdbcTemplate().query(query,
new MapSqlParameterSource("ids", mids)
.addValue("uid", uid), new MessageMapper());
@@ -867,7 +869,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ "COALESCE(qw.user_id, m.user_id) as to_uid, COALESCE(qu.nick, mu.nick) as to_name, "
+ "replies.updated_at, replies.user_uri as uri, "
+ "qw.user_uri as to_uri, replies.reply_uri, replies.html, 0 as unread, "
- + "0 as subscribed "
+ + "0 as subscribed, users.premium "
+ "FROM replies LEFT JOIN users " + "ON replies.user_id = users.id "
+ "LEFT JOIN replies qw ON replies.message_id = qw.message_id and replies.replyto = qw.reply_id "
+ "LEFT JOIN messages m on replies.message_id = m.message_id "
diff --git a/src/main/java/com/juick/service/UserService.java b/src/main/java/com/juick/service/UserService.java
index 05725b73..ec5beb13 100644
--- a/src/main/java/com/juick/service/UserService.java
+++ b/src/main/java/com/juick/service/UserService.java
@@ -152,7 +152,7 @@ public interface UserService {
String getTelegramName(int uid);
- Optional<Pair<String, String>> getVkTokens(int uid);
+ List<Pair<String, String>> getVkTokens(List<Integer> uids);
void deleteVKUser(Integer uid);
@@ -166,6 +166,9 @@ public interface UserService {
boolean createVKUser(long vkID, String loginhash, String token, String vkName, String vkLink);
+ boolean updateVkUser(long vkID, String token, String vkName, String vkLink);
+ boolean updateVkToken(long userId, String token);
+
String getFacebookNameByHash(String hash);
String getTelegramNameByHash(String hash);
@@ -197,4 +200,6 @@ public interface UserService {
boolean addToken(Integer uid, String serviceType, String token);
boolean deleteToken(String serviceType, String token);
+
+ void setPremium(Integer uid, boolean isPremium);
}
diff --git a/src/main/java/com/juick/service/UserServiceImpl.java b/src/main/java/com/juick/service/UserServiceImpl.java
index 932be19c..a7ea5c5f 100644
--- a/src/main/java/com/juick/service/UserServiceImpl.java
+++ b/src/main/java/com/juick/service/UserServiceImpl.java
@@ -65,6 +65,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
user.setSeen(seen.toInstant());
}
user.setVerified(rs.getLong(6) > 0);
+ user.setPremium(rs.getInt(7) > 0);
return user;
}
}
@@ -113,7 +114,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
public Optional<User> getUserByUID(final int uid) {
var list = getJdbcTemplate().query("""
SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen,
- COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified
+ COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium
FROM users u LEFT JOIN facebook f ON f.user_id = u.id
LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id
LEFT JOIN emails e ON e.user_id = u.id WHERE u.id = ?""", new UserMapper(), uid);
@@ -127,7 +128,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
if (StringUtils.isNotBlank(username)) {
var list = getJdbcTemplate().query("""
SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen,
- COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified
+ COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium
FROM users u LEFT JOIN facebook f ON f.user_id = u.id
LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id
LEFT JOIN emails e ON e.user_id = u.id
@@ -152,7 +153,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
try {
List<User> list = getJdbcTemplate().query(
"SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, " +
- "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
+ "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
"LEFT JOIN emails e ON e.user_id = u.id " +
@@ -178,7 +179,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
if (StringUtils.isNotBlank(jid)) {
List<User> list = getJdbcTemplate().query(
"SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," +
- "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
+ "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
"LEFT JOIN emails e ON e.user_id = u.id " +
@@ -200,7 +201,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
return getNamedParameterJdbcTemplate().query(
"SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," +
- "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
+ "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
"LEFT JOIN emails e ON e.user_id = u.id " +
@@ -217,7 +218,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
return getNamedParameterJdbcTemplate().query(
"SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," +
- "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
+ "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
"LEFT JOIN emails e ON e.user_id = u.id " +
@@ -252,7 +253,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
if (StringUtils.isNotBlank(hash)) {
List<User> list = getJdbcTemplate().query(
"SELECT DISTINCT logins.user_id, u.nick, u.passw, u.banned, u.last_seen," +
- "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
+ "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " +
"FROM logins INNER JOIN users u ON logins.user_id = u.id " +
"LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
@@ -290,7 +291,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
if (StringUtils.isNotBlank(username)) {
List<User> list = getJdbcTemplate().query(
"SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," +
- "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
+ "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
"LEFT JOIN emails e ON e.user_id = u.id " +
@@ -719,7 +720,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
List<User> list = getJdbcTemplate().query(
"""
SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen,
- COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified
+ COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium
FROM users u LEFT JOIN facebook f ON f.user_id = u.id
LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id
LEFT JOIN emails e ON e.user_id = u.id
@@ -732,14 +733,14 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
@Transactional(readOnly = true)
@Override
- public Optional<Pair<String, String>> getVkTokens(final int uid) {
- List<Optional<Pair<String, String>>> list = getJdbcTemplate().query(
- "SELECT vk_id, access_token FROM vk WHERE user_id = ? AND crosspost = 1",
- (rs, num) -> Optional.of(Pair.of(rs.getString(1), rs.getString(2))),
- uid);
-
- return list.isEmpty() ?
- Optional.empty() : list.get(0);
+ public List<Pair<String, String>> getVkTokens(List<Integer> uids) {
+ return getNamedParameterJdbcTemplate().query(
+ """
+ SELECT vk_id, access_token FROM vk WHERE crosspost = 1 AND access_token <> ''"""
+ + (uids.isEmpty() ? "" : " AND user_id IN (:uids)"),
+ new MapSqlParameterSource()
+ .addValue("uids", uids),
+ (rs, num) -> Pair.of(rs.getString(1), rs.getString(2)));
}
@Transactional
@@ -753,7 +754,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
public Optional<User> getUserByFacebookId(long facebookId) {
List<User> list = getJdbcTemplate().query(
"SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, " +
- "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
+ "COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified, premium " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
"LEFT JOIN emails e ON e.user_id = u.id WHERE f.fb_id = ?", new UserMapper(), facebookId);
@@ -789,6 +790,18 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
return getJdbcTemplate().update("INSERT INTO vk(vk_id,loginhash,access_token,vk_name,vk_link) VALUES (?,?,?,?,?)",
vkID, loginhash, token, vkName, vkLink) > 0;
}
+ @Transactional
+ @Override
+ public boolean updateVkUser(long vkID, String token, String vkName, String vkLink) {
+ return getJdbcTemplate().update("UPDATE vk SET access_token=?,vk_name=?,vk_link=? WHERE vk_id=?",
+ token, vkName, vkLink, vkID) > 0;
+ }
+ @Transactional
+ @Override
+ public boolean updateVkToken(long userId, String token) {
+ return getJdbcTemplate().update("UPDATE vk SET access_token=? WHERE user_id=?",
+ token, userId) > 0;
+ }
@Transactional(readOnly = true)
@Override
@@ -864,7 +877,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
@Transactional(readOnly = true)
@Override
public boolean canDeleteTelegramUser(User user) {
- return getEmails(user).size() > 0 || getFbCrossPostStatus(user.getUid()).isConnected() || getVkTokens(user.getUid()).isPresent();
+ return getEmails(user).size() > 0 || getFbCrossPostStatus(user.getUid()).isConnected() || !getVkTokens(List.of(user.getUid())).isEmpty();
}
private static class TokenMapper implements RowMapper<ExternalToken> {
@@ -918,5 +931,11 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
public boolean deleteToken(final String serviceType, final String token) {
return getJdbcTemplate().update("DELETE FROM user_services WHERE regid=? AND service_type=?", token, serviceType) > 0;
}
+
+ @Transactional
+ @Override
+ public void setPremium(final Integer uid, boolean isPremium) {
+ getJdbcTemplate().update("UPDATE users SET premium=? WHERE id=?", isPremium ? 1 : 0, uid);
+ }
}
diff --git a/src/main/java/com/juick/www/api/ApiSocialLogin.java b/src/main/java/com/juick/www/api/ApiSocialLogin.java
index a05e33d9..bf0d26bc 100644
--- a/src/main/java/com/juick/www/api/ApiSocialLogin.java
+++ b/src/main/java/com/juick/www/api/ApiSocialLogin.java
@@ -113,7 +113,7 @@ public class ApiSocialLogin {
.build(FacebookApi.instance());
vkAuthService = vkBuilder
.apiSecret(VK_SECRET)
- .defaultScope("friends,wall,offline")
+ .defaultScope("friends,wall,offline,groups")
.callback(VK_REDIRECT)
.build(VkontakteApi.instance());
ServiceBuilder appleSignInBuilder = new ServiceBuilder(appleApplicationId);
@@ -195,7 +195,7 @@ public class ApiSocialLogin {
}
OAuth2AccessToken token = vkAuthService.getAccessToken(code);
- OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://api.vk.com/method/users.get?fields=screen_name&v=5.73");
+ OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://api.vk.com/method/users.get?fields=screen_name&v=5.131");
vkAuthService.signRequest(token, meRequest);
String graph = vkAuthService.execute(meRequest).getBody();
diff --git a/src/main/java/com/juick/www/controllers/SocialLogin.java b/src/main/java/com/juick/www/controllers/SocialLogin.java
index 1ab0a139..b0738fea 100644
--- a/src/main/java/com/juick/www/controllers/SocialLogin.java
+++ b/src/main/java/com/juick/www/controllers/SocialLogin.java
@@ -45,6 +45,7 @@ import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.RememberMeServices;
@@ -119,7 +120,7 @@ public class SocialLogin {
String facebookRedirectUri = redirectBuilder.replacePath("/_fblogin").build().toUriString();
facebookAuthService = facebookBuilder.apiSecret(FACEBOOK_SECRET).callback(facebookRedirectUri)
.defaultScope("email").build(FacebookApi.instance());
- vkAuthService = vkBuilder.apiSecret(VK_SECRET).defaultScope("friends,wall,offline").callback(VK_REDIRECT)
+ vkAuthService = vkBuilder.apiSecret(VK_SECRET).defaultScope("friends,wall,offline,groups").callback(VK_REDIRECT)
.build(VkontakteApi.instance());
ServiceBuilder appleSignInBuilder = new ServiceBuilder(appleApplicationId);
String appleSignInRedirectUri = redirectBuilder.replacePath("/_apple").build().toUriString();
@@ -194,7 +195,7 @@ public class SocialLogin {
@GetMapping("/_twitter")
protected void doTwitterLogin(com.juick.model.User user, HttpServletRequest request,
- HttpServletResponse response) throws IOException, ExecutionException, InterruptedException {
+ HttpServletResponse response) throws IOException, ExecutionException, InterruptedException {
String hash = StringUtils.EMPTY, request_token = StringUtils.EMPTY, request_token_secret = StringUtils.EMPTY;
String verifier = request.getParameter("oauth_verifier");
Cookie[] cookies = request.getCookies();
@@ -279,6 +280,7 @@ public class SocialLogin {
long vkID = NumberUtils.toLong(jsonUser.id(), 0);
int uid = userService.getUIDbyVKID(vkID);
if (uid > 0) {
+ userService.updateVkUser(vkID, token.getAccessToken(), vkName, vkLink);
Cookie c = new Cookie("hash", userService.getHashByUID(uid));
c.setMaxAge(50 * 24 * 60 * 60);
response.addCookie(c);
@@ -299,9 +301,9 @@ public class SocialLogin {
@GetMapping("/_tglogin")
public String doDurovLogin(@RequestParam Map<String, String> params,
- @RequestParam String hash,
- @RequestHeader(value = "referer", required = false) String referer,
- HttpServletRequest request, HttpServletResponse response) {
+ @RequestParam String hash,
+ @RequestHeader(value = "referer", required = false) String referer,
+ HttpServletRequest request, HttpServletResponse response) {
String dataCheckString = params.entrySet().stream().filter(p -> !p.getKey().equals("hash"))
.sorted(Map.Entry.comparingByKey()).map(p -> p.getKey() + "=" + p.getValue())
.collect(Collectors.joining("\n"));
@@ -345,7 +347,8 @@ public class SocialLogin {
@PostMapping("/_apple")
public String doVerifyAppleResponse(HttpServletRequest request, HttpServletResponse response,
- @RequestParam Map<String, String> body, HttpSession session) throws InterruptedException, ExecutionException, IOException {
+ @RequestParam Map<String, String> body, HttpSession session)
+ throws InterruptedException, ExecutionException, IOException {
OAuth2AccessToken token = appleSignInService.getAccessToken(body.get("code"));
var jsonNode = jsonMapper.readTree(token.getRawResponse());
var idToken = jsonNode.get("id_token").textValue();
@@ -372,4 +375,35 @@ public class SocialLogin {
}
throw new HttpBadRequestException();
}
+
+ @Scheduled(fixedRate = 3600000)
+ public void updatePremium() {
+ userService.getVkTokens(List.of())
+ .forEach(vkUser -> {
+ var userId = userService.getUIDbyVKID(Long.parseLong(vkUser.getLeft()));
+ if (userId > 0) {
+ OAuth2AccessToken token = new OAuth2AccessToken(vkUser.getRight());
+ OAuthRequest donRequest = new OAuthRequest(Verb.GET,
+ "https://api.vk.com/method/donut.isDon?owner_id=-67669480&v=5.131");
+ vkAuthService.signRequest(token, donRequest);
+ try {
+ Response vkResponse = vkAuthService.execute(donRequest);
+ if (vkResponse.isSuccessful()) {
+ logger.info(vkResponse.getBody());
+ var response = jsonMapper.readTree(vkResponse.getBody());
+ if (response.has("response")) {
+ var isDon = response.get("response").intValue() > 0;
+ logger.info("{} is Don: {}", vkUser.getLeft(), isDon);
+ userService.setPremium(userId, isDon);
+ } else {
+ // token is expired or does not have "groups" permissions
+ userService.updateVkToken(userId, "");
+ }
+ }
+ } catch (Exception e) {
+ logger.error("Don request error", e);
+ }
+ }
+ });
+ }
}
diff --git a/src/main/resources/db/migration/V1.46__premium_users.sql b/src/main/resources/db/migration/V1.46__premium_users.sql
new file mode 100644
index 00000000..11edb121
--- /dev/null
+++ b/src/main/resources/db/migration/V1.46__premium_users.sql
@@ -0,0 +1 @@
+alter table users add column premium smallint not null default (0)::smallint;
diff --git a/src/main/resources/schema-h2.sql b/src/main/resources/schema-h2.sql
index ea979df1..8b786382 100644
--- a/src/main/resources/schema-h2.sql
+++ b/src/main/resources/schema-h2.sql
@@ -66,6 +66,7 @@ CREATE MEMORY TABLE "PUBLIC"."USERS"(
"LASTPM" INTEGER DEFAULT '0' NOT NULL,
"LASTPHOTO" INTEGER DEFAULT '0' NOT NULL,
"KARMA" SMALLINT DEFAULT '0' NOT NULL,
+ "PREMIUM" SMALLINT DEFAULT '0' NOT NULL,
"LAST_SEEN" TIMESTAMP(6)
);
ALTER TABLE "PUBLIC"."USERS" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_4" PRIMARY KEY("ID");
diff --git a/src/main/resources/schema-mysql.sql b/src/main/resources/schema-mysql.sql
index e458203f..d0943254 100644
--- a/src/main/resources/schema-mysql.sql
+++ b/src/main/resources/schema-mysql.sql
@@ -558,6 +558,7 @@ CREATE TABLE `users` (
`lastpm` int(11) NOT NULL DEFAULT 0,
`lastphoto` int(11) NOT NULL DEFAULT 0,
`karma` smallint(6) NOT NULL DEFAULT 0,
+ `premium` smallint default '0' not null,
`last_seen` timestamp(6) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `nick` (`nick`)
diff --git a/src/main/resources/schema-sqlite.sql b/src/main/resources/schema-sqlite.sql
index 84ad015c..ae1f4317 100644
--- a/src/main/resources/schema-sqlite.sql
+++ b/src/main/resources/schema-sqlite.sql
@@ -227,6 +227,7 @@ CREATE TABLE users (
lastpm bigint DEFAULT (0) NOT NULL,
lastphoto bigint DEFAULT (0) NOT NULL,
karma smallint DEFAULT (0) NOT NULL,
+ premium smallint DEFAULT (0) NOT NULL,
last_seen timestamp with time zone,
UNIQUE(nick)
);
diff --git a/src/main/resources/schema-sqlserver.sql b/src/main/resources/schema-sqlserver.sql
index 4a683189..34032b67 100644
--- a/src/main/resources/schema-sqlserver.sql
+++ b/src/main/resources/schema-sqlserver.sql
@@ -8,6 +8,7 @@ CREATE TABLE users (
lastpm bigint DEFAULT (0) NOT NULL,
lastphoto bigint DEFAULT (0) NOT NULL,
karma smallint DEFAULT (0) NOT NULL,
+ premium smallint DEFAULT (0) NOT NULL,
last_seen datetimeoffset,
UNIQUE(nick),
PRIMARY KEY (id)
diff --git a/src/main/resources/templates/views/partial/message.html b/src/main/resources/templates/views/partial/message.html
index 9d54d614..e9ddd3db 100644
--- a/src/main/resources/templates/views/partial/message.html
+++ b/src/main/resources/templates/views/partial/message.html
@@ -1,7 +1,7 @@
<article class="msg-cont" data-mid="{{ msg.mid }}">
<header class="h">
<span>
- <a href="/{{ msg.user.name }}/"><span>{{ msg.user.name }}</span></a>
+ <a href="/{{ msg.user.name }}/"><span>{{ msg.user.name }}</span>{% if msg.user.premium %}<span style="color: green;"><i data-icon="ei-star" data-size="s"></i></span>{% endif %}</a>
</span>
<div class="msg-avatar"><a href="/{{ msg.user.name }}/">
<img src="{{ msg.user.avatar }}" alt="{{ msg.user.name }}"/></a>
diff --git a/src/main/resources/templates/views/partial/navigation.html b/src/main/resources/templates/views/partial/navigation.html
index 159c3d21..105cc568 100644
--- a/src/main/resources/templates/views/partial/navigation.html
+++ b/src/main/resources/templates/views/partial/navigation.html
@@ -4,6 +4,9 @@
<div id="ctitle">
<a href="/{{ visitor.name }}/">
<img src="{{ visitor.avatar }}" alt=""/>{{ visitor.name }}
+ {% if visitor.premium %}
+ <span style="color: green;"><i data-icon="ei-star" data-size="s"></i></span>
+ {% endif %}
{% if not visitor.verified %}
<span style="color: red;"><i data-icon="ei-exclamation" data-size="s"></i></span>
{% endif %}
diff --git a/src/main/resources/templates/views/thread.html b/src/main/resources/templates/views/thread.html
index ca74a2ff..84c52282 100644
--- a/src/main/resources/templates/views/thread.html
+++ b/src/main/resources/templates/views/thread.html
@@ -9,7 +9,7 @@
<a href="/{{ msg.user.name }}/"><img src="{{ msg.user.avatar }}" alt="{{ msg.user.name }}"/></a>
</div>
<span>
- <a href="/{{ msg.user.name }}/"><span>{{ msg.user.name }}</span></a>
+ <a href="/{{ msg.user.name }}/"><span>{{ msg.user.name }}</span>{% if msg.user.premium %}<span style="color: green;"><i data-icon="ei-star" data-size="s"></i></span>{% endif %}</a>
</span>
<div class="msg-ts">
{% if msg.FriendsOnly %}
@@ -129,7 +129,7 @@
<div class="msg-cont">
<div class="msg-header" data-uri="{{ msg.user.uri }}">
{% if not msg.user.banned %}
- <a class="a-username" href="/{{ msg.user.name }}/">{{ msg.user.name }}</a>
+ <a class="a-username" href="/{{ msg.user.name }}/">{{ msg.user.name }}{% if msg.user.premium %}<span style="color: green;"><i data-icon="ei-star" data-size="s"></i></span>{% endif %}</a>
<div class="msg-avatar">
<a class="a-username" href="/{{ msg.user.name }}/">
<img src="{{ msg.user.avatar }}" alt="{{ msg.user.name }}"/>