F: Implement backend hooks for frontend

This commit is contained in:
Ivan 2025-07-14 15:46:28 +03:00
parent e4411c2c78
commit 1d11bd4ab5
13 changed files with 234 additions and 14 deletions

View File

@ -1,2 +1,2 @@
''' Serializers for persistent data manipulation (AI Prompts). '''
from .data_access import PromptTemplateSerializer
from .data_access import PromptTemplateListSerializer, PromptTemplateSerializer

View File

@ -37,3 +37,12 @@ class PromptTemplateSerializer(serializers.ModelSerializer):
if validated_data['is_shared'] and not (user.is_superuser or user.is_staff):
raise serializers.ValidationError(msg.promptSharedPermissionDenied())
return super().update(instance, validated_data)
class PromptTemplateListSerializer(serializers.ModelSerializer):
'''Serializer for listing PromptTemplates without the 'text' field.'''
class Meta:
''' serializer metadata. '''
model = PromptTemplate
fields = ['id', 'owner', 'is_shared', 'label', 'description']
read_only_fields = ['id', 'owner']

View File

@ -98,6 +98,8 @@ class TestPromptTemplateViewSet(EndpointTester):
labels = [item['label'] for item in response.data]
self.assertIn('Mine', labels)
self.assertIn('Shared', labels)
for item in response.data:
self.assertNotIn('text', item)
@decl_endpoint('/api/prompts/{item}/', method='patch')

View File

@ -7,7 +7,7 @@ from rest_framework.decorators import action
from rest_framework.response import Response
from ..models import PromptTemplate
from ..serializers import PromptTemplateSerializer
from ..serializers import PromptTemplateListSerializer, PromptTemplateSerializer
class IsOwnerOrAdmin(permissions.BasePermission):
@ -48,5 +48,5 @@ class PromptTemplateViewSet(viewsets.ModelViewSet):
owned = PromptTemplate.objects.filter(owner=user)
shared = PromptTemplate.objects.filter(is_shared=True)
templates = (owned | shared).distinct()
serializer = self.get_serializer(templates, many=True)
serializer = PromptTemplateListSerializer(templates, many=True)
return Response(serializer.data)

View File

@ -16,6 +16,7 @@ export const KEYS = {
library: 'library',
users: 'users',
cctext: 'cctext',
prompts: 'prompts',
global_mutation: 'global_mutation',
composite: {

View File

@ -0,0 +1,70 @@
import { queryOptions } from '@tanstack/react-query';
import { axiosDelete, axiosGet, axiosPatch, axiosPost } from '@/backend/api-transport';
import { DELAYS, KEYS } from '@/backend/configuration';
import { infoMsg } from '@/utils/labels';
import {
type ICreatePromptTemplateDTO,
type IPromptTemplateDTO,
type IPromptTemplateListDTO,
type IUpdatePromptTemplateDTO,
schemaPromptTemplate,
schemaPromptTemplateList
} from './types';
export const promptsApi = {
baseKey: KEYS.prompts,
getAvailableTemplatesQueryOptions: () =>
queryOptions({
queryKey: [KEYS.prompts, 'available'] as const,
staleTime: DELAYS.staleShort,
queryFn: meta =>
axiosGet<IPromptTemplateListDTO>({
schema: schemaPromptTemplateList,
endpoint: '/api/prompts/available/',
options: { signal: meta.signal }
})
}),
getPromptTemplateQueryOptions: (id: number) =>
queryOptions({
queryKey: [KEYS.prompts, id],
staleTime: DELAYS.staleShort,
queryFn: meta =>
axiosGet<IPromptTemplateDTO>({
schema: schemaPromptTemplate,
endpoint: `/api/prompts/${id}/`,
options: { signal: meta.signal }
})
}),
createPromptTemplate: (data: ICreatePromptTemplateDTO) =>
axiosPost<ICreatePromptTemplateDTO, IPromptTemplateDTO>({
schema: schemaPromptTemplate,
endpoint: '/api/prompts/',
request: {
data: data,
successMessage: infoMsg.changesSaved
}
}),
updatePromptTemplate: (id: number, data: IUpdatePromptTemplateDTO) =>
axiosPatch<IUpdatePromptTemplateDTO, IPromptTemplateDTO>({
schema: schemaPromptTemplate,
endpoint: `/api/prompts/${id}/`,
request: {
data: data,
successMessage: infoMsg.changesSaved
}
}),
deletePromptTemplate: (id: number) =>
axiosDelete({
endpoint: `/api/prompts/${id}/`,
request: {
successMessage: infoMsg.changesSaved
}
})
} as const;

View File

@ -1,11 +1,54 @@
import { z } from 'zod';
/** Represents AI prompt. */
export interface IPromptTemplate {
id: number;
owner: number | null;
is_shared: boolean;
label: string;
description: string;
text: string;
}
export type IPromptTemplate = IPromptTemplateDTO;
export type IPromptTemplateInfo = z.infer<typeof schemaPromptTemplateInfo>;
/** Full prompt template as returned by backend. */
export type IPromptTemplateDTO = z.infer<typeof schemaPromptTemplate>;
/** List item for available prompt templates (no text field). */
export type IPromptTemplateListDTO = z.infer<typeof schemaPromptTemplateList>;
/** Data for creating a prompt template. */
export type ICreatePromptTemplateDTO = z.infer<typeof schemaCreatePromptTemplate>;
/** Data for updating a prompt template. */
export type IUpdatePromptTemplateDTO = z.infer<typeof schemaUpdatePromptTemplate>;
// ========= SCHEMAS ========
export const schemaPromptTemplate = z.strictObject({
id: z.number(),
owner: z.number().nullable(),
label: z.string(),
description: z.string(),
text: z.string(),
is_shared: z.boolean()
});
export const schemaCreatePromptTemplate = schemaPromptTemplate.pick({
label: true,
description: true,
text: true,
is_shared: true
});
export const schemaUpdatePromptTemplate = schemaPromptTemplate.pick({
owner: true,
label: true,
description: true,
text: true,
is_shared: true
});
export const schemaPromptTemplateInfo = schemaPromptTemplate.pick({
id: true,
owner: true,
label: true,
description: true,
is_shared: true
});
export const schemaPromptTemplateList = schemaPromptTemplateInfo.array();

View File

@ -0,0 +1,17 @@
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { promptsApi } from './api';
export function useAvailableTemplates() {
const { data, isLoading, error } = useQuery({
...promptsApi.getAvailableTemplatesQueryOptions()
});
return { data, isLoading, error };
}
export function useAvailableTemplatesSuspense() {
const { data } = useSuspenseQuery({
...promptsApi.getAvailableTemplatesQueryOptions()
});
return { data };
}

View File

@ -0,0 +1,20 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { promptsApi } from './api';
export function useCreatePromptTemplate() {
const client = useQueryClient();
const mutation = useMutation({
mutationKey: [promptsApi.baseKey, 'create'],
mutationFn: promptsApi.createPromptTemplate,
onSuccess: () => {
void client.invalidateQueries({ queryKey: [promptsApi.baseKey] });
}
});
return {
createPromptTemplate: mutation.mutateAsync,
isPending: mutation.isPending,
error: mutation.error,
reset: mutation.reset
};
}

View File

@ -0,0 +1,21 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { promptsApi } from './api';
export function useDeletePromptTemplate() {
const client = useQueryClient();
const mutation = useMutation({
mutationKey: [promptsApi.baseKey, 'delete'],
mutationFn: promptsApi.deletePromptTemplate,
onSuccess: (_data, id) => {
void client.invalidateQueries({ queryKey: [promptsApi.baseKey] });
void client.invalidateQueries({ queryKey: [promptsApi.baseKey, id] });
}
});
return {
deletePromptTemplate: mutation.mutateAsync,
isPending: mutation.isPending,
error: mutation.error,
reset: mutation.reset
};
}

View File

@ -0,0 +1,17 @@
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { promptsApi } from './api';
export function usePromptTemplate(id: number) {
const { data, isLoading, error } = useQuery({
...promptsApi.getPromptTemplateQueryOptions(id)
});
return { data, isLoading, error };
}
export function usePromptTemplateSuspense(id: number) {
const { data } = useSuspenseQuery({
...promptsApi.getPromptTemplateQueryOptions(id)
});
return { data };
}

View File

@ -0,0 +1,22 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { promptsApi } from './api';
import { type IUpdatePromptTemplateDTO } from './types';
export function useUpdatePromptTemplate() {
const client = useQueryClient();
const mutation = useMutation({
mutationKey: [promptsApi.baseKey, 'update'],
mutationFn: ({ id, data }: { id: number; data: IUpdatePromptTemplateDTO }) =>
promptsApi.updatePromptTemplate(id, data),
onSuccess: (_, variables) => {
void client.invalidateQueries({ queryKey: [promptsApi.baseKey, variables.id] });
}
});
return {
updatePromptTemplate: mutation.mutateAsync,
isPending: mutation.isPending,
error: mutation.error,
reset: mutation.reset
};
}

View File

@ -89,9 +89,7 @@ export interface ICheckConstituentaDTO {
/** Represents data, used in merging multiple {@link IConstituenta}. */
export type ISubstitutionsDTO = z.infer<typeof schemaSubstitutions>;
/**
* Represents Constituenta list.
*/
/** Represents Constituenta list. */
export interface IConstituentaList {
items: number[];
}