F: Rework users API

This commit is contained in:
Ivan 2025-02-22 19:21:10 +03:00
parent 21269a1072
commit c5238bf1a0
6 changed files with 47 additions and 49 deletions

View File

@ -1,12 +1,18 @@
import { queryOptions } from '@tanstack/react-query'; import { queryOptions } from '@tanstack/react-query';
import { z } from 'zod';
import { axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport'; import { axiosGet, axiosPatch, axiosPost } from '@/backend/apiTransport';
import { DELAYS, KEYS } from '@/backend/configuration'; import { DELAYS, KEYS } from '@/backend/configuration';
import { infoMsg } from '@/utils/labels'; import { infoMsg } from '@/utils/labels';
import { type IUserInfo, type IUserProfile } from '../models/user'; import {
type IUpdateProfileDTO,
import { type IUpdateProfileDTO, type IUserSignupDTO } from './types'; type IUserInfo,
type IUserProfile,
type IUserSignupDTO,
schemaUserInfo,
schemaUserProfile
} from './types';
export const usersApi = { export const usersApi = {
baseKey: KEYS.users, baseKey: KEYS.users,
@ -17,6 +23,7 @@ export const usersApi = {
staleTime: DELAYS.staleMedium, staleTime: DELAYS.staleMedium,
queryFn: meta => queryFn: meta =>
axiosGet<IUserInfo[]>({ axiosGet<IUserInfo[]>({
schema: z.array(schemaUserInfo),
endpoint: '/users/api/active-users', endpoint: '/users/api/active-users',
options: { signal: meta.signal } options: { signal: meta.signal }
}) })
@ -27,6 +34,7 @@ export const usersApi = {
staleTime: DELAYS.staleShort, staleTime: DELAYS.staleShort,
queryFn: meta => queryFn: meta =>
axiosGet<IUserProfile>({ axiosGet<IUserProfile>({
schema: schemaUserProfile,
endpoint: '/users/api/profile', endpoint: '/users/api/profile',
options: { signal: meta.signal } options: { signal: meta.signal }
}) })
@ -34,6 +42,7 @@ export const usersApi = {
signup: (data: IUserSignupDTO) => signup: (data: IUserSignupDTO) =>
axiosPost<IUserSignupDTO, IUserProfile>({ axiosPost<IUserSignupDTO, IUserProfile>({
schema: schemaUserProfile,
endpoint: '/users/api/signup', endpoint: '/users/api/signup',
request: { request: {
data: data, data: data,
@ -43,6 +52,7 @@ export const usersApi = {
updateProfile: (data: IUpdateProfileDTO) => updateProfile: (data: IUpdateProfileDTO) =>
axiosPatch<IUpdateProfileDTO, IUserProfile>({ axiosPatch<IUpdateProfileDTO, IUserProfile>({
schema: schemaUserProfile,
endpoint: '/users/api/profile', endpoint: '/users/api/profile',
request: { request: {
data: data, data: data,

View File

@ -4,8 +4,37 @@ import { patterns } from '@/utils/constants';
import { errorMsg } from '@/utils/labels'; import { errorMsg } from '@/utils/labels';
/** /**
* Represents signup data, used to create new users. * Represents user detailed information.
* Some information should only be accessible to authorized users
*/ */
export type IUser = z.infer<typeof schemaUser>;
/** Represents user profile for viewing and editing {@link IUser}. */
export type IUserProfile = z.infer<typeof schemaUserProfile>;
/** Represents user reference information. */
export type IUserInfo = z.infer<typeof schemaUserInfo>;
/** Represents signup data, used to create new users. */
export type IUserSignupDTO = z.infer<typeof schemaUserSignup>;
/** Represents user data, intended to update user profile in persistent storage. */
export type IUpdateProfileDTO = z.infer<typeof schemaUpdateProfile>;
// ========= SCHEMAS ========
export const schemaUser = z.object({
id: z.coerce.number(),
username: z.string().nonempty(errorMsg.requiredField),
is_staff: z.boolean(),
email: z.string().email(errorMsg.emailField),
first_name: z.string(),
last_name: z.string()
});
export const schemaUserProfile = schemaUser.omit({ is_staff: true });
export const schemaUserInfo = schemaUser.omit({ username: true, email: true, is_staff: true });
export const schemaUserSignup = z export const schemaUserSignup = z
.object({ .object({
username: z.string().nonempty(errorMsg.requiredField).regex(RegExp(patterns.login), errorMsg.loginFormat), username: z.string().nonempty(errorMsg.requiredField).regex(RegExp(patterns.login), errorMsg.loginFormat),
@ -18,21 +47,8 @@ export const schemaUserSignup = z
}) })
.refine(schema => schema.password === schema.password2, { path: ['password2'], message: errorMsg.passwordsMismatch }); .refine(schema => schema.password === schema.password2, { path: ['password2'], message: errorMsg.passwordsMismatch });
/**
* Represents signup data, used to create new users.
*/
export type IUserSignupDTO = z.infer<typeof schemaUserSignup>;
/**
* Represents user data, intended to update user profile in persistent storage.
*/
export const schemaUpdateProfile = z.object({ export const schemaUpdateProfile = z.object({
email: z.string().email(errorMsg.emailField), email: z.string().email(errorMsg.emailField),
first_name: z.string(), first_name: z.string(),
last_name: z.string() last_name: z.string()
}); });
/**
* Represents user data, intended to update user profile in persistent storage.
*/
export type IUpdateProfileDTO = z.infer<typeof schemaUpdateProfile>;

View File

@ -4,7 +4,7 @@ import { MiniButton } from '@/components/Control';
import { createColumnHelper, DataTable } from '@/components/DataTable'; import { createColumnHelper, DataTable } from '@/components/DataTable';
import { IconRemove } from '@/components/Icons'; import { IconRemove } from '@/components/Icons';
import { type IUserInfo } from '../models/user'; import { type IUserInfo } from '../backend/types';
interface TableUsersProps { interface TableUsersProps {
items: IUserInfo[]; items: IUserInfo[];

View File

@ -1,26 +0,0 @@
/**
* Module: Models for Users.
*/
/**
* Represents user detailed information.
* Some information should only be accessible to authorized users
*/
export interface IUser {
id: number;
username: string;
is_staff: boolean;
email: string;
first_name: string;
last_name: string;
}
/**
* Represents user profile for viewing and editing {@link IUser}.
*/
export interface IUserProfile extends Omit<IUser, 'is_staff'> {}
/**
* Represents user reference information.
*/
export interface IUserInfo extends Omit<IUserProfile, 'email' | 'username'> {}

View File

@ -4,7 +4,7 @@
import { TextMatcher } from '@/utils/utils'; import { TextMatcher } from '@/utils/utils';
import { type IUserInfo } from './user'; import { type IUserInfo } from '../backend/types';
/** /**
* Checks if a given target {@link IConstituenta} matches the specified query using the provided matching mode. * Checks if a given target {@link IConstituenta} matches the specified query using the provided matching mode.

View File

@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import clsx from 'clsx'; import clsx from 'clsx';
import { urls, useBlockNavigation, useConceptNavigation } from '@/app'; import { urls, useConceptNavigation } from '@/app';
import { HelpTopic } from '@/features/help'; import { HelpTopic } from '@/features/help';
import { isAxiosError } from '@/backend/apiTransport'; import { isAxiosError } from '@/backend/apiTransport';
@ -30,13 +30,11 @@ export function FormSignup() {
register, register,
handleSubmit, handleSubmit,
clearErrors, clearErrors,
formState: { errors, isDirty } formState: { errors }
} = useForm<IUserSignupDTO>({ } = useForm<IUserSignupDTO>({
resolver: zodResolver(schemaUserSignup) resolver: zodResolver(schemaUserSignup)
}); });
useBlockNavigation(isDirty);
function resetErrors() { function resetErrors() {
clearServerError(); clearServerError();
clearErrors(); clearErrors();