forked from baron/baron-sso
style: apply backend go fmt and frontend biome auto-fixes
This commit is contained in:
@@ -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 addedUser = searchResults.find(u => u.id === userId);
|
const previousAdmins = queryClient.getQueryData<any[]>([
|
||||||
|
"tenant-admins",
|
||||||
|
tenantId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
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 ||
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ func (h *AuthHandler) CheckLoginID(c *fiber.Ctx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errorJSON(c, fiber.StatusServiceUnavailable, "Identity provider unavailable")
|
return errorJSON(c, fiber.StatusServiceUnavailable, "Identity provider unavailable")
|
||||||
}
|
}
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
return c.JSON(fiber.Map{"available": false, "message": "ID already registered"})
|
return c.JSON(fiber.Map{"available": false, "message": "ID already registered"})
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -579,7 +579,7 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
|
|||||||
finalLoginID := extractTraitString(attributes, "id")
|
finalLoginID := extractTraitString(attributes, "id")
|
||||||
userEmail := email
|
userEmail := email
|
||||||
userPhone := normalizePhoneNumber(item.Phone)
|
userPhone := normalizePhoneNumber(item.Phone)
|
||||||
|
|
||||||
if err := domain.ValidateLoginID(finalLoginID, userEmail, userPhone); err != nil {
|
if err := domain.ValidateLoginID(finalLoginID, userEmail, userPhone); err != nil {
|
||||||
results = append(results, bulkUserResult{Email: email, Success: false, Message: err.Error()})
|
results = append(results, bulkUserResult{Email: email, Success: false, Message: err.Error()})
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -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"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user