user prompts

This commit is contained in:
Jake Potrebic 2021-04-05 16:35:53 -07:00
parent bf9d694884
commit 2d2d257bd8
No known key found for this signature in database
GPG Key ID: 7C58557EC9C421F8
11 changed files with 103 additions and 114 deletions

View 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>

View File

@ -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',

View File

@ -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;

View File

@ -40,6 +40,7 @@ declare module 'hangar-internal' {
}
interface IPrompt {
ordinal: number;
name: Prompt;
titleKey: string;
messageKey: string;

View File

@ -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}")

View File

@ -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;
}
}
}

View File

@ -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}")

View File

@ -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() {

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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());
}
}
}