1
0
forked from baron/baron-sso

style: apply backend go fmt and frontend biome auto-fixes

This commit is contained in:
2026-03-27 17:52:39 +09:00
parent 2383c6a6be
commit 5ae0e19e31
6 changed files with 142 additions and 61 deletions

View File

@@ -83,16 +83,27 @@ export function TenantAdminsAndOwnersTab() {
const addOwnerMutation = useMutation({ const addOwnerMutation = useMutation({
mutationFn: (userId: string) => addTenantOwner(tenantId, userId), mutationFn: (userId: string) => addTenantOwner(tenantId, userId),
onMutate: async (userId) => { onMutate: async (userId) => {
await queryClient.cancelQueries({ queryKey: ["tenant-owners", tenantId] }); await queryClient.cancelQueries({
const previousOwners = queryClient.getQueryData<any[]>(["tenant-owners", tenantId]); queryKey: ["tenant-owners", tenantId],
});
const previousOwners = queryClient.getQueryData<any[]>([
"tenant-owners",
tenantId,
]);
// Optimistically add to the list to prevent immediate double clicks // Optimistically add to the list to prevent immediate double clicks
const addedUser = searchResults.find(u => u.id === userId); const addedUser = searchResults.find((u) => u.id === userId);
if (addedUser) { if (addedUser) {
queryClient.setQueryData<any[]>(["tenant-owners", tenantId], old => { queryClient.setQueryData<any[]>(["tenant-owners", tenantId], (old) => {
if (!old) return [{ id: userId, name: addedUser.name, email: addedUser.email }]; if (!old)
if (old.some(o => o.id === userId)) return old; return [
return [...old, { id: userId, name: addedUser.name, email: addedUser.email }]; { id: userId, name: addedUser.name, email: addedUser.email },
];
if (old.some((o) => o.id === userId)) return old;
return [
...old,
{ id: userId, name: addedUser.name, email: addedUser.email },
];
}); });
} }
return { previousOwners }; return { previousOwners };
@@ -100,7 +111,9 @@ export function TenantAdminsAndOwnersTab() {
onSuccess: () => { onSuccess: () => {
// Delay invalidation slightly to give the backend outbox time to process // Delay invalidation slightly to give the backend outbox time to process
setTimeout(() => { setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["tenant-owners", tenantId] }); queryClient.invalidateQueries({
queryKey: ["tenant-owners", tenantId],
});
}, 1000); }, 1000);
toast.success( toast.success(
t("msg.admin.tenants.owners.add_success", "소유자가 추가되었습니다."), t("msg.admin.tenants.owners.add_success", "소유자가 추가되었습니다."),
@@ -109,7 +122,10 @@ export function TenantAdminsAndOwnersTab() {
}, },
onError: (err: AxiosError<{ error?: string }>, userId, context) => { onError: (err: AxiosError<{ error?: string }>, userId, context) => {
if (context?.previousOwners) { if (context?.previousOwners) {
queryClient.setQueryData(["tenant-owners", tenantId], context.previousOwners); queryClient.setQueryData(
["tenant-owners", tenantId],
context.previousOwners,
);
} }
toast.error( toast.error(
err.response?.data?.error || err.response?.data?.error ||
@@ -121,16 +137,23 @@ export function TenantAdminsAndOwnersTab() {
const removeOwnerMutation = useMutation({ const removeOwnerMutation = useMutation({
mutationFn: (userId: string) => removeTenantOwner(tenantId, userId), mutationFn: (userId: string) => removeTenantOwner(tenantId, userId),
onMutate: async (userId) => { onMutate: async (userId) => {
await queryClient.cancelQueries({ queryKey: ["tenant-owners", tenantId] }); await queryClient.cancelQueries({
const previousOwners = queryClient.getQueryData<any[]>(["tenant-owners", tenantId]); queryKey: ["tenant-owners", tenantId],
queryClient.setQueryData<any[]>(["tenant-owners", tenantId], old => });
old ? old.filter(o => o.id !== userId) : [] const previousOwners = queryClient.getQueryData<any[]>([
"tenant-owners",
tenantId,
]);
queryClient.setQueryData<any[]>(["tenant-owners", tenantId], (old) =>
old ? old.filter((o) => o.id !== userId) : [],
); );
return { previousOwners }; return { previousOwners };
}, },
onSuccess: () => { onSuccess: () => {
setTimeout(() => { setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["tenant-owners", tenantId] }); queryClient.invalidateQueries({
queryKey: ["tenant-owners", tenantId],
});
}, 1000); }, 1000);
toast.success( toast.success(
t( t(
@@ -141,7 +164,10 @@ export function TenantAdminsAndOwnersTab() {
}, },
onError: (err: AxiosError<{ error?: string }>, userId, context) => { onError: (err: AxiosError<{ error?: string }>, userId, context) => {
if (context?.previousOwners) { if (context?.previousOwners) {
queryClient.setQueryData(["tenant-owners", tenantId], context.previousOwners); queryClient.setQueryData(
["tenant-owners", tenantId],
context.previousOwners,
);
} }
toast.error( toast.error(
err.response?.data?.error || err.response?.data?.error ||
@@ -153,22 +179,35 @@ export function TenantAdminsAndOwnersTab() {
const addAdminMutation = useMutation({ const addAdminMutation = useMutation({
mutationFn: (userId: string) => addTenantAdmin(tenantId, userId), mutationFn: (userId: string) => addTenantAdmin(tenantId, userId),
onMutate: async (userId) => { onMutate: async (userId) => {
await queryClient.cancelQueries({ queryKey: ["tenant-admins", tenantId] }); await queryClient.cancelQueries({
const previousAdmins = queryClient.getQueryData<any[]>(["tenant-admins", tenantId]); queryKey: ["tenant-admins", tenantId],
});
const previousAdmins = queryClient.getQueryData<any[]>([
"tenant-admins",
tenantId,
]);
const addedUser = searchResults.find(u => u.id === userId); const addedUser = searchResults.find((u) => u.id === userId);
if (addedUser) { if (addedUser) {
queryClient.setQueryData<any[]>(["tenant-admins", tenantId], old => { queryClient.setQueryData<any[]>(["tenant-admins", tenantId], (old) => {
if (!old) return [{ id: userId, name: addedUser.name, email: addedUser.email }]; if (!old)
if (old.some(a => a.id === userId)) return old; return [
return [...old, { id: userId, name: addedUser.name, email: addedUser.email }]; { id: userId, name: addedUser.name, email: addedUser.email },
];
if (old.some((a) => a.id === userId)) return old;
return [
...old,
{ id: userId, name: addedUser.name, email: addedUser.email },
];
}); });
} }
return { previousAdmins }; return { previousAdmins };
}, },
onSuccess: () => { onSuccess: () => {
setTimeout(() => { setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["tenant-admins", tenantId] }); queryClient.invalidateQueries({
queryKey: ["tenant-admins", tenantId],
});
}, 1000); }, 1000);
toast.success( toast.success(
t("msg.admin.tenants.admins.add_success", "관리자가 추가되었습니다."), t("msg.admin.tenants.admins.add_success", "관리자가 추가되었습니다."),
@@ -177,7 +216,10 @@ export function TenantAdminsAndOwnersTab() {
}, },
onError: (err: AxiosError<{ error?: string }>, userId, context) => { onError: (err: AxiosError<{ error?: string }>, userId, context) => {
if (context?.previousAdmins) { if (context?.previousAdmins) {
queryClient.setQueryData(["tenant-admins", tenantId], context.previousAdmins); queryClient.setQueryData(
["tenant-admins", tenantId],
context.previousAdmins,
);
} }
toast.error( toast.error(
err.response?.data?.error || err.response?.data?.error ||
@@ -189,16 +231,23 @@ export function TenantAdminsAndOwnersTab() {
const removeAdminMutation = useMutation({ const removeAdminMutation = useMutation({
mutationFn: (userId: string) => removeTenantAdmin(tenantId, userId), mutationFn: (userId: string) => removeTenantAdmin(tenantId, userId),
onMutate: async (userId) => { onMutate: async (userId) => {
await queryClient.cancelQueries({ queryKey: ["tenant-admins", tenantId] }); await queryClient.cancelQueries({
const previousAdmins = queryClient.getQueryData<any[]>(["tenant-admins", tenantId]); queryKey: ["tenant-admins", tenantId],
queryClient.setQueryData<any[]>(["tenant-admins", tenantId], old => });
old ? old.filter(a => a.id !== userId) : [] const previousAdmins = queryClient.getQueryData<any[]>([
"tenant-admins",
tenantId,
]);
queryClient.setQueryData<any[]>(["tenant-admins", tenantId], (old) =>
old ? old.filter((a) => a.id !== userId) : [],
); );
return { previousAdmins }; return { previousAdmins };
}, },
onSuccess: () => { onSuccess: () => {
setTimeout(() => { setTimeout(() => {
queryClient.invalidateQueries({ queryKey: ["tenant-admins", tenantId] }); queryClient.invalidateQueries({
queryKey: ["tenant-admins", tenantId],
});
}, 1000); }, 1000);
toast.success( toast.success(
t("msg.admin.tenants.admins.remove_success", "권한이 회수되었습니다."), t("msg.admin.tenants.admins.remove_success", "권한이 회수되었습니다."),
@@ -206,7 +255,10 @@ export function TenantAdminsAndOwnersTab() {
}, },
onError: (err: AxiosError<{ error?: string }>, userId, context) => { onError: (err: AxiosError<{ error?: string }>, userId, context) => {
if (context?.previousAdmins) { if (context?.previousAdmins) {
queryClient.setQueryData(["tenant-admins", tenantId], context.previousAdmins); queryClient.setQueryData(
["tenant-admins", tenantId],
context.previousAdmins,
);
} }
toast.error( toast.error(
err.response?.data?.error || err.response?.data?.error ||

View File

@@ -161,7 +161,9 @@ function UserDetailPage() {
const [error, setError] = React.useState<string | null>(null); const [error, setError] = React.useState<string | null>(null);
const [successMsg, setSuccessMsg] = React.useState<string | null>(null); const [successMsg, setSuccessMsg] = React.useState<string | null>(null);
const [isPasswordResetOpen, setIsPasswordResetOpen] = React.useState(false); const [isPasswordResetOpen, setIsPasswordResetOpen] = React.useState(false);
const [generatedPassword, setGeneratedPassword] = React.useState<string | null>(null); const [generatedPassword, setGeneratedPassword] = React.useState<
string | null
>(null);
const { data: profile } = useQuery({ const { data: profile } = useQuery({
queryKey: ["me"], queryKey: ["me"],
@@ -470,7 +472,8 @@ function UserDetailPage() {
</div> </div>
)} )}
<p className="text-[10px] text-muted-foreground"> <p className="text-[10px] text-muted-foreground">
* . *
.
</p> </p>
</div> </div>
@@ -571,7 +574,10 @@ function UserDetailPage() {
{...register("role")} {...register("role")}
> >
<option value="user"> <option value="user">
{t("ui.admin.users.detail.form.role_user", "일반 사용자")} {t(
"ui.admin.users.detail.form.role_user",
"일반 사용자",
)}
</option> </option>
<option value="tenant_admin"> <option value="tenant_admin">
{t( {t(
@@ -601,7 +607,10 @@ function UserDetailPage() {
{t("ui.admin.users.detail.form.status_active", "활성")} {t("ui.admin.users.detail.form.status_active", "활성")}
</option> </option>
<option value="inactive"> <option value="inactive">
{t("ui.admin.users.detail.form.status_inactive", "비활성")} {t(
"ui.admin.users.detail.form.status_inactive",
"비활성",
)}
</option> </option>
</select> </select>
</div> </div>
@@ -661,7 +670,9 @@ function UserDetailPage() {
</div> </div>
<div className="grid gap-4"> <div className="grid gap-4">
{userAffiliatedTenants.map((t) => { {userAffiliatedTenants.map((t) => {
const tDetail = tenants.find((tenant) => tenant.id === t.id); const tDetail = tenants.find(
(tenant) => tenant.id === t.id,
);
const schema = (tDetail?.config?.userSchema || const schema = (tDetail?.config?.userSchema ||
[]) as UserSchemaField[]; []) as UserSchemaField[];
return ( return (
@@ -707,7 +718,8 @@ function UserDetailPage() {
)} )}
</p> </p>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
.
.
</p> </p>
</div> </div>
<Button variant="outline" onClick={handleGeneratePassword}> <Button variant="outline" onClick={handleGeneratePassword}>
@@ -725,12 +737,26 @@ function UserDetailPage() {
)} )}
</p> </p>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<Button variant="ghost" size="sm" onClick={() => setIsPasswordResetOpen(false)}> <Button
variant="ghost"
size="sm"
onClick={() => setIsPasswordResetOpen(false)}
>
{t("ui.common.cancel", "취소")} {t("ui.common.cancel", "취소")}
</Button> </Button>
<Button variant="destructive" size="sm" onClick={confirmGeneratePassword} disabled={resetPasswordMutation.isPending}> <Button
{resetPasswordMutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} variant="destructive"
{t("ui.admin.users.detail.reset_password", "초기화 및 생성")} size="sm"
onClick={confirmGeneratePassword}
disabled={resetPasswordMutation.isPending}
>
{resetPasswordMutation.isPending && (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
)}
{t(
"ui.admin.users.detail.reset_password",
"초기화 및 생성",
)}
</Button> </Button>
</div> </div>
</div> </div>
@@ -746,7 +772,11 @@ function UserDetailPage() {
{generatedPassword} {generatedPassword}
</p> </p>
</div> </div>
<Button size="sm" variant="secondary" onClick={handleCopyPassword}> <Button
size="sm"
variant="secondary"
onClick={handleCopyPassword}
>
<Copy className="mr-2 h-4 w-4" /> <Copy className="mr-2 h-4 w-4" />
{t("ui.common.copy", "복사")} {t("ui.common.copy", "복사")}
</Button> </Button>
@@ -770,7 +800,11 @@ function UserDetailPage() {
</div> </div>
<div> <div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Badge variant={user.status === "active" ? "default" : "secondary"}> <Badge
variant={
user.status === "active" ? "default" : "secondary"
}
>
{user.status === "active" ? "Active" : "Inactive"} {user.status === "active" ? "Active" : "Inactive"}
</Badge> </Badge>
<Badge variant="outline">{user.role}</Badge> <Badge variant="outline">{user.role}</Badge>

View File

@@ -149,9 +149,6 @@ test.describe("User Schema Dynamic Form", () => {
}) => { }) => {
await page.goto("/users/u-1"); await page.goto("/users/u-1");
// 섹션 헤더 확인 // 섹션 헤더 확인
const header = page const header = page
.getByText(/테넌트별 프로필 관리|Per-tenant Profile/i) .getByText(/테넌트별 프로필 관리|Per-tenant Profile/i)
@@ -176,9 +173,6 @@ test.describe("User Schema Dynamic Form", () => {
}) => { }) => {
await page.goto("/users/u-1"); await page.goto("/users/u-1");
const empIdInput = page.locator('input[id*="emp_id"]'); const empIdInput = page.locator('input[id*="emp_id"]');
await empIdInput.waitFor({ state: "visible" }); await empIdInput.waitFor({ state: "visible" });
await empIdInput.fill("invalid"); await empIdInput.fill("invalid");

View File

@@ -4058,7 +4058,8 @@ func (h *AuthHandler) resolveCurrentProfile(c *fiber.Ctx) (*domain.UserProfileRe
// Fallback to Hydra introspection. This is expected for API calls using Bearer tokens. // Fallback to Hydra introspection. This is expected for API calls using Bearer tokens.
slog.Debug("Kratos cookie session absent, falling back to Hydra token", "error", err.Error()) slog.Debug("Kratos cookie session absent, falling back to Hydra token", "error", err.Error())
profile, err = h.getHydraProfile(c.Context(), token) profile, err = h.getHydraProfile(c.Context(), token)
} } else if cookie != "" { }
} else if cookie != "" {
profile, err = h.getKratosProfileWithCookie(cookie) profile, err = h.getKratosProfileWithCookie(cookie)
} }
} }

View File

@@ -128,16 +128,16 @@ func TestUserHandler_BulkCreateUsers(t *testing.T) {
payload := map[string]interface{}{ payload := map[string]interface{}{
"users": []map[string]interface{}{ "users": []map[string]interface{}{
{ {
"email": "user1@test.com", "email": "user1@test.com",
"name": "User One", "name": "User One",
"tenantSlug": "test-tenant", "tenantSlug": "test-tenant",
"metadata": map[string]interface{}{"emp_id": "E001"}, "metadata": map[string]interface{}{"emp_id": "E001"},
}, },
{ {
"email": "user2@test.com", "email": "user2@test.com",
"name": "User Two", "name": "User Two",
"tenantSlug": "test-tenant", "tenantSlug": "test-tenant",
"metadata": map[string]interface{}{"emp_id": "E002"}, "metadata": map[string]interface{}{"emp_id": "E002"},
}, },
}, },
} }