mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-02-23 15:12:52 +08:00
feat: denormalize user avatars
This commit is contained in:
parent
ae3d0abb08
commit
b780050b47
backend/src/main
java/io/papermc/hangar
components
config
controller/internal
db/dao
model
api
common/roles
db
internal/user
service
resources/db/migration
frontend/src
@ -8,6 +8,7 @@ import io.papermc.hangar.components.auth.model.credential.PasswordCredential;
|
||||
import io.papermc.hangar.components.auth.model.db.UserCredentialTable;
|
||||
import io.papermc.hangar.components.auth.model.dto.SignupForm;
|
||||
import io.papermc.hangar.components.auth.model.dto.login.LoginResponse;
|
||||
import io.papermc.hangar.components.images.service.AvatarService;
|
||||
import io.papermc.hangar.db.customtypes.JSONB;
|
||||
import io.papermc.hangar.db.dao.internal.table.UserDAO;
|
||||
import io.papermc.hangar.exceptions.HangarApiException;
|
||||
@ -50,8 +51,9 @@ public class AuthService extends HangarComponent implements UserDetailsService {
|
||||
private final UsersApiService usersApiService;
|
||||
private final BucketService bucketService;
|
||||
private final TurnstileService turnstileService;
|
||||
private final AvatarService avatarService;
|
||||
|
||||
public AuthService(final UserDAO userDAO, final UserCredentialDAO userCredentialDAO, final PasswordEncoder passwordEncoder, final ValidationService validationService, final VerificationService verificationService, final CredentialsService credentialsService, final HibpService hibpService, final MailService mailService, final TokenService tokenService, final UsersApiService usersApiService, final BucketService bucketService, final TurnstileService turnstileService) {
|
||||
public AuthService(final UserDAO userDAO, final UserCredentialDAO userCredentialDAO, final PasswordEncoder passwordEncoder, final ValidationService validationService, final VerificationService verificationService, final CredentialsService credentialsService, final HibpService hibpService, final MailService mailService, final TokenService tokenService, final UsersApiService usersApiService, final BucketService bucketService, final TurnstileService turnstileService, final AvatarService avatarService) {
|
||||
this.userDAO = userDAO;
|
||||
this.userCredentialDAO = userCredentialDAO;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
@ -64,6 +66,7 @@ public class AuthService extends HangarComponent implements UserDetailsService {
|
||||
this.usersApiService = usersApiService;
|
||||
this.bucketService = bucketService;
|
||||
this.turnstileService = turnstileService;
|
||||
this.avatarService = avatarService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@ -88,7 +91,7 @@ public class AuthService extends HangarComponent implements UserDetailsService {
|
||||
}
|
||||
}
|
||||
|
||||
final UserTable userTable = this.userDAO.create(UUID.randomUUID(), form.username(), form.email(), null, "en", List.of(), false, "light", emailVerified, new JSONB(Map.of()));
|
||||
final UserTable userTable = this.userDAO.create(UUID.randomUUID(), form.username(), form.email(), null, "en", List.of(), false, "light", emailVerified, avatarService.getDefaultAvatarUrl(), new JSONB(Map.of()));
|
||||
this.credentialsService.registerCredential(userTable.getUserId(), new PasswordCredential(this.passwordEncoder.encode(form.password())));
|
||||
if (!emailVerified) {
|
||||
this.verificationService.sendVerificationCode(userTable.getUserId(), userTable.getEmail(), userTable.getName());
|
||||
|
@ -13,6 +13,7 @@ import io.papermc.hangar.components.auth.model.oauth.OAuthCodeResponse;
|
||||
import io.papermc.hangar.components.auth.model.oauth.OAuthMode;
|
||||
import io.papermc.hangar.components.auth.model.oauth.OAuthProvider;
|
||||
import io.papermc.hangar.components.auth.model.oauth.OAuthUserDetails;
|
||||
import io.papermc.hangar.components.images.service.AvatarService;
|
||||
import io.papermc.hangar.db.customtypes.JSONB;
|
||||
import io.papermc.hangar.db.dao.internal.table.UserDAO;
|
||||
import io.papermc.hangar.exceptions.HangarApiException;
|
||||
@ -41,10 +42,11 @@ public class OAuthService extends HangarComponent {
|
||||
private final VerificationService verificationService;
|
||||
private final UserCredentialDAO userCredentialDAO;
|
||||
private final CredentialsService credentialsService;
|
||||
private final AvatarService avatarService;
|
||||
|
||||
private final Map<String, OAuthProvider> providers = new HashMap<>();
|
||||
|
||||
public OAuthService(final RestClient restClient, final Algorithm algo, final JWT jwt, final AuthService authService, final UserDAO userDAO, final VerificationService verificationService, final UserCredentialDAO userCredentialDAO, final CredentialsService credentialsService) {
|
||||
public OAuthService(final RestClient restClient, final Algorithm algo, final JWT jwt, final AuthService authService, final UserDAO userDAO, final VerificationService verificationService, final UserCredentialDAO userCredentialDAO, final CredentialsService credentialsService, final AvatarService avatarService) {
|
||||
this.restClient = restClient;
|
||||
this.algo = algo;
|
||||
this.jwt = jwt;
|
||||
@ -53,6 +55,7 @@ public class OAuthService extends HangarComponent {
|
||||
this.verificationService = verificationService;
|
||||
this.userCredentialDAO = userCredentialDAO;
|
||||
this.credentialsService = credentialsService;
|
||||
this.avatarService = avatarService;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
@ -211,7 +214,7 @@ public class OAuthService extends HangarComponent {
|
||||
throw new HangarApiException("This " + provider + " account is already linked to a Hangar account");
|
||||
}
|
||||
|
||||
final UserTable userTable = this.userDAO.create(UUID.randomUUID(), username, email, null, "en", List.of(), false, "light", emailVerified, new JSONB(Map.of()));
|
||||
final UserTable userTable = this.userDAO.create(UUID.randomUUID(), username, email, null, "en", List.of(), false, "light", emailVerified, this.avatarService.getDefaultAvatarUrl(), new JSONB(Map.of()));
|
||||
if (!emailVerified) {
|
||||
this.verificationService.sendVerificationCode(userTable.getUserId(), userTable.getEmail(), userTable.getName());
|
||||
}
|
||||
|
@ -1,26 +1,19 @@
|
||||
package io.papermc.hangar.components.images.service;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import io.papermc.hangar.HangarComponent;
|
||||
import io.papermc.hangar.components.images.controller.AvatarController;
|
||||
import io.papermc.hangar.components.images.dao.AvatarDAO;
|
||||
import io.papermc.hangar.components.images.model.AvatarTable;
|
||||
import io.papermc.hangar.config.CacheConfig;
|
||||
import io.papermc.hangar.model.api.User;
|
||||
import io.papermc.hangar.model.db.UserTable;
|
||||
import io.papermc.hangar.model.internal.user.HangarUser;
|
||||
import io.papermc.hangar.service.internal.file.FileService;
|
||||
import io.papermc.hangar.service.internal.users.UserService;
|
||||
import io.papermc.hangar.util.CryptoUtils;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -46,17 +39,12 @@ public class AvatarService extends HangarComponent {
|
||||
private String defaultAvatarUrl;
|
||||
private String defaultAvatarPath;
|
||||
|
||||
private final Cache<String, String> usernameCache;
|
||||
|
||||
public AvatarService(final FileService fileService, final ImageService imageService, final AvatarDAO avatarDAO, final UserService userService, @Qualifier(CacheConfig.USERNAME) final org.springframework.cache.Cache usernameCache, final RestTemplate restTemplate) {
|
||||
public AvatarService(final FileService fileService, final ImageService imageService, final AvatarDAO avatarDAO, final UserService userService, final RestTemplate restTemplate) {
|
||||
this.fileService = fileService;
|
||||
this.imageService = imageService;
|
||||
this.avatarDAO = avatarDAO;
|
||||
this.userService = userService;
|
||||
this.restTemplate = restTemplate;
|
||||
|
||||
//noinspection unchecked
|
||||
this.usernameCache = (Cache<String, String>) usernameCache.getNativeCache();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
@ -92,27 +80,10 @@ public class AvatarService extends HangarComponent {
|
||||
* Get methods
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getUserAvatarUrl(final UserTable userTable) {
|
||||
private String getUserAvatarUrl(final UserTable userTable) {
|
||||
return this.getAvatarUrl(USER, userTable.getUuid().toString(), null);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getUserAvatarUrl(final UUID orgUserUuid) {
|
||||
return this.getAvatarUrl(USER, orgUserUuid.toString(), null);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getUserAvatarUrl(final User user) {
|
||||
final String uuid;
|
||||
if (user instanceof final HangarUser hangarUser) {
|
||||
uuid = hangarUser.getUuid().toString();
|
||||
} else {
|
||||
uuid = this.usernameCache.get(user.getName(), (key) -> Objects.requireNonNull(this.userService.getUserTable(user.getName())).getUuid().toString());
|
||||
}
|
||||
|
||||
return this.getAvatarUrl(USER, uuid, null);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getProjectAvatarUrl(final long projectId, final String ownerName) {
|
||||
return this.getAvatarUrl(PROJECT, String.valueOf(projectId), () -> {
|
||||
@ -149,15 +120,17 @@ public class AvatarService extends HangarComponent {
|
||||
/*
|
||||
* change methods
|
||||
*/
|
||||
public void changeUserAvatar(final UUID uuid, final byte[] avatar) throws IOException {
|
||||
this.changeAvatar(USER, uuid.toString(), avatar);
|
||||
public void changeUserAvatar(final UserTable user, final byte[] avatar) throws IOException {
|
||||
final String newUrl = this.changeAvatar(USER, user.getUuid().toString(), avatar);
|
||||
user.setAvatarUrl(newUrl);
|
||||
this.userService.updateUser(user);
|
||||
}
|
||||
|
||||
public void changeProjectAvatar(final long projectId, final byte[] avatar) throws IOException {
|
||||
this.changeAvatar(PROJECT, String.valueOf(projectId), avatar);
|
||||
}
|
||||
|
||||
private void changeAvatar(final String type, final String subject, byte[] avatar) throws IOException {
|
||||
private String changeAvatar(final String type, final String subject, byte[] avatar) throws IOException {
|
||||
final String unoptimizedHash = CryptoUtils.md5ToHex(avatar);
|
||||
AvatarTable table = this.avatarDAO.getAvatar(type, subject);
|
||||
if (table != null && table.getUnoptimizedHash().equals(unoptimizedHash)) {
|
||||
@ -175,6 +148,7 @@ public class AvatarService extends HangarComponent {
|
||||
this.avatarDAO.updateAvatar(table);
|
||||
}
|
||||
this.fileService.write(new ByteArrayInputStream(avatar), this.getPath(type, subject), AvatarController.WEBP.toString());
|
||||
return fileService.getAvatarUrl(type, subject, String.valueOf(table.getVersion()));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -220,4 +194,9 @@ public class AvatarService extends HangarComponent {
|
||||
return this.fileService.bytes(this.getPath(type, subject));
|
||||
}
|
||||
}
|
||||
|
||||
public void fixAvatarUrls() {
|
||||
// TODO implement
|
||||
//this.avatarDAO.getUsersWithBrokenAvatars();
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ public class CacheConfig {
|
||||
public static final String GLOBAL_SITEMAP = "globalSitemap-cache";
|
||||
public static final String USER_SITEMAP = "userSitemap-cache";
|
||||
public static final String AVATARS = "avatars-cache";
|
||||
public static final String USERNAME = "username-cache";
|
||||
public static final String VERSION_DEPENDENCIES = "version-dependencies-cache";
|
||||
public static final String LATEST_VERSION = "latest-version-cache";
|
||||
|
||||
@ -174,11 +173,6 @@ public class CacheConfig {
|
||||
return this.createCache(AVATARS, Duration.ofMinutes(30), 2000);
|
||||
}
|
||||
|
||||
@Bean(USERNAME)
|
||||
Cache usernameCache() {
|
||||
return this.createCache(USERNAME, Duration.ofHours(2), 500);
|
||||
}
|
||||
|
||||
@Bean(VERSION_DEPENDENCIES)
|
||||
Cache versionDependenciesCache() {
|
||||
return this.createCache(VERSION_DEPENDENCIES, Duration.ofMinutes(30), 2000);
|
||||
|
@ -204,7 +204,7 @@ public class HangarUserController extends HangarComponent {
|
||||
if (table == null) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown org " + userName);
|
||||
}
|
||||
this.avatarService.changeUserAvatar(table.getUuid(), avatar.getBytes());
|
||||
this.avatarService.changeUserAvatar(table, avatar.getBytes());
|
||||
}
|
||||
|
||||
// @el(userName: String)
|
||||
|
@ -205,7 +205,11 @@ public class OrganizationController extends HangarComponent {
|
||||
if (table == null) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown org " + orgName);
|
||||
}
|
||||
this.avatarService.changeUserAvatar(table.getUserUuid(), avatar.getBytes());
|
||||
final UserTable userTable = this.userService.getUserTable(table.getUserId());
|
||||
if (userTable == null) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown org user " + orgName + " (" + table.getUserId() + ")");
|
||||
}
|
||||
this.avatarService.changeUserAvatar(userTable, avatar.getBytes());
|
||||
}
|
||||
|
||||
@Anyone
|
||||
|
@ -38,6 +38,7 @@ public interface UsersDAO {
|
||||
u.language,
|
||||
u.theme,
|
||||
u.socials,
|
||||
u.avatar_url,
|
||||
exists(SELECT 1 FROM organizations o WHERE u.id = o.user_id) AS is_organization
|
||||
FROM users u
|
||||
WHERE lower(u.name) = lower(:name)
|
||||
@ -67,6 +68,7 @@ public interface UsersDAO {
|
||||
" u.locked," +
|
||||
" u.language," +
|
||||
" u.theme," +
|
||||
" u.avatar_url," +
|
||||
" exists(SELECT 1 FROM organizations o WHERE u.id = o.user_id) AS is_organization" +
|
||||
" FROM users u" +
|
||||
" <if(hasQuery)>WHERE u.name ILIKE '%' || :query || '%'<endif>" +
|
||||
|
@ -21,24 +21,24 @@ public interface UserDAO {
|
||||
@Timestamped
|
||||
@GetGeneratedKeys
|
||||
@SqlUpdate("""
|
||||
INSERT INTO users (uuid, created_at, name, email, tagline, read_prompts, locked, language, theme, email_verified, socials)
|
||||
VALUES (:uuid, :now, :name, :email, :tagline, :readPrompts, :locked, :language, :theme, :emailVerified, :socials)
|
||||
INSERT INTO users (uuid, created_at, name, email, tagline, read_prompts, locked, language, theme, email_verified, avatar_url, socials)
|
||||
VALUES (:uuid, :now, :name, :email, :tagline, :readPrompts, :locked, :language, :theme, :emailVerified, :avatarUrl, :socials)
|
||||
""")
|
||||
UserTable insert(@BindBean UserTable user);
|
||||
|
||||
@Timestamped
|
||||
@GetGeneratedKeys
|
||||
@SqlUpdate("""
|
||||
INSERT INTO users (uuid, created_at, name, email, tagline, read_prompts, locked, language, theme, email_verified, socials)
|
||||
VALUES (:uuid, :now, :name, :email, :tagline, :readPrompts, :locked, :language, :theme, :emailVerified, :socials)
|
||||
INSERT INTO users (uuid, created_at, name, email, tagline, read_prompts, locked, language, theme, email_verified, avatar_url, socials)
|
||||
VALUES (:uuid, :now, :name, :email, :tagline, :readPrompts, :locked, :language, :theme, :emailVerified, :avatarUrl, :socials)
|
||||
""")
|
||||
UserTable create(UUID uuid, String name, String email, String tagline, String language, List<Integer> readPrompts, boolean locked, String theme, boolean emailVerified, JSONB socials);
|
||||
UserTable create(UUID uuid, String name, String email, String tagline, String language, List<Integer> readPrompts, boolean locked, String theme, boolean emailVerified, String avatarUrl, JSONB socials);
|
||||
|
||||
@SqlUpdate("DELETE FROM users WHERE id = :id")
|
||||
void delete(long id);
|
||||
|
||||
@GetGeneratedKeys
|
||||
@SqlUpdate("UPDATE users SET name = :name, email = :email, tagline = :tagline, read_prompts = :readPrompts, locked = :locked, language = :language, theme = :theme, email_verified = :emailVerified, socials = :socials WHERE id = :id")
|
||||
@SqlUpdate("UPDATE users SET name = :name, email = :email, tagline = :tagline, read_prompts = :readPrompts, locked = :locked, language = :language, theme = :theme, email_verified = :emailVerified, avatar_url = :avatarUrl, socials = :socials WHERE id = :id")
|
||||
UserTable update(@BindBean UserTable user);
|
||||
|
||||
@SqlUpdate("DELETE FROM users WHERE id = :id")
|
||||
|
@ -60,7 +60,7 @@ public interface OrganizationRolesDAO extends IRolesDAO<OrganizationRoleTable> {
|
||||
|
||||
@KeyColumn("name")
|
||||
@SqlQuery("""
|
||||
SELECT o.name, uor.*, ow.id AS ownerId, ow.name AS ownerName, ou.uuid
|
||||
SELECT o.name, uor.*, ow.id AS ownerId, ow.name AS ownerName, ou.uuid, ou.avatar_url
|
||||
FROM user_organization_roles uor
|
||||
JOIN organizations o ON o.id = uor.organization_id
|
||||
JOIN users u ON uor.user_id = u.id
|
||||
|
@ -155,6 +155,7 @@ public interface ProjectsApiDAO {
|
||||
" u.tagline," +
|
||||
" u.locked," +
|
||||
" u.socials, " +
|
||||
" u.avatar_url," +
|
||||
" array_agg(r.id) AS roles," +
|
||||
" (SELECT count(*)" +
|
||||
" FROM project_members_all pma" +
|
||||
@ -182,6 +183,7 @@ public interface ProjectsApiDAO {
|
||||
" u.tagline," +
|
||||
" u.locked," +
|
||||
" u.socials, " +
|
||||
" u.avatar_url," +
|
||||
" array_agg(r.id) AS roles," +
|
||||
" (SELECT count(*)" +
|
||||
" FROM project_members_all pma" +
|
||||
|
@ -109,6 +109,7 @@ public interface UsersApiDAO {
|
||||
" u.tagline," +
|
||||
" u.locked," +
|
||||
" u.socials, " +
|
||||
" u.avatar_url," +
|
||||
" array(SELECT r.id FROM roles r JOIN user_global_roles ugr ON r.id = ugr.role_id WHERE u.id = ugr.user_id ORDER BY r.permission::bigint DESC) AS roles," +
|
||||
" (SELECT count(*) FROM project_members_all pma WHERE pma.user_id = u.id) AS project_count" +
|
||||
" FROM users u" +
|
||||
@ -138,6 +139,7 @@ public interface UsersApiDAO {
|
||||
" u.tagline," +
|
||||
" u.locked," +
|
||||
" u.socials, " +
|
||||
" u.avatar_url," +
|
||||
" array_agg(r.id ORDER BY r.permission::bigint DESC) AS roles," +
|
||||
" (SELECT count(*) FROM project_members_all pma WHERE pma.user_id = u.id) AS project_count" +
|
||||
" FROM users u" +
|
||||
|
@ -26,7 +26,7 @@ public class User extends Model implements Named, Identified {
|
||||
private final JSONB socials;
|
||||
|
||||
@JdbiConstructor
|
||||
public User(final OffsetDateTime createdAt, final long id, final String name, final String tagline, final List<Long> roles, final long projectCount, final boolean locked, @Nullable final List<UserNameChange> nameHistory, final JSONB socials) {
|
||||
public User(final OffsetDateTime createdAt, final long id, final String name, final String tagline, final List<Long> roles, final long projectCount, final boolean locked, @Nullable final List<UserNameChange> nameHistory, final String avatarUrl, final JSONB socials) {
|
||||
super(createdAt);
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
@ -36,6 +36,7 @@ public class User extends Model implements Named, Identified {
|
||||
this.isOrganization = roles.contains(GlobalRole.ORGANIZATION.getRoleId());
|
||||
this.locked = locked;
|
||||
this.nameHistory = nameHistory;
|
||||
this.avatarUrl = avatarUrl;
|
||||
this.socials = socials;
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,6 @@ public enum OrganizationRole implements Role<OrganizationRoleTable> {
|
||||
@Override
|
||||
public @NotNull OrganizationRoleTable create(final Long organizationId, final UUID uuid, final long userId, final boolean isAccepted) {
|
||||
Preconditions.checkNotNull(organizationId, "organization id");
|
||||
return new OrganizationRoleTable(userId, this, isAccepted, organizationId, uuid);
|
||||
return new OrganizationRoleTable(userId, this, isAccepted, organizationId, uuid, null);
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,11 @@ public class UserTable extends Table implements ProjectOwner {
|
||||
private String language;
|
||||
private String theme;
|
||||
private boolean emailVerified;
|
||||
private String avatarUrl;
|
||||
private JSONB socials;
|
||||
|
||||
@JdbiConstructor
|
||||
public UserTable(final OffsetDateTime createdAt, @PropagateNull final long id, final UUID uuid, final String name, final String email, final String tagline, final List<Integer> readPrompts, final boolean locked, final String language, final String theme, final boolean emailVerified, final JSONB socials) {
|
||||
public UserTable(final OffsetDateTime createdAt, @PropagateNull final long id, final UUID uuid, final String name, final String email, final String tagline, final List<Integer> readPrompts, final boolean locked, final String language, final String theme, final boolean emailVerified, final String avatarUrl, final JSONB socials) {
|
||||
super(createdAt, id);
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
@ -35,10 +36,11 @@ public class UserTable extends Table implements ProjectOwner {
|
||||
this.language = language;
|
||||
this.theme = theme;
|
||||
this.emailVerified = emailVerified;
|
||||
this.avatarUrl = avatarUrl;
|
||||
this.socials = socials;
|
||||
}
|
||||
|
||||
public UserTable(final long id, final UUID uuid, final String name, final String email, final List<Integer> readPrompts, final boolean locked, final String language, final String theme, final boolean emailVerified, final JSONB socials) {
|
||||
public UserTable(final long id, final UUID uuid, final String name, final String email, final List<Integer> readPrompts, final boolean locked, final String language, final String theme, final boolean emailVerified, final String avatarUrl, final JSONB socials) {
|
||||
super(id);
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
@ -48,6 +50,7 @@ public class UserTable extends Table implements ProjectOwner {
|
||||
this.language = language;
|
||||
this.theme = theme;
|
||||
this.emailVerified = emailVerified;
|
||||
this.avatarUrl = avatarUrl;
|
||||
this.socials = socials;
|
||||
}
|
||||
|
||||
@ -133,6 +136,14 @@ public class UserTable extends Table implements ProjectOwner {
|
||||
this.socials = socials;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(final String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUserId() {
|
||||
return this.id;
|
||||
|
@ -17,21 +17,23 @@ public class OrganizationRoleTable extends ExtendedRoleTable<OrganizationRole, O
|
||||
private final UUID uuid;
|
||||
private final long ownerId;
|
||||
private final String ownerName;
|
||||
private String avatarUrl;
|
||||
private final String avatarUrl;
|
||||
|
||||
@JdbiConstructor
|
||||
public OrganizationRoleTable(final OffsetDateTime createdAt, final long id, final long userId, @ColumnName("role_type") final OrganizationRole role, final boolean accepted, final long organizationId, @Nullable final UUID uuid, @Nullable final Long ownerId, @Nullable final String ownerName) {
|
||||
public OrganizationRoleTable(final OffsetDateTime createdAt, final long id, final long userId, @ColumnName("role_type") final OrganizationRole role, final boolean accepted, final long organizationId, @Nullable final UUID uuid, @Nullable final Long ownerId, @Nullable final String ownerName, @Nullable final String avatarUrl) {
|
||||
super(createdAt, id, userId, role, accepted);
|
||||
this.organizationId = organizationId;
|
||||
this.uuid = uuid;
|
||||
this.ownerId = ownerId == null ? -1 : ownerId;
|
||||
this.ownerName = ownerName;
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
public OrganizationRoleTable(final long userId, final OrganizationRole role, final boolean accepted, final long organizationId, @Nullable final UUID uuid) {
|
||||
public OrganizationRoleTable(final long userId, final OrganizationRole role, final boolean accepted, final long organizationId, @Nullable final UUID uuid, @Nullable final String avatarUrl) {
|
||||
super(userId, role, accepted);
|
||||
this.organizationId = organizationId;
|
||||
this.uuid = uuid;
|
||||
this.avatarUrl = avatarUrl;
|
||||
this.ownerId = -1;
|
||||
this.ownerName = null;
|
||||
}
|
||||
@ -63,10 +65,6 @@ public class OrganizationRoleTable extends ExtendedRoleTable<OrganizationRole, O
|
||||
return this.avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(final String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OrganizationRoleTable{" +
|
||||
|
@ -25,8 +25,8 @@ public class HangarUser extends User {
|
||||
@Nullable
|
||||
private Integer aal;
|
||||
|
||||
public HangarUser(final OffsetDateTime createdAt, final String name, final String tagline, final List<Long> roles, final long projectCount, final boolean locked, @Nullable final List<UserNameChange> nameHistory, final long id, final UUID uuid, final String email, final List<Integer> readPrompts, final String language, final String theme, final JSONB socials) {
|
||||
super(createdAt, id, name, tagline, roles, projectCount, locked, nameHistory, socials);
|
||||
public HangarUser(final OffsetDateTime createdAt, final String name, final String tagline, final List<Long> roles, final long projectCount, final boolean locked, @Nullable final List<UserNameChange> nameHistory, final long id, final UUID uuid, final String email, final List<Integer> readPrompts, final String language, final String theme, final String avatarUrl, final JSONB socials) {
|
||||
super(createdAt, id, name, tagline, roles, projectCount, locked, nameHistory, avatarUrl, socials);
|
||||
this.uuid = uuid;
|
||||
this.email = email;
|
||||
this.readPrompts = readPrompts;
|
||||
|
@ -9,7 +9,6 @@ public class JoinableMember<R extends ExtendedRoleTable<?, ?>> {
|
||||
private final R role;
|
||||
private final UserTable user;
|
||||
private final boolean hidden;
|
||||
private String avatarUrl;
|
||||
|
||||
public JoinableMember(@Nested final R role, final UserTable user, final boolean hidden) {
|
||||
this.role = role;
|
||||
@ -29,14 +28,6 @@ public class JoinableMember<R extends ExtendedRoleTable<?, ?>> {
|
||||
return this.hidden;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return this.avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(final String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JoinableMember{" +
|
||||
|
@ -70,16 +70,12 @@ public class ProjectsApiService extends HangarComponent {
|
||||
@Transactional(readOnly = true)
|
||||
public PaginatedResult<User> getProjectStargazers(final ProjectTable project, final RequestPagination pagination) {
|
||||
final List<User> stargazers = this.projectsApiDAO.getProjectStargazers(project.getId(), pagination.getLimit(), pagination.getOffset());
|
||||
// TODO rewrite avatar fetching
|
||||
stargazers.forEach(this.usersApiService::supplyAvatarUrl);
|
||||
return new PaginatedResult<>(new Pagination(this.projectsApiDAO.getProjectStargazersCount(project.getId()), pagination), stargazers);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public PaginatedResult<User> getProjectWatchers(final ProjectTable project, final RequestPagination pagination) {
|
||||
final List<User> watchers = this.projectsApiDAO.getProjectWatchers(project.getId(), pagination.getLimit(), pagination.getOffset());
|
||||
// TODO rewrite avatar fetching
|
||||
watchers.forEach(this.usersApiService::supplyAvatarUrl);
|
||||
return new PaginatedResult<>(new Pagination(this.projectsApiDAO.getProjectWatchersCount(project.getId()), pagination), watchers);
|
||||
}
|
||||
|
||||
|
@ -70,8 +70,6 @@ public class UsersApiService extends HangarComponent {
|
||||
public <T extends User> T getUser(final long id, final Class<T> type) {
|
||||
final T user = this.getUserRequired(id, this.usersDAO::getUser, type);
|
||||
this.supplyNameHistory(user);
|
||||
// TODO rewrite avatar fetching
|
||||
this.supplyAvatarUrl(user);
|
||||
return user instanceof HangarUser ? (T) this.supplyHeaderData((HangarUser) user) : user;
|
||||
}
|
||||
|
||||
@ -79,8 +77,6 @@ public class UsersApiService extends HangarComponent {
|
||||
public <T extends User> PaginatedResult<T> getUsers(final String query, final RequestPagination pagination, final Class<T> type) {
|
||||
final boolean hasQuery = !StringUtils.isBlank(query);
|
||||
final List<T> users = this.usersDAO.getUsers(hasQuery, query, pagination, type);
|
||||
// TODO rewrite avatar fetching
|
||||
users.forEach(u -> u.setAvatarUrl(this.avatarService.getUserAvatarUrl(u)));
|
||||
return new PaginatedResult<>(new Pagination(this.usersDAO.getUsersCount(hasQuery, query), pagination), users);
|
||||
}
|
||||
|
||||
@ -112,8 +108,6 @@ public class UsersApiService extends HangarComponent {
|
||||
public PaginatedResult<User> getAuthors(final String query, final RequestPagination pagination) {
|
||||
final boolean hasQuery = !StringUtils.isBlank(query);
|
||||
final List<User> users = this.usersApiDAO.getAuthors(hasQuery, query, pagination);
|
||||
// TODO rewrite avatar fetching (less important)
|
||||
users.forEach(u -> u.setAvatarUrl(this.avatarService.getUserAvatarUrl(u)));
|
||||
final long count = this.usersApiDAO.getAuthorsCount(hasQuery, query);
|
||||
return new PaginatedResult<>(new Pagination(count, pagination), users);
|
||||
}
|
||||
@ -128,8 +122,6 @@ public class UsersApiService extends HangarComponent {
|
||||
public PaginatedResult<User> getStaff(final String query, final RequestPagination pagination) {
|
||||
final boolean hasQuery = !StringUtils.isBlank(query);
|
||||
final List<User> users = this.usersApiDAO.getStaff(hasQuery, query, this.config.user.staffRoles(), pagination);
|
||||
// TODO rewrite avatar fetching (less important)
|
||||
users.forEach(u -> u.setAvatarUrl(this.avatarService.getUserAvatarUrl(u)));
|
||||
final long count = this.usersApiDAO.getStaffCount(hasQuery, query, this.config.user.staffRoles());
|
||||
return new PaginatedResult<>(new Pagination(count, pagination), users);
|
||||
}
|
||||
@ -175,11 +167,6 @@ public class UsersApiService extends HangarComponent {
|
||||
user.setNameHistory(userNameHistory);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public void supplyAvatarUrl(final User user) {
|
||||
user.setAvatarUrl(this.avatarService.getUserAvatarUrl(user));
|
||||
}
|
||||
|
||||
public List<ProjectCompact> getUserPinned(final UserTable user) {
|
||||
return this.pinnedProjectService.getPinnedProjects(user.getId());
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import io.papermc.hangar.HangarComponent;
|
||||
import io.papermc.hangar.components.auth.model.credential.PasswordCredential;
|
||||
import io.papermc.hangar.components.auth.model.credential.TotpCredential;
|
||||
import io.papermc.hangar.components.auth.service.CredentialsService;
|
||||
import io.papermc.hangar.components.images.service.AvatarService;
|
||||
import io.papermc.hangar.config.CacheConfig;
|
||||
import io.papermc.hangar.db.customtypes.JSONB;
|
||||
import io.papermc.hangar.db.dao.internal.table.UserDAO;
|
||||
@ -52,8 +53,9 @@ public class FakeDataService extends HangarComponent {
|
||||
private final CredentialsService credentialsService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final QrDataFactory qrDataFactory;
|
||||
private final AvatarService avatarService;
|
||||
|
||||
public FakeDataService(final UserDAO userDAO, final GlobalRoleService globalRoleService, final ProjectService projectService, final ProjectFactory projectFactory, final ProjectsDAO projectsDAO, final CredentialsService credentialsService, final PasswordEncoder passwordEncoder, final QrDataFactory qrDataFactory) {
|
||||
public FakeDataService(final UserDAO userDAO, final GlobalRoleService globalRoleService, final ProjectService projectService, final ProjectFactory projectFactory, final ProjectsDAO projectsDAO, final CredentialsService credentialsService, final PasswordEncoder passwordEncoder, final QrDataFactory qrDataFactory, final AvatarService avatarService) {
|
||||
this.userDAO = userDAO;
|
||||
this.globalRoleService = globalRoleService;
|
||||
this.projectService = projectService;
|
||||
@ -62,6 +64,7 @@ public class FakeDataService extends HangarComponent {
|
||||
this.credentialsService = credentialsService;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.qrDataFactory = qrDataFactory;
|
||||
this.avatarService = avatarService;
|
||||
}
|
||||
|
||||
@CacheEvict(cacheNames = CacheConfig.PROJECTS, allEntries = true)
|
||||
@ -101,6 +104,7 @@ public class FakeDataService extends HangarComponent {
|
||||
false,
|
||||
"dark",
|
||||
true,
|
||||
this.avatarService.getDefaultAvatarUrl(),
|
||||
new JSONB(Map.of()));
|
||||
this.globalRoleService.addRole(new GlobalRoleTable(userTable.getId(), GlobalRole.DUMMY));
|
||||
return userTable;
|
||||
@ -158,6 +162,7 @@ public class FakeDataService extends HangarComponent {
|
||||
false,
|
||||
"dark",
|
||||
true,
|
||||
this.avatarService.getDefaultAvatarUrl(),
|
||||
new JSONB(Map.of()));
|
||||
this.globalRoleService.addRole(new GlobalRoleTable(admin.getId(), GlobalRole.HANGAR_ADMIN));
|
||||
|
||||
@ -169,6 +174,7 @@ public class FakeDataService extends HangarComponent {
|
||||
false,
|
||||
"dark",
|
||||
true,
|
||||
this.avatarService.getDefaultAvatarUrl(),
|
||||
new JSONB(Map.of()));
|
||||
|
||||
final String password = this.config.e2e.password();
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.papermc.hangar.service.internal.organizations;
|
||||
|
||||
import io.papermc.hangar.HangarComponent;
|
||||
import io.papermc.hangar.components.images.service.AvatarService;
|
||||
import io.papermc.hangar.db.customtypes.JSONB;
|
||||
import io.papermc.hangar.db.dao.internal.table.OrganizationDAO;
|
||||
import io.papermc.hangar.db.dao.internal.table.UserDAO;
|
||||
@ -34,9 +35,10 @@ public class OrganizationFactory extends HangarComponent {
|
||||
private final ProjectInviteService inviteService;
|
||||
private final ProjectsDAO projectsDAO;
|
||||
private final ProjectFactory projectFactory;
|
||||
private final AvatarService avatarService;
|
||||
|
||||
@Autowired
|
||||
public OrganizationFactory(final UserDAO userDAO, final OrganizationDAO organizationDAO, final OrganizationService organizationService, final OrganizationMemberService organizationMemberService, final GlobalRoleService globalRoleService, final ProjectInviteService inviteService, final ProjectsDAO projectsDAO, final ProjectFactory projectFactory) {
|
||||
public OrganizationFactory(final UserDAO userDAO, final OrganizationDAO organizationDAO, final OrganizationService organizationService, final OrganizationMemberService organizationMemberService, final GlobalRoleService globalRoleService, final ProjectInviteService inviteService, final ProjectsDAO projectsDAO, final ProjectFactory projectFactory, final AvatarService avatarService) {
|
||||
this.userDAO = userDAO;
|
||||
this.organizationDAO = organizationDAO;
|
||||
this.organizationService = organizationService;
|
||||
@ -45,6 +47,7 @@ public class OrganizationFactory extends HangarComponent {
|
||||
this.inviteService = inviteService;
|
||||
this.projectsDAO = projectsDAO;
|
||||
this.projectFactory = projectFactory;
|
||||
this.avatarService = avatarService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@ -57,7 +60,7 @@ public class OrganizationFactory extends HangarComponent {
|
||||
}
|
||||
|
||||
final String dummyEmail = name.replaceAll("[^a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]", "") + '@' + this.config.org.dummyEmailDomain();
|
||||
final UserTable userTable = this.userDAO.create(UUID.randomUUID(), name, dummyEmail, "", "", List.of(), false, null, true, new JSONB(Map.of()));
|
||||
final UserTable userTable = this.userDAO.create(UUID.randomUUID(), name, dummyEmail, "", "", List.of(), false, null, true, this.avatarService.getDefaultAvatarUrl(), new JSONB(Map.of()));
|
||||
final OrganizationTable organizationTable = this.organizationDAO.insert(new OrganizationTable(userTable.getId(), name, this.getHangarPrincipal().getId(), userTable.getId(), userTable.getUuid()));
|
||||
this.globalRoleService.addRole(GlobalRole.ORGANIZATION.create(null, userTable.getUuid(), userTable.getId(), false));
|
||||
this.organizationMemberService.addNewAcceptedByDefaultMember(OrganizationRole.ORGANIZATION_OWNER.create(organizationTable.getId(), userTable.getUuid(), this.getHangarPrincipal().getId(), true));
|
||||
|
@ -75,8 +75,6 @@ public class OrganizationService extends HangarComponent {
|
||||
}
|
||||
final UserTable owner = this.userDAO.getUserTable(organizationTable.getOwnerId());
|
||||
final List<JoinableMember<OrganizationRoleTable>> members = this.hangarOrganizationsDAO.getOrganizationMembers(organizationTable.getId(), this.getHangarUserId(), this.permissionService.getOrganizationPermissions(this.getHangarUserId(), organizationTable.getId()).has(Permission.ManageOrganizationMembers));
|
||||
// TODO rewrite avatar fetching
|
||||
members.forEach(member -> member.setAvatarUrl(this.avatarService.getUserAvatarUrl(member.getUser())));
|
||||
return new HangarOrganization(organizationTable.getId(), owner, members);
|
||||
}
|
||||
|
||||
@ -86,8 +84,6 @@ public class OrganizationService extends HangarComponent {
|
||||
final Map<String, Boolean> visibility = this.organizationMemberService.getUserOrganizationMembershipVisibility(user);
|
||||
roles.keySet().removeIf(org -> Boolean.TRUE.equals(visibility.getOrDefault(org, true)));
|
||||
}
|
||||
// TODO rewrite avatar fetching
|
||||
roles.values().forEach(org -> org.setAvatarUrl(this.avatarService.getUserAvatarUrl(org.getUuid())));
|
||||
return roles;
|
||||
}
|
||||
|
||||
|
@ -145,10 +145,7 @@ public class ProjectService extends HangarComponent {
|
||||
}
|
||||
|
||||
final CompletableFuture<List<JoinableMember<ProjectRoleTable>>> membersFuture = this.supply(() -> {
|
||||
final List<JoinableMember<ProjectRoleTable>> members = this.hangarProjectsDAO.getProjectMembers(projectId, hangarUserId, this.permissionService.getProjectPermissions(hangarUserId, projectId).has(Permission.EditProjectSettings));
|
||||
// TODO rewrite avatar fetching
|
||||
members.parallelStream().forEach((member) -> member.setAvatarUrl(this.avatarService.getUserAvatarUrl(member.getUser())));
|
||||
return members;
|
||||
return this.hangarProjectsDAO.getProjectMembers(projectId, hangarUserId, this.permissionService.getProjectPermissions(hangarUserId, projectId).has(Permission.EditProjectSettings));
|
||||
});
|
||||
|
||||
final Map<Platform, HangarVersion> mainChannelVersions = new EnumMap<>(Platform.class);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.papermc.hangar.service.internal.versions;
|
||||
|
||||
import io.papermc.hangar.components.images.service.AvatarService;
|
||||
import io.papermc.hangar.db.customtypes.JSONB;
|
||||
import io.papermc.hangar.db.dao.internal.table.projects.ProjectsDAO;
|
||||
import io.papermc.hangar.db.dao.internal.table.versions.ProjectVersionsDAO;
|
||||
@ -62,9 +63,10 @@ public class JarScanningService {
|
||||
private final FileService fileService;
|
||||
private final ReviewService reviewService;
|
||||
private final UserService userService;
|
||||
private final AvatarService avatarService;
|
||||
private UserTable jarScannerUser;
|
||||
|
||||
public JarScanningService(final JarScanResultDAO dao, final ProjectVersionsDAO projectVersionsDAO, final ProjectVersionDownloadsDAO downloadsDAO, final ProjectsDAO projectsDAO, final ProjectFiles projectFiles, final FileService fileService, final ReviewService reviewService, final UserService userService) {
|
||||
public JarScanningService(final JarScanResultDAO dao, final ProjectVersionsDAO projectVersionsDAO, final ProjectVersionDownloadsDAO downloadsDAO, final ProjectsDAO projectsDAO, final ProjectFiles projectFiles, final FileService fileService, final ReviewService reviewService, final UserService userService, final AvatarService avatarService) {
|
||||
this.dao = dao;
|
||||
this.projectVersionsDAO = projectVersionsDAO;
|
||||
this.downloadsDAO = downloadsDAO;
|
||||
@ -73,6 +75,7 @@ public class JarScanningService {
|
||||
this.fileService = fileService;
|
||||
this.reviewService = reviewService;
|
||||
this.userService = userService;
|
||||
this.avatarService = avatarService;
|
||||
}
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
@ -112,6 +115,7 @@ public class JarScanningService {
|
||||
Locale.ENGLISH.toLanguageTag(),
|
||||
"white",
|
||||
true,
|
||||
this.avatarService.getDefaultAvatarUrl(),
|
||||
new JSONB(Map.of())
|
||||
);
|
||||
return this.userService.insertUser(userTable);
|
||||
|
@ -0,0 +1 @@
|
||||
ALTER TABLE users ADD COLUMN avatar_url VARCHAR(255);
|
@ -118,9 +118,16 @@ async function save() {
|
||||
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
notifications.error("Error while saving avatar");
|
||||
console.error("Error while saving avatar", err);
|
||||
handleRequestError(err, "Error while saving avatar");
|
||||
reset();
|
||||
modal.value?.close();
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
cropperResult.value = undefined;
|
||||
selectedFile.value = undefined;
|
||||
cropperInput.value = undefined;
|
||||
v.value.$reset();
|
||||
}
|
||||
</script>
|
||||
|
@ -160,7 +160,7 @@ async function doSearch(val?: string) {
|
||||
:key="member.user.name"
|
||||
class="p-2 w-full border border-gray-100 dark:border-gray-800 rounded inline-flex flex-row space-x-4"
|
||||
>
|
||||
<UserAvatar :username="member.user.name" :avatar-url="member.avatarUrl" size="sm" class="flex-shrink-0" />
|
||||
<UserAvatar :username="member.user.name" :avatar-url="member.user.avatarUrl" size="sm" class="flex-shrink-0" />
|
||||
<div class="flex-grow truncate">
|
||||
<p class="font-semibold">
|
||||
<Link :to="'/' + member.user.name">{{ member.user.name }}</Link>
|
||||
|
@ -769,6 +769,7 @@ export interface UserTable {
|
||||
language: string;
|
||||
theme: string;
|
||||
emailVerified: boolean;
|
||||
avatarUrl: string;
|
||||
socials: JsonNode;
|
||||
/** @format int64 */
|
||||
userId: number;
|
||||
@ -1180,7 +1181,6 @@ export interface JoinableMemberProjectRoleTable {
|
||||
role: ProjectRoleTable;
|
||||
user: UserTable;
|
||||
hidden: boolean;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export interface PossibleProjectOwner {
|
||||
@ -1239,7 +1239,6 @@ export interface JoinableMemberOrganizationRoleTable {
|
||||
role: OrganizationRoleTable;
|
||||
user: UserTable;
|
||||
hidden: boolean;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export enum OAuthMode {
|
||||
|
Loading…
Reference in New Issue
Block a user