mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-11-21 01:21:54 +08:00
Show possible alts of user
This commit is contained in:
parent
4ee03c7c9f
commit
c3c98d7326
@ -7,6 +7,7 @@ import io.papermc.hangar.components.auth.service.CredentialsService;
|
||||
import io.papermc.hangar.components.auth.service.TokenService;
|
||||
import io.papermc.hangar.components.images.service.AvatarService;
|
||||
import io.papermc.hangar.db.customtypes.JSONB;
|
||||
import io.papermc.hangar.db.dao.internal.table.stats.ProjectViewsDAO;
|
||||
import io.papermc.hangar.exceptions.HangarApiException;
|
||||
import io.papermc.hangar.model.api.PaginatedResult;
|
||||
import io.papermc.hangar.model.api.requests.RequestPagination;
|
||||
@ -90,9 +91,10 @@ public class HangarUserController extends HangarComponent {
|
||||
private final TokenService tokenService;
|
||||
private final AvatarService avatarService;
|
||||
private final CredentialsService credentialsService;
|
||||
private final ProjectViewsDAO projectViewsDAO;
|
||||
|
||||
@Autowired
|
||||
public HangarUserController(final ObjectMapper mapper, final UsersApiService usersApiService, final UserService userService, final NotificationService notificationService, final ProjectRoleService projectRoleService, final OrganizationService organizationService, final OrganizationRoleService organizationRoleService, final ProjectInviteService projectInviteService, final OrganizationInviteService organizationInviteService, final TokenService tokenService, final AvatarService avatarService, final CredentialsService credentialsService) {
|
||||
public HangarUserController(final ObjectMapper mapper, final UsersApiService usersApiService, final UserService userService, final NotificationService notificationService, final ProjectRoleService projectRoleService, final OrganizationService organizationService, final OrganizationRoleService organizationRoleService, final ProjectInviteService projectInviteService, final OrganizationInviteService organizationInviteService, final TokenService tokenService, final AvatarService avatarService, final CredentialsService credentialsService, final ProjectViewsDAO projectViewsDAO) {
|
||||
this.mapper = mapper;
|
||||
this.usersApiService = usersApiService;
|
||||
this.userService = userService;
|
||||
@ -105,6 +107,7 @@ public class HangarUserController extends HangarComponent {
|
||||
this.tokenService = tokenService;
|
||||
this.avatarService = avatarService;
|
||||
this.credentialsService = credentialsService;
|
||||
this.projectViewsDAO = projectViewsDAO;
|
||||
}
|
||||
|
||||
@Anyone
|
||||
@ -138,6 +141,18 @@ public class HangarUserController extends HangarComponent {
|
||||
return ResponseEntity.ok(user);
|
||||
}
|
||||
|
||||
@Unlocked
|
||||
@PermissionRequired(NamedPermission.IS_STAFF)
|
||||
@ResponseBody
|
||||
@GetMapping(value = "/users/{userName}/alts", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public List<String> possibleAltAccounts(@PathVariable final String userName) {
|
||||
final UserTable table = this.userService.getUserTable(userName);
|
||||
if (table == null) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown org " + userName);
|
||||
}
|
||||
return this.projectViewsDAO.getUsersSharingAddressWith(table.getUserId());
|
||||
}
|
||||
|
||||
// @el(userName: String)
|
||||
@Unlocked
|
||||
@CurrentUser("#userName")
|
||||
|
@ -2,6 +2,7 @@ package io.papermc.hangar.db.dao.internal.table.stats;
|
||||
|
||||
import io.papermc.hangar.model.db.stats.ProjectViewIndividualTable;
|
||||
import java.net.InetAddress;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
|
||||
import org.jdbi.v3.sqlobject.customizer.BindBean;
|
||||
@ -20,4 +21,15 @@ public interface ProjectViewsDAO {
|
||||
|
||||
@SqlQuery("SELECT * FROM project_views_individual WHERE address = :address OR (:userId IS NOT NULL AND user_id = :userId) LIMIT 1")
|
||||
Optional<ProjectViewIndividualTable> getIndividualView(Long userId, InetAddress address);
|
||||
|
||||
@SqlQuery("""
|
||||
SELECT DISTINCT ON (u.id) u.name
|
||||
FROM users u
|
||||
JOIN project_views_individual pvi ON u.id = pvi.user_id
|
||||
WHERE pvi.address IN (
|
||||
SELECT address FROM project_views_individual WHERE user_id = :userId
|
||||
)
|
||||
AND pvi.user_id != :userId;
|
||||
""")
|
||||
List<String> getUsersSharingAddressWith(long userId);
|
||||
}
|
||||
|
@ -22,9 +22,10 @@ import { AsyncData } from "nuxt/app";
|
||||
import { ComputedRef, Ref } from "vue";
|
||||
import { NuxtApp } from "@nuxt/schema";
|
||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||
import { ref, useAsyncData, createError } from "#imports";
|
||||
import { createError, hasPerms, ref, useAsyncData } from "#imports";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useAuthStore } from "~/store/auth";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
|
||||
export type NonNullAsyncData<T, E = unknown> = { data: Ref<T> } & Pick<AsyncData<T, E>, "pending" | "refresh" | "execute" | "error">;
|
||||
|
||||
@ -182,6 +183,7 @@ export async function useUserData(
|
||||
organizations: { [key: string]: OrganizationRoleTable };
|
||||
pinned: ProjectCompact[];
|
||||
organizationVisibility: { [key: string]: boolean } | null;
|
||||
possibleAlts: string[] | null;
|
||||
} | null>
|
||||
> {
|
||||
const self = user === useAuthStore().user?.name;
|
||||
@ -198,6 +200,7 @@ export async function useUserData(
|
||||
useInternalApi<{ [key: string]: RoleTable }>(`organizations/${user}/userOrganizations`),
|
||||
useApi<ProjectCompact[]>(`users/${user}/pinned`),
|
||||
self ? useOrgVisibility(user) : null,
|
||||
hasPerms(NamedPermission.IS_STAFF) ? useInternalApi<string[]>(`users/${user}/alts`) : null,
|
||||
]);
|
||||
return {
|
||||
starred: data[0] as PaginatedResult<ProjectCompact>,
|
||||
@ -206,6 +209,7 @@ export async function useUserData(
|
||||
organizations: data[3] as { [key: string]: OrganizationRoleTable },
|
||||
pinned: data[4] as ProjectCompact[],
|
||||
organizationVisibility: data[5],
|
||||
possibleAlts: data[6],
|
||||
};
|
||||
})
|
||||
);
|
||||
|
@ -71,7 +71,7 @@ const requestParams = computed(() => {
|
||||
});
|
||||
|
||||
const userData = await useUserData(props.user.name, requestParams.value);
|
||||
const { starred, watching, organizations, pinned, organizationVisibility } = userData.value || { starred: null };
|
||||
const { starred, watching, organizations, pinned, organizationVisibility, possibleAlts } = userData.value || { starred: null };
|
||||
const projects = ref(userData.value?.projects);
|
||||
const orgRoles = useBackendData.orgRoles.filter((role) => role.assignable);
|
||||
const authStore = useAuthStore();
|
||||
@ -167,6 +167,17 @@ useHead(useSeo(props.user.name, description, route, props.user.avatarUrl));
|
||||
<LockUserModal v-if="!isCurrentUser && hasPerms(NamedPermission.IS_STAFF)" :user="user" />
|
||||
</Card>
|
||||
|
||||
<Card v-if="possibleAlts?.length !== 0" class="mb-4">
|
||||
<template #header> Shares address with </template>
|
||||
<ul>
|
||||
<li v-for="name in possibleAlts" :key="name">
|
||||
<Link :to="'/' + name">
|
||||
{{ name }}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<template v-if="!user.isOrganization && organizations">
|
||||
<Card class="mb-4" accent>
|
||||
<template #header>
|
||||
|
Loading…
Reference in New Issue
Block a user