From 36c187b7a4886785887fcec2048af69113287845 Mon Sep 17 00:00:00 2001 From: MiniDigger | Martin Date: Fri, 7 Apr 2023 15:21:52 +0200 Subject: [PATCH] feat: profile saving --- .../internal/HangarUserController.java | 37 +++++++++++++++++++ .../api/requests/UserProfileSettings.java | 3 ++ .../src/pages/auth/settings/[...slug].vue | 8 +++- 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/java/io/papermc/hangar/model/internal/api/requests/UserProfileSettings.java diff --git a/backend/src/main/java/io/papermc/hangar/controller/internal/HangarUserController.java b/backend/src/main/java/io/papermc/hangar/controller/internal/HangarUserController.java index e0c2ce73..a9290882 100644 --- a/backend/src/main/java/io/papermc/hangar/controller/internal/HangarUserController.java +++ b/backend/src/main/java/io/papermc/hangar/controller/internal/HangarUserController.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.papermc.hangar.HangarComponent; import io.papermc.hangar.components.images.service.AvatarService; +import io.papermc.hangar.db.customtypes.JSONB; import io.papermc.hangar.exceptions.HangarApiException; import io.papermc.hangar.model.api.PaginatedResult; import io.papermc.hangar.model.api.requests.RequestPagination; @@ -13,6 +14,7 @@ import io.papermc.hangar.model.common.roles.Role; import io.papermc.hangar.model.db.UserTable; import io.papermc.hangar.model.db.roles.ExtendedRoleTable; import io.papermc.hangar.model.internal.api.requests.StringContent; +import io.papermc.hangar.model.internal.api.requests.UserProfileSettings; import io.papermc.hangar.model.internal.api.requests.UserSettings; import io.papermc.hangar.model.internal.logs.LogAction; import io.papermc.hangar.model.internal.logs.contexts.UserContext; @@ -41,8 +43,12 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -68,6 +74,8 @@ import org.springframework.web.server.ResponseStatusException; @RequestMapping(path = "/api/internal", produces = MediaType.APPLICATION_JSON_VALUE, method = {RequestMethod.GET, RequestMethod.POST}) public class HangarUserController extends HangarComponent { + private static final Set ACCEPTED_SOCIAL_TYPES = Set.of("discord", "github"); + private final ObjectMapper mapper; private final UsersApiService usersApiService; private final UserService userService; @@ -208,6 +216,35 @@ public class HangarUserController extends HangarComponent { setThemeCookie(settings, response); } + // @el(userName: String) + @Unlocked + @CurrentUser("#userName") + @ResponseStatus(HttpStatus.OK) + @RateLimit(overdraft = 5, refillTokens = 2) + @PermissionRequired(NamedPermission.EDIT_OWN_USER_SETTINGS) + @PostMapping("/users/{userName}/settings/profile") + public void saveProfileSettings(@PathVariable final String userName, @RequestBody final UserProfileSettings settings) { + final UserTable userTable = this.userService.getUserTable(userName); + if (userTable == null) { + throw new HangarApiException(HttpStatus.NOT_FOUND); + } + + Map map = new HashMap<>(); + for (final String[] social : settings.socials()) { + if (social.length != 2) { + throw new HangarApiException("Badly formatted request, " + Arrays.toString(social) + " wasn't of length 2!"); + } + if (!ACCEPTED_SOCIAL_TYPES.contains(social[0])) { + throw new HangarApiException("Badly formatted request, social type " + social[0] + " is unknown!"); + } + map.put(social[0], social[1]); + } + userTable.setSocials(new JSONB(map)); + userTable.setTagline(settings.tagline()); + // TODO user action logging + this.userService.updateUser(userTable); + } + @Anyone @ResponseStatus(HttpStatus.OK) @RateLimit(overdraft = 5, refillTokens = 2) diff --git a/backend/src/main/java/io/papermc/hangar/model/internal/api/requests/UserProfileSettings.java b/backend/src/main/java/io/papermc/hangar/model/internal/api/requests/UserProfileSettings.java new file mode 100644 index 00000000..6fdc85a2 --- /dev/null +++ b/backend/src/main/java/io/papermc/hangar/model/internal/api/requests/UserProfileSettings.java @@ -0,0 +1,3 @@ +package io.papermc.hangar.model.internal.api.requests; + +public record UserProfileSettings(String tagline, String[][] socials) {} diff --git a/frontend/src/pages/auth/settings/[...slug].vue b/frontend/src/pages/auth/settings/[...slug].vue index ab5d6865..978b940c 100644 --- a/frontend/src/pages/auth/settings/[...slug].vue +++ b/frontend/src/pages/auth/settings/[...slug].vue @@ -5,6 +5,7 @@ import { reactive, ref, watch } from "vue"; import * as webauthnJson from "@github/webauthn-json"; import { AuthSettings } from "hangar-internal"; import { useI18n } from "vue-i18n"; +import { useVuelidate } from "@vuelidate/core"; import { useSeo } from "~/composables/useSeo"; import { useAuthStore } from "~/store/auth"; import Button from "~/components/design/Button.vue"; @@ -32,6 +33,7 @@ const router = useRouter(); const auth = useAuthStore(); const notification = useNotificationStore(); const { t } = useI18n(); +const v = useVuelidate(); const settings = await useAuthSettings(); @@ -248,10 +250,12 @@ function removeLink(idx: number) { profileForm.socials.splice(idx, 1); } -function saveProfile() { +async function saveProfile() { + if (!(await v.value.$validate())) return; loading.value = true; try { - // todo saveProfile + await useInternalApi("users/" + auth.user?.name + "/settings/profile", "POST", profileForm); + notification.success("Saved!"); } catch (e) { notification.error(e); }