mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-03-13 15:39:18 +08:00
user prompts
This commit is contained in:
parent
bf9d694884
commit
2d2d257bd8
59
frontend/components/users/Prompt.vue
Normal file
59
frontend/components/users/Prompt.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<v-menu v-model="open" :close-on-click="false" offset-x :close-on-content-click="false">
|
||||
<template #activator="props">
|
||||
<slot name="activator" v-bind="props" />
|
||||
</template>
|
||||
<v-card max-width="300" color="info" light>
|
||||
<v-card-title>{{ $t(promptInfo.titleKey) }}</v-card-title>
|
||||
<v-card-text>{{ $t(promptInfo.messageKey) }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn small :loading="loading" @click.stop="markAsRead">
|
||||
<v-icon color="success" left>mdi-check-circle</v-icon>
|
||||
{{ $t('prompts.confirm') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop } from 'nuxt-property-decorator';
|
||||
import { IPrompt } from 'hangar-internal';
|
||||
import { PropType } from 'vue';
|
||||
import { RootState } from '~/store';
|
||||
import { Prompt as PromptEnum } from '~/types/enums';
|
||||
import { HangarComponent } from '~/components/mixins';
|
||||
|
||||
@Component
|
||||
export default class Prompt extends HangarComponent {
|
||||
open = false;
|
||||
loading = false;
|
||||
|
||||
@Prop({ type: String as PropType<PromptEnum>, required: true })
|
||||
prompt!: PromptEnum;
|
||||
|
||||
created() {
|
||||
this.open = !this.currentUser!.readPrompts.includes(this.promptInfo.ordinal);
|
||||
}
|
||||
|
||||
get promptInfo(): IPrompt {
|
||||
return (this.$store.state as RootState).prompts.get(this.prompt)!;
|
||||
}
|
||||
|
||||
markAsRead() {
|
||||
this.loading = true;
|
||||
this.$api
|
||||
.requestInternal(`read-prompt/${this.promptInfo.ordinal}`, true, 'post')
|
||||
.then(() => {
|
||||
this.open = false;
|
||||
this.$auth.refreshUser();
|
||||
})
|
||||
.catch(this.$util.handleRequestError)
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -742,6 +742,13 @@ const msgs: LocaleMessageObject = {
|
||||
invalidFormat: '{0} is invalid',
|
||||
invalidUrl: 'Invalid URL format',
|
||||
},
|
||||
prompts: {
|
||||
confirm: 'Got it!',
|
||||
changeAvatar: {
|
||||
title: 'Change your avatar!',
|
||||
message: "Welcome to your new organization! Start by changing it's avatar by clicking on it.",
|
||||
},
|
||||
},
|
||||
error: {
|
||||
userLocked: 'Your account is locked.',
|
||||
401: 'You must be logged in for this',
|
||||
|
@ -4,14 +4,18 @@
|
||||
<v-col class="flex-grow-0">
|
||||
<div style="position: relative" class="mt-2">
|
||||
<UserAvatar :username="user.name" :avatar-url="$util.avatarUrl(user.name)" :clazz="avatarClazz">
|
||||
<v-tooltip left>
|
||||
<template #activator="{ on }">
|
||||
<v-btn fab x-small color="warning" absolute style="right: -16px; top: -16px" v-on="on" @click.stop="changeAvatar">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<Prompt v-if="user.isOrganization" prompt="CHANGE_AVATAR">
|
||||
<template #activator>
|
||||
<v-tooltip left>
|
||||
<template #activator="{ on }">
|
||||
<v-btn fab x-small color="warning" absolute style="right: -16px; top: -16px" v-on="on" @click.stop="changeAvatar">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>{{ $t('author.org.editAvatar') }}</span>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
<span>{{ $t('author.org.editAvatar') }}</span>
|
||||
</v-tooltip>
|
||||
</Prompt>
|
||||
</UserAvatar>
|
||||
</div>
|
||||
</v-col>
|
||||
@ -90,6 +94,7 @@ import { Organization } from 'hangar-internal';
|
||||
import UserAvatar from '../components/users/UserAvatar.vue';
|
||||
import HangarModal from '~/components/modals/HangarModal.vue';
|
||||
import { UserPage } from '~/components/mixins';
|
||||
import Prompt from '~/components/users/Prompt.vue';
|
||||
|
||||
interface Button {
|
||||
icon: string;
|
||||
@ -100,7 +105,7 @@ interface Button {
|
||||
}
|
||||
|
||||
@Component({
|
||||
components: { HangarModal, UserAvatar },
|
||||
components: { Prompt, HangarModal, UserAvatar },
|
||||
})
|
||||
export default class UserParentPage extends UserPage {
|
||||
taglineForm: string | null = null;
|
||||
|
1
frontend/types/internal/data.d.ts
vendored
1
frontend/types/internal/data.d.ts
vendored
@ -40,6 +40,7 @@ declare module 'hangar-internal' {
|
||||
}
|
||||
|
||||
interface IPrompt {
|
||||
ordinal: number;
|
||||
name: Prompt;
|
||||
titleKey: string;
|
||||
messageKey: string;
|
||||
|
@ -6,6 +6,7 @@ import io.papermc.hangar.controller.HangarController;
|
||||
import io.papermc.hangar.exceptions.HangarApiException;
|
||||
import io.papermc.hangar.model.common.NamedPermission;
|
||||
import io.papermc.hangar.model.common.Permission;
|
||||
import io.papermc.hangar.model.common.Prompt;
|
||||
import io.papermc.hangar.model.common.roles.Role;
|
||||
import io.papermc.hangar.model.db.UserTable;
|
||||
import io.papermc.hangar.model.db.roles.ExtendedRoleTable;
|
||||
@ -15,6 +16,7 @@ import io.papermc.hangar.model.internal.logs.contexts.UserContext;
|
||||
import io.papermc.hangar.model.internal.user.HangarUser;
|
||||
import io.papermc.hangar.model.internal.user.notifications.HangarInvite.InviteType;
|
||||
import io.papermc.hangar.model.internal.user.notifications.HangarNotification;
|
||||
import io.papermc.hangar.security.annotations.LoggedIn;
|
||||
import io.papermc.hangar.security.annotations.permission.PermissionRequired;
|
||||
import io.papermc.hangar.security.annotations.unlocked.Unlocked;
|
||||
import io.papermc.hangar.security.authentication.HangarAuthenticationToken;
|
||||
@ -31,7 +33,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@ -45,7 +46,7 @@ import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@Secured("ROLE_USER")
|
||||
@LoggedIn
|
||||
@RequestMapping(path = "/api/internal", produces = MediaType.APPLICATION_JSON_VALUE, method = { RequestMethod.GET, RequestMethod.POST })
|
||||
public class HangarUserController extends HangarController {
|
||||
|
||||
@ -136,6 +137,13 @@ public class HangarUserController extends HangarController {
|
||||
return ResponseEntity.ok(invites);
|
||||
}
|
||||
|
||||
@Unlocked
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@PostMapping("/read-prompt/{prompt}")
|
||||
public void readPrompt(@PathVariable Prompt prompt) {
|
||||
userService.markPromptRead(prompt);
|
||||
}
|
||||
|
||||
@Unlocked
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@PostMapping("/invites/project/{id}/{status}")
|
||||
|
@ -1,42 +0,0 @@
|
||||
package io.papermc.hangar.controllerold;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import io.papermc.hangar.model.common.NamedPermission;
|
||||
import io.papermc.hangar.securityold.annotations.OrganizationPermission;
|
||||
import io.papermc.hangar.serviceold.AuthenticationService;
|
||||
import io.papermc.hangar.util.AlertUtil;
|
||||
import io.papermc.hangar.util.AlertUtil.AlertType;
|
||||
import io.papermc.hangar.util.Routes;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
@Controller("oldOrganizationController")
|
||||
public class OrganizationController extends HangarController {
|
||||
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
@Autowired
|
||||
public OrganizationController(AuthenticationService authenticationService) {
|
||||
this.authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
@OrganizationPermission(NamedPermission.EDIT_SUBJECT_SETTINGS)
|
||||
@Secured("ROLE_USER")
|
||||
@GetMapping("/organizations/{organization}/settings/avatar")
|
||||
public ModelAndView updateAvatar(@PathVariable String organization) {
|
||||
try {
|
||||
URI uri = authenticationService.changeAvatarUri(getCurrentUser().getName(), organization);
|
||||
return Routes.getRedirectToUrl(uri.toString());
|
||||
} catch (JsonProcessingException e) {
|
||||
ModelAndView mav = Routes.USERS_SHOW_PROJECTS.getRedirect(organization);
|
||||
AlertUtil.showAlert(mav, AlertType.ERROR, "organization.avatarFailed");
|
||||
return mav;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ package io.papermc.hangar.controllerold;
|
||||
import io.papermc.hangar.config.hangar.HangarConfig;
|
||||
import io.papermc.hangar.db.modelold.UsersTable;
|
||||
import io.papermc.hangar.model.common.NamedPermission;
|
||||
import io.papermc.hangar.model.common.Prompt;
|
||||
import io.papermc.hangar.model.internal.sso.AuthUser;
|
||||
import io.papermc.hangar.securityold.annotations.GlobalPermission;
|
||||
import io.papermc.hangar.service.internal.auth.SSOService;
|
||||
@ -16,9 +15,7 @@ import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
@ -37,16 +34,6 @@ public class UsersController extends HangarController {
|
||||
this.ssoService = ssoService;
|
||||
}
|
||||
|
||||
@Secured("ROLE_USER")
|
||||
@PostMapping("/prompts/read/{id}")
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public void markPromptRead(@PathVariable("id") Prompt prompt) {
|
||||
if (prompt == null) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid prompt id");
|
||||
}
|
||||
userService.markPromptAsRead(prompt);
|
||||
}
|
||||
|
||||
@GlobalPermission(NamedPermission.EDIT_OWN_USER_SETTINGS)
|
||||
@Secured("ROLE_USER")
|
||||
@GetMapping("/{user}/settings/lock/{locked}")
|
||||
|
@ -5,7 +5,7 @@ import com.fasterxml.jackson.annotation.JsonFormat.Shape;
|
||||
|
||||
@JsonFormat(shape = Shape.OBJECT)
|
||||
public enum Prompt {
|
||||
CHANGE_AVATAR("prompt.changeAvatar.title", "prompt.changeAvatar.message");
|
||||
CHANGE_AVATAR("prompts.changeAvatar.title", "prompts.changeAvatar.message");
|
||||
|
||||
final String titleKey;
|
||||
final String messageKey;
|
||||
@ -15,6 +15,8 @@ public enum Prompt {
|
||||
this.messageKey = messageKey;
|
||||
}
|
||||
|
||||
public int getOrdinal() { return ordinal(); }
|
||||
|
||||
public String getName() { return name(); }
|
||||
|
||||
public String getTitleKey() {
|
||||
|
@ -3,6 +3,7 @@ package io.papermc.hangar.service.internal.users;
|
||||
import io.papermc.hangar.db.dao.HangarDao;
|
||||
import io.papermc.hangar.db.dao.internal.HangarUsersDAO;
|
||||
import io.papermc.hangar.db.dao.internal.table.UserDAO;
|
||||
import io.papermc.hangar.model.common.Prompt;
|
||||
import io.papermc.hangar.model.common.roles.GlobalRole;
|
||||
import io.papermc.hangar.model.db.UserTable;
|
||||
import io.papermc.hangar.model.internal.sso.AuthUser;
|
||||
@ -61,6 +62,14 @@ public class UserService extends HangarService {
|
||||
}
|
||||
}
|
||||
|
||||
public void markPromptRead(Prompt prompt) {
|
||||
UserTable userTable = userDAO.getUserTable(getHangarPrincipal().getId());
|
||||
if (!userTable.getReadPrompts().contains(prompt.ordinal())) {
|
||||
userTable.getReadPrompts().add(prompt.ordinal());
|
||||
userDAO.update(userTable);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateUser(UserTable userTable) {
|
||||
userDAO.update(userTable);
|
||||
}
|
||||
|
@ -1,30 +1,17 @@
|
||||
package io.papermc.hangar.serviceold;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import io.papermc.hangar.config.hangar.HangarConfig;
|
||||
import io.papermc.hangar.db.dao.HangarDao;
|
||||
import io.papermc.hangar.db.daoold.ApiKeyDao;
|
||||
import io.papermc.hangar.db.daoold.api.SessionsDao;
|
||||
import io.papermc.hangar.db.modelold.UsersTable;
|
||||
import io.papermc.hangar.model.internal.ChangeAvatarToken;
|
||||
import io.papermc.hangar.modelold.ApiAuthInfo;
|
||||
import io.papermc.hangar.util.AuthUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.context.annotation.RequestScope;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
@ -35,11 +22,8 @@ import java.util.regex.Pattern;
|
||||
public class AuthenticationService extends HangarService {
|
||||
|
||||
|
||||
private final HangarConfig hangarConfig;
|
||||
private final HangarDao<SessionsDao> sessionsDao;
|
||||
private final HangarDao<ApiKeyDao> apiKeyDao;
|
||||
private final RestTemplate restTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
@ -48,12 +32,9 @@ public class AuthenticationService extends HangarService {
|
||||
private static final Pattern API_KEY_PATTERN = Pattern.compile("(" + UUID_REGEX + ").(" + UUID_REGEX + ")");
|
||||
|
||||
@Autowired
|
||||
public AuthenticationService(HangarConfig hangarConfig, HangarDao<SessionsDao> sessionsDao, HangarDao<ApiKeyDao> apiKeyDao, RestTemplate restTemplate, ObjectMapper objectMapper, HttpServletRequest request, Supplier<Optional<UsersTable>> currentUser) {
|
||||
this.hangarConfig = hangarConfig;
|
||||
public AuthenticationService(HangarDao<SessionsDao> sessionsDao, HangarDao<ApiKeyDao> apiKeyDao, HttpServletRequest request, Supplier<Optional<UsersTable>> currentUser) {
|
||||
this.sessionsDao = sessionsDao;
|
||||
this.apiKeyDao = apiKeyDao;
|
||||
this.restTemplate = restTemplate;
|
||||
this.objectMapper = objectMapper;
|
||||
this.request = request;
|
||||
this.currentUser = currentUser;
|
||||
}
|
||||
@ -82,22 +63,4 @@ public class AuthenticationService extends HangarService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public URI changeAvatarUri(String requester, String organization) throws JsonProcessingException {
|
||||
ChangeAvatarToken token = getChangeAvatarToken(requester, organization);
|
||||
UriComponentsBuilder uriComponents = UriComponentsBuilder.fromHttpUrl(hangarConfig.getAuthUrl());
|
||||
uriComponents.path("/accounts/user/{organization}/change-avatar/").queryParam("key", token.getSignedData());
|
||||
return uriComponents.build(organization);
|
||||
}
|
||||
|
||||
public ChangeAvatarToken getChangeAvatarToken(String requester, String organization) throws JsonProcessingException {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
MultiValueMap<String, String> bodyMap = new LinkedMultiValueMap<>();
|
||||
bodyMap.add("api-key", hangarConfig.sso.getApiKey());
|
||||
bodyMap.add("request_username", requester);
|
||||
ChangeAvatarToken token;
|
||||
token = objectMapper.treeToValue(restTemplate.postForObject(hangarConfig.security.api.getUrl() + "/api/users/" + organization + "/change-avatar-token/", new HttpEntity<>(bodyMap, headers), ObjectNode.class), ChangeAvatarToken.class);
|
||||
return token;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import io.papermc.hangar.db.modelold.OrganizationsTable;
|
||||
import io.papermc.hangar.db.modelold.UserOrganizationRolesTable;
|
||||
import io.papermc.hangar.db.modelold.UsersTable;
|
||||
import io.papermc.hangar.model.common.Permission;
|
||||
import io.papermc.hangar.model.common.Prompt;
|
||||
import io.papermc.hangar.modelold.Role;
|
||||
import io.papermc.hangar.modelold.viewhelpers.FlagActivity;
|
||||
import io.papermc.hangar.modelold.viewhelpers.HeaderData;
|
||||
@ -170,13 +169,4 @@ public class UserService extends HangarService {
|
||||
return userDao.get().getFlagActivity(username);
|
||||
}
|
||||
|
||||
public void markPromptAsRead(Prompt prompt) {
|
||||
Optional<UsersTable> currUser = currentUser.get();
|
||||
if (currUser.isPresent()) {
|
||||
if (!currUser.get().getReadPrompts().contains(prompt.ordinal())) {
|
||||
currUser.get().getReadPrompts().add(prompt.ordinal());
|
||||
}
|
||||
userDao.get().update(currUser.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user