Skip to content

Commit 079a1e2

Browse files
fusharclaude
andcommitted
Admin: use username instead of userJid in user view URL (#883)
Change the admin user view page URL from `/admin/users/JIDUSER123` to `/admin/users/andi` for human-readable URLs. - Add `GET /api/v2/users/username/{username}` backend endpoint - Add corresponding feign client method - Update frontend route, query, and components to use username param Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1be5db1 commit 079a1e2

File tree

8 files changed

+41
-13
lines changed

8 files changed

+41
-13
lines changed

judgels-backends/judgels-server-app/src/main/java/judgels/jophiel/user/UserResource.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,20 @@ public User getUser(
168168
return checkFound(userStore.getUserByJid(userJid));
169169
}
170170

171+
@GET
172+
@Path("/username/{username}")
173+
@Produces(APPLICATION_JSON)
174+
@UnitOfWork(readOnly = true)
175+
public User getUserByUsername(
176+
@HeaderParam(AUTHORIZATION) AuthHeader authHeader,
177+
@PathParam("username") String username) {
178+
179+
String actorJid = actorChecker.check(authHeader);
180+
checkAllowed(roleChecker.canAdminister(actorJid));
181+
182+
return checkFound(userStore.getUserByUsername(username));
183+
}
184+
171185
@GET
172186
@Path("/me")
173187
@Produces(APPLICATION_JSON)

judgels-backends/judgels-server-feign/src/main/java/judgels/jophiel/UserClient.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ public interface UserClient {
2626
@Headers("Authorization: Bearer {token}")
2727
User getUser(@Param("token") String token, @Param("userJid") String userJid);
2828

29+
@RequestLine("GET /api/v2/users/username/{username}")
30+
@Headers("Authorization: Bearer {token}")
31+
User getUserByUsername(@Param("token") String token, @Param("username") String username);
32+
2933
@RequestLine("GET /api/v2/users/me")
3034
@Headers("Authorization: Bearer {token}")
3135
User getMyself(@Param("token") String token);

judgels-client/src/modules/api/jophiel/user.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export const userAPI = {
1414
return get(`${baseUsersURL}/${userJid}`, token);
1515
},
1616

17+
getUserByUsername: (token, username) => {
18+
return get(`${baseUsersURL}/username/${username}`, token);
19+
},
20+
1721
getMyself: token => {
1822
return get(`${baseUsersURL}/me`, token);
1923
},

judgels-client/src/modules/queries/user.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ export const userQueryOptions = userJid =>
1010
queryFn: () => userAPI.getUser(getToken(), userJid),
1111
});
1212

13+
export const userByUsernameQueryOptions = username =>
14+
queryOptions({
15+
queryKey: ['user', 'username', username],
16+
queryFn: () => userAPI.getUserByUsername(getToken(), username),
17+
});
18+
1319
export const usersQueryOptions = params => {
1420
const { page, orderBy, orderDir } = params || {};
1521
return queryOptions({

judgels-client/src/routes/admin/routes.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Navigate, createRoute, lazyRouteComponent } from '@tanstack/react-route
22

33
import { isTLX } from '../../conf';
44
import { retryImport } from '../../lazy';
5-
import { userQueryOptions } from '../../modules/queries/user';
5+
import { userByUsernameQueryOptions } from '../../modules/queries/user';
66
import { userInfoQueryOptions } from '../../modules/queries/userInfo';
77
import { queryClient } from '../../modules/queryClient';
88
import { createDocumentTitle } from '../../utils/title';
@@ -33,11 +33,11 @@ export const createAdminRoutes = appRoute => {
3333

3434
const adminUserViewRoute = createRoute({
3535
getParentRoute: () => adminRoute,
36-
path: 'users/$userJid',
36+
path: 'users/$username',
3737
component: lazyRouteComponent(retryImport(() => import('./users/UserViewPage/UserViewPage'))),
38-
loader: async ({ params: { userJid } }) => {
39-
await queryClient.ensureQueryData(userQueryOptions(userJid));
40-
await queryClient.ensureQueryData(userInfoQueryOptions(userJid));
38+
loader: async ({ params: { username } }) => {
39+
const user = await queryClient.ensureQueryData(userByUsernameQueryOptions(username));
40+
await queryClient.ensureQueryData(userInfoQueryOptions(user.jid));
4141
},
4242
});
4343

judgels-client/src/routes/admin/users/UserViewPage/UserViewPage.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ import { useState } from 'react';
77

88
import { ContentCard } from '../../../../components/ContentCard/ContentCard';
99
import { FormTable } from '../../../../components/forms/FormTable/FormTable';
10-
import { userQueryOptions } from '../../../../modules/queries/user';
10+
import { userByUsernameQueryOptions } from '../../../../modules/queries/user';
1111
import { updateUserInfoMutationOptions, userInfoQueryOptions } from '../../../../modules/queries/userInfo';
1212
import UserEditInfoForm from '../UserEditInfoForm/UserEditInfoForm';
1313

1414
import * as toastActions from '../../../../modules/toast/toastActions';
1515

1616
export default function UserViewPage() {
17-
const { userJid } = useParams({ strict: false });
17+
const { username } = useParams({ strict: false });
1818

19-
const { data: user } = useSuspenseQuery(userQueryOptions(userJid));
20-
const { data: userInfo } = useSuspenseQuery(userInfoQueryOptions(userJid));
19+
const { data: user } = useSuspenseQuery(userByUsernameQueryOptions(username));
20+
const { data: userInfo } = useSuspenseQuery(userInfoQueryOptions(user.jid));
2121

22-
const updateUserInfoMutation = useMutation(updateUserInfoMutationOptions(userJid));
22+
const updateUserInfoMutation = useMutation(updateUserInfoMutationOptions(user.jid));
2323

2424
const [isEditingInfo, setIsEditingInfo] = useState(false);
2525

judgels-client/src/routes/admin/users/UserViewPage/UserViewPage.test.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe('UserViewPage', () => {
1414
});
1515

1616
const renderComponent = async () => {
17-
nockJophiel().get('/users/JIDUSER123').reply(200, {
17+
nockJophiel().get('/users/username/andi').reply(200, {
1818
jid: 'JIDUSER123',
1919
username: 'andi',
2020
email: 'andi@example.com',
@@ -29,7 +29,7 @@ describe('UserViewPage', () => {
2929
await act(async () =>
3030
render(
3131
<QueryClientProviderWrapper>
32-
<TestRouter initialEntries={['/admin/users/JIDUSER123']} path="/admin/users/$userJid">
32+
<TestRouter initialEntries={['/admin/users/andi']} path="/admin/users/$username">
3333
<UserViewPage />
3434
</TestRouter>
3535
</QueryClientProviderWrapper>

judgels-client/src/routes/admin/users/UsersTable/UsersTable.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export function UsersTable({ users, lastSessionTimesMap }) {
77
const rows = users.map(user => (
88
<tr key={user.jid}>
99
<td>
10-
<Link to={`/admin/users/${user.jid}`}>{user.username}</Link>
10+
<Link to={`/admin/users/${user.username}`}>{user.username}</Link>
1111
</td>
1212
<td>{user.email}</td>
1313
<td>{lastSessionTimesMap[user.jid] ? <FormattedDate value={lastSessionTimesMap[user.jid]} /> : '-'}</td>

0 commit comments

Comments
 (0)