diff --git a/client/src/api/api.ts b/client/src/api/api.ts index 3be31c50c7..ed9b222133 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -1756,6 +1756,31 @@ export interface CreateGratitudeDto { */ 'badgeId': string; } +/** + * + * @export + * @interface CreatePromptDto + */ +export interface CreatePromptDto { + /** + * + * @type {string} + * @memberof CreatePromptDto + */ + 'type': string; + /** + * + * @type {string} + * @memberof CreatePromptDto + */ + 'text': string; + /** + * + * @type {number} + * @memberof CreatePromptDto + */ + 'temperature': number; +} /** * * @export @@ -2068,6 +2093,108 @@ export const CriteriaDtoTypeEnum = { export type CriteriaDtoTypeEnum = typeof CriteriaDtoTypeEnum[keyof typeof CriteriaDtoTypeEnum]; +/** + * + * @export + * @interface CrossCheckAuthorDto + */ +export interface CrossCheckAuthorDto { + /** + * + * @type {number} + * @memberof CrossCheckAuthorDto + */ + 'id': number; + /** + * + * @type {string} + * @memberof CrossCheckAuthorDto + */ + 'name': string; + /** + * + * @type {string} + * @memberof CrossCheckAuthorDto + */ + 'githubId': string; + /** + * + * @type {Discord} + * @memberof CrossCheckAuthorDto + */ + 'discord': Discord | null; +} +/** + * + * @export + * @interface CrossCheckCriteriaDataDto + */ +export interface CrossCheckCriteriaDataDto { + /** + * + * @type {string} + * @memberof CrossCheckCriteriaDataDto + */ + 'key': string; + /** + * + * @type {number} + * @memberof CrossCheckCriteriaDataDto + */ + 'max'?: number; + /** + * + * @type {string} + * @memberof CrossCheckCriteriaDataDto + */ + 'text': string; + /** + * + * @type {string} + * @memberof CrossCheckCriteriaDataDto + */ + 'type': CrossCheckCriteriaDataDtoTypeEnum; + /** + * + * @type {number} + * @memberof CrossCheckCriteriaDataDto + */ + 'point'?: number; + /** + * + * @type {string} + * @memberof CrossCheckCriteriaDataDto + */ + 'textComment'?: string; +} + +export const CrossCheckCriteriaDataDtoTypeEnum = { + Title: 'title', + Subtask: 'subtask', + Penalty: 'penalty' +} as const; + +export type CrossCheckCriteriaDataDtoTypeEnum = typeof CrossCheckCriteriaDataDtoTypeEnum[keyof typeof CrossCheckCriteriaDataDtoTypeEnum]; + +/** + * + * @export + * @interface CrossCheckFeedbackDto + */ +export interface CrossCheckFeedbackDto { + /** + * + * @type {string} + * @memberof CrossCheckFeedbackDto + */ + 'url'?: string; + /** + * + * @type {Array} + * @memberof CrossCheckFeedbackDto + */ + 'reviews'?: Array; +} /** * * @export @@ -2230,6 +2357,55 @@ export interface CrossCheckPairResponseDto { */ 'pagination': PaginationDto; } +/** + * + * @export + * @interface CrossCheckSolutionReviewDto + */ +export interface CrossCheckSolutionReviewDto { + /** + * + * @type {number} + * @memberof CrossCheckSolutionReviewDto + */ + 'id': number; + /** + * + * @type {number} + * @memberof CrossCheckSolutionReviewDto + */ + 'dateTime': number; + /** + * + * @type {string} + * @memberof CrossCheckSolutionReviewDto + */ + 'comment': string; + /** + * + * @type {Array} + * @memberof CrossCheckSolutionReviewDto + */ + 'criteria'?: Array; + /** + * + * @type {CrossCheckAuthorDto} + * @memberof CrossCheckSolutionReviewDto + */ + 'author': CrossCheckAuthorDto | null; + /** + * + * @type {number} + * @memberof CrossCheckSolutionReviewDto + */ + 'score': number; + /** + * + * @type {Array} + * @memberof CrossCheckSolutionReviewDto + */ + 'messages': Array; +} /** * * @export @@ -2354,6 +2530,25 @@ export interface Education { */ 'graduationYear': number; } +/** + * + * @export + * @interface EndorsementDto + */ +export interface EndorsementDto { + /** + * + * @type {string} + * @memberof EndorsementDto + */ + 'summary': string; + /** + * + * @type {object} + * @memberof EndorsementDto + */ + 'data': object | null; +} /** * * @export @@ -2803,6 +2998,12 @@ export interface HistoricalScoreDto { * @memberof HistoricalScoreDto */ 'dateTime': string; + /** + * + * @type {Array} + * @memberof HistoricalScoreDto + */ + 'criteria'?: Array; } /** * @@ -3751,6 +3952,37 @@ export interface ProfileInfoDto { */ 'isProfileSettingsChanged': boolean; } +/** + * + * @export + * @interface PromptDto + */ +export interface PromptDto { + /** + * + * @type {number} + * @memberof PromptDto + */ + 'id': number; + /** + * + * @type {string} + * @memberof PromptDto + */ + 'type': string; + /** + * + * @type {string} + * @memberof PromptDto + */ + 'text': string; + /** + * + * @type {number} + * @memberof PromptDto + */ + 'temperature': number; +} /** * * @export @@ -5666,6 +5898,31 @@ export interface UpdateProfileInfoDto { */ 'discord'?: Discord | null; } +/** + * + * @export + * @interface UpdatePromptDto + */ +export interface UpdatePromptDto { + /** + * + * @type {number} + * @memberof UpdatePromptDto + */ + 'temperature': number; + /** + * + * @type {string} + * @memberof UpdatePromptDto + */ + 'type': string; + /** + * + * @type {string} + * @memberof UpdatePromptDto + */ + 'text': string; +} /** * * @export @@ -9445,6 +9702,43 @@ export const CoursesTasksApiAxiosParamCreator = function (configuration?: Config + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {number} courseId + * @param {number} courseTaskId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getMyCrossCheckFeedbacks: async (courseId: number, courseTaskId: number, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'courseId' is not null or undefined + assertParamExists('getMyCrossCheckFeedbacks', 'courseId', courseId) + // verify required parameter 'courseTaskId' is not null or undefined + assertParamExists('getMyCrossCheckFeedbacks', 'courseTaskId', courseTaskId) + const localVarPath = `/courses/{courseId}/cross-checks/{courseTaskId}/feedbacks/my` + .replace(`{${"courseId"}}`, encodeURIComponent(String(courseId))) + .replace(`{${"courseTaskId"}}`, encodeURIComponent(String(courseTaskId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -9646,6 +9940,17 @@ export const CoursesTasksApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getCrossCheckPairs(courseId, pageSize, current, orderBy, orderDirection, checker, student, url, task, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {number} courseId + * @param {number} courseTaskId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getMyCrossCheckFeedbacks(courseId: number, courseTaskId: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getMyCrossCheckFeedbacks(courseId, courseTaskId, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {CheckTasksDeadlineDto} checkTasksDeadlineDto @@ -9773,6 +10078,16 @@ export const CoursesTasksApiFactory = function (configuration?: Configuration, b getCrossCheckPairs(courseId: number, pageSize: number, current: number, orderBy?: string, orderDirection?: string, checker?: string, student?: string, url?: string, task?: string, options?: any): AxiosPromise { return localVarFp.getCrossCheckPairs(courseId, pageSize, current, orderBy, orderDirection, checker, student, url, task, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {number} courseId + * @param {number} courseTaskId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getMyCrossCheckFeedbacks(courseId: number, courseTaskId: number, options?: any): AxiosPromise { + return localVarFp.getMyCrossCheckFeedbacks(courseId, courseTaskId, options).then((request) => request(axios, basePath)); + }, /** * * @param {CheckTasksDeadlineDto} checkTasksDeadlineDto @@ -9916,6 +10231,18 @@ export class CoursesTasksApi extends BaseAPI { return CoursesTasksApiFp(this.configuration).getCrossCheckPairs(courseId, pageSize, current, orderBy, orderDirection, checker, student, url, task, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {number} courseId + * @param {number} courseTaskId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof CoursesTasksApi + */ + public getMyCrossCheckFeedbacks(courseId: number, courseTaskId: number, options?: AxiosRequestConfig) { + return CoursesTasksApiFp(this.configuration).getMyCrossCheckFeedbacks(courseId, courseTaskId, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {CheckTasksDeadlineDto} checkTasksDeadlineDto @@ -12242,10 +12569,10 @@ export const ProfileApiAxiosParamCreator = function (configuration?: Configurati * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getPersonalProfile: async (username: string, options: AxiosRequestConfig = {}): Promise => { + getEndorsement: async (username: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'username' is not null or undefined - assertParamExists('getPersonalProfile', 'username', username) - const localVarPath = `/profile/{username}/personal` + assertParamExists('getEndorsement', 'username', username) + const localVarPath = `/profile/{username}/endorsement` .replace(`{${"username"}}`, encodeURIComponent(String(username))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -12275,9 +12602,42 @@ export const ProfileApiAxiosParamCreator = function (configuration?: Configurati * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getProfile: async (username: string, options: AxiosRequestConfig = {}): Promise => { + getPersonalProfile: async (username: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'username' is not null or undefined - assertParamExists('getProfile', 'username', username) + assertParamExists('getPersonalProfile', 'username', username) + const localVarPath = `/profile/{username}/personal` + .replace(`{${"username"}}`, encodeURIComponent(String(username))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} username + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getProfile: async (username: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'username' is not null or undefined + assertParamExists('getProfile', 'username', username) const localVarPath = `/profile/{username}` .replace(`{${"username"}}`, encodeURIComponent(String(username))); // use dummy base URL string because the URL constructor only accepts absolute URLs. @@ -12450,6 +12810,16 @@ export const ProfileApiAxiosParamCreator = function (configuration?: Configurati export const ProfileApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = ProfileApiAxiosParamCreator(configuration) return { + /** + * + * @param {string} username + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getEndorsement(username: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getEndorsement(username, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {string} username @@ -12520,6 +12890,15 @@ export const ProfileApiFp = function(configuration?: Configuration) { export const ProfileApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = ProfileApiFp(configuration) return { + /** + * + * @param {string} username + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getEndorsement(username: string, options?: any): AxiosPromise { + return localVarFp.getEndorsement(username, options).then((request) => request(axios, basePath)); + }, /** * * @param {string} username @@ -12584,6 +12963,17 @@ export const ProfileApiFactory = function (configuration?: Configuration, basePa * @extends {BaseAPI} */ export class ProfileApi extends BaseAPI { + /** + * + * @param {string} username + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ProfileApi + */ + public getEndorsement(username: string, options?: AxiosRequestConfig) { + return ProfileApiFp(this.configuration).getEndorsement(username, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {string} username @@ -12652,6 +13042,300 @@ export class ProfileApi extends BaseAPI { } +/** + * PromptsApi - axios parameter creator + * @export + */ +export const PromptsApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {CreatePromptDto} createPromptDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createPrompt: async (createPromptDto: CreatePromptDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'createPromptDto' is not null or undefined + assertParamExists('createPrompt', 'createPromptDto', createPromptDto) + const localVarPath = `/prompts`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(createPromptDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {number} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deletePrompt: async (id: number, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('deletePrompt', 'id', id) + const localVarPath = `/prompts/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPrompts: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/prompts`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {number} id + * @param {UpdatePromptDto} updatePromptDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updatePrompt: async (id: number, updatePromptDto: UpdatePromptDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('updatePrompt', 'id', id) + // verify required parameter 'updatePromptDto' is not null or undefined + assertParamExists('updatePrompt', 'updatePromptDto', updatePromptDto) + const localVarPath = `/prompts/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(updatePromptDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * PromptsApi - functional programming interface + * @export + */ +export const PromptsApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = PromptsApiAxiosParamCreator(configuration) + return { + /** + * + * @param {CreatePromptDto} createPromptDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createPrompt(createPromptDto: CreatePromptDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createPrompt(createPromptDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {number} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deletePrompt(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deletePrompt(id, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getPrompts(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getPrompts(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {number} id + * @param {UpdatePromptDto} updatePromptDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updatePrompt(id: number, updatePromptDto: UpdatePromptDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updatePrompt(id, updatePromptDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * PromptsApi - factory interface + * @export + */ +export const PromptsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = PromptsApiFp(configuration) + return { + /** + * + * @param {CreatePromptDto} createPromptDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createPrompt(createPromptDto: CreatePromptDto, options?: any): AxiosPromise { + return localVarFp.createPrompt(createPromptDto, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {number} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deletePrompt(id: number, options?: any): AxiosPromise { + return localVarFp.deletePrompt(id, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getPrompts(options?: any): AxiosPromise> { + return localVarFp.getPrompts(options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {number} id + * @param {UpdatePromptDto} updatePromptDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updatePrompt(id: number, updatePromptDto: UpdatePromptDto, options?: any): AxiosPromise { + return localVarFp.updatePrompt(id, updatePromptDto, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * PromptsApi - object-oriented interface + * @export + * @class PromptsApi + * @extends {BaseAPI} + */ +export class PromptsApi extends BaseAPI { + /** + * + * @param {CreatePromptDto} createPromptDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PromptsApi + */ + public createPrompt(createPromptDto: CreatePromptDto, options?: AxiosRequestConfig) { + return PromptsApiFp(this.configuration).createPrompt(createPromptDto, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {number} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PromptsApi + */ + public deletePrompt(id: number, options?: AxiosRequestConfig) { + return PromptsApiFp(this.configuration).deletePrompt(id, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PromptsApi + */ + public getPrompts(options?: AxiosRequestConfig) { + return PromptsApiFp(this.configuration).getPrompts(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {number} id + * @param {UpdatePromptDto} updatePromptDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof PromptsApi + */ + public updatePrompt(id: number, updatePromptDto: UpdatePromptDto, options?: AxiosRequestConfig) { + return PromptsApiFp(this.configuration).updatePrompt(id, updatePromptDto, options).then((request) => request(this.axios, this.basePath)); + } +} + + /** * RegistryApi - axios parameter creator * @export diff --git a/client/src/components/Sider/data/menuItems.tsx b/client/src/components/Sider/data/menuItems.tsx index 2f23ad5aeb..e081f2db74 100644 --- a/client/src/components/Sider/data/menuItems.tsx +++ b/client/src/components/Sider/data/menuItems.tsx @@ -8,6 +8,7 @@ import NotificationFilled from '@ant-design/icons/NotificationFilled'; import ProfileFilled from '@ant-design/icons/ProfileFilled'; import TeamOutlined from '@ant-design/icons/TeamOutlined'; import UserOutlined from '@ant-design/icons/UserOutlined'; +import FileTextOutlined from '@ant-design/icons/FileTextOutlined'; import { DiscordOutlined } from 'components/Icons/DiscordOutlined'; import { Session } from 'components/withSession'; import { @@ -128,6 +129,13 @@ const adminMenuItems: AdminMenuItemsData[] = [ href: '/admin/notifications', access: session => isAdmin(session), }, + { + name: 'Propmts', + key: 'prompts', + icon: , + href: '/admin/prompts', + access: session => isAdmin(session), + }, ]; export function getAdminMenuItems(session: Session): MenuItemsRenderData[] { diff --git a/client/src/components/Student/AssignStudentModal.tsx b/client/src/components/Student/AssignStudentModal.tsx index 8fc50fdbca..cb05076722 100644 --- a/client/src/components/Student/AssignStudentModal.tsx +++ b/client/src/components/Student/AssignStudentModal.tsx @@ -1,91 +1,50 @@ -import { Button, Modal, Row, Typography, message } from 'antd'; -import * as React from 'react'; +import { Modal, Typography, message } from 'antd'; import { StudentSearch } from 'components/StudentSearch'; +import { useCallback, useState } from 'react'; import { CourseService } from 'services/course'; const { Text } = Typography; -type State = { - isModalOpened: boolean; - studentGithubId: string | null; -}; - type Props = { - mentorGithuId: string; + mentorGithuId: string | null; courseId: number; + open: boolean; + onClose: () => void; }; -class AssignStudentModal extends React.PureComponent { - state: State = { - isModalOpened: false, - studentGithubId: null, - }; - - openModal = () => { - this.setState({ isModalOpened: true }); - }; - - closeModal = () => { - this.setState({ isModalOpened: false }); - }; - - addStudent = async () => { - const { mentorGithuId } = this.props; - const { studentGithubId } = this.state; +export function AssignStudentModal(props: Props) { + const [studentGithubId, setStudentGithubId] = useState(null); + const addStudent = useCallback(async () => { if (!studentGithubId) { return; } - - this.reset(); - try { - await new CourseService(this.props.courseId).updateStudent(studentGithubId, { mentorGithuId }); - message.success('Success'); + await new CourseService(props.courseId).updateStudent(studentGithubId, { mentorGithuId: props.mentorGithuId }); + props.onClose(); + message.success('Student has been added to mentor'); } catch (e) { message.error(`${e}`); } - }; - - reset = () => { - this.setState({ isModalOpened: false, studentGithubId: null }); - }; - - handleStudentSelect = (githubId: string) => { - this.setState({ studentGithubId: githubId ?? null }); - }; - - render() { - const { mentorGithuId, courseId } = this.props; - const { isModalOpened } = this.state; - - return ( - <> - - - Assign Student to {mentorGithuId} - - } - open={isModalOpened} - onOk={this.addStudent} - onCancel={this.reset} - > - - - - - - ); - } + }, [props.mentorGithuId, studentGithubId]); + + return ( + + Assign Student to {props.mentorGithuId} + + } + open={props.open} + onOk={addStudent} + onCancel={props.onClose} + > + + + ); } - -export default AssignStudentModal; diff --git a/client/src/components/Student/index.ts b/client/src/components/Student/index.ts index 076f0e2412..c9c91fc599 100644 --- a/client/src/components/Student/index.ts +++ b/client/src/components/Student/index.ts @@ -1,2 +1,2 @@ -export { default as AssignStudentModal } from './AssignStudentModal'; +export { AssignStudentModal } from './AssignStudentModal'; export { DashboardDetails } from './DashboardDetails'; diff --git a/client/src/modules/AutoTest/components/Question/Question.tsx b/client/src/modules/AutoTest/components/Question/Question.tsx index bb965410d4..dbd589d09e 100644 --- a/client/src/modules/AutoTest/components/Question/Question.tsx +++ b/client/src/modules/AutoTest/components/Question/Question.tsx @@ -1,5 +1,6 @@ import { Typography, Form, Row, Checkbox, Radio, Col, Space } from 'antd'; import { SelfEducationQuestionSelectedAnswersDto } from 'api'; +import css from 'styled-jsx/css'; const { Title } = Typography; @@ -13,60 +14,70 @@ function Question({ question: selfEducationQuestion, questionIndex }: Props): JS const Element = multiple ? Checkbox : Radio; return ( - - - {question} - - - {questionImage && ( - - )} - - - } - name={`answer-${questionIndex}`} - valuePropName="checked" - > - - {answers?.map((answer, answerIndex) => { - const checked = Array.isArray(selectedAnswers) - ? selectedAnswers?.includes(answerIndex) - : selectedAnswers === answerIndex; +
+ + + {question} + + + {questionImage && ( + + )} + + + } + name={`answer-${questionIndex}`} + valuePropName="checked" + > + + + {answers?.map((answer, answerIndex) => { + const checked = Array.isArray(selectedAnswers) + ? selectedAnswers?.includes(answerIndex) + : selectedAnswers === answerIndex; - return ( - - - {answersType === 'image' ? ( - <> - ({answerIndex + 1}){' '} - - - ) : ( - answer - )} - - - ); - })} - - + return ( + + {answersType === 'image' ? ( + <> + ({answerIndex + 1}){' '} + + + ) : ( + answer + )} + + ); + })} + + + + +
); } +const styles = css` + .question :global(.ant-radio) { + align-self: flex-start !important; + margin-top: 3px !important; + } +`; + export default Question; diff --git a/client/src/modules/AutoTest/components/SelfEducation/SelfEducation.tsx b/client/src/modules/AutoTest/components/SelfEducation/SelfEducation.tsx index 6e217224ed..bbe3cb4e97 100644 --- a/client/src/modules/AutoTest/components/SelfEducation/SelfEducation.tsx +++ b/client/src/modules/AutoTest/components/SelfEducation/SelfEducation.tsx @@ -3,6 +3,7 @@ import { useMemo } from 'react'; import { SelfEducationQuestionWithIndex, SelfEducationQuestion } from 'services/course'; import shuffle from 'lodash/shuffle'; import { CourseTaskVerifications } from 'modules/AutoTest/types'; +import css from 'styled-jsx/css'; type SelfEducationProps = { courseTask: CourseTaskVerifications; @@ -24,7 +25,7 @@ function SelfEducation({ courseTask }: SelfEducationProps) { ); return ( - <> +
To submit the task answer the questions. {randomQuestions?.map( ({ question, answers, multiple, questionImage, answersType, index: questionIndex }, idx) => { @@ -58,9 +59,9 @@ function SelfEducation({ courseTask }: SelfEducationProps) { > {multiple ? ( - {answers?.map((answer, answerIndex) => ( - - + + {answers?.map((answer, answerIndex) => ( + {answersType === 'image' ? ( <> ({answerIndex + 1}){' '} @@ -77,14 +78,14 @@ function SelfEducation({ courseTask }: SelfEducationProps) { answer )} - - ))} + ))} + ) : ( - {answers?.map((answer, index) => ( - - + + {answers?.map((answer, index) => ( + {answersType === 'image' ? ( <> ({index + 1}){' '} @@ -101,16 +102,24 @@ function SelfEducation({ courseTask }: SelfEducationProps) { answer )} - - ))} + ))} + )} ); }, )} - + +
); } +const styles = css` + .self-education :global(.ant-radio) { + align-self: flex-start !important; + margin-top: 3px !important; + } +`; + export default SelfEducation; diff --git a/client/src/modules/Course/contexts/SessionContext.tsx b/client/src/modules/Course/contexts/SessionContext.tsx index c8ebaeec0f..5a46f2c12e 100644 --- a/client/src/modules/Course/contexts/SessionContext.tsx +++ b/client/src/modules/Course/contexts/SessionContext.tsx @@ -15,6 +15,7 @@ let sessionCache: Session | undefined; type Props = React.PropsWithChildren<{ allowedRoles?: CourseRole[]; course?: ProfileCourseDto; + adminOnly?: boolean; }>; export function SessionProvider(props: Props) { @@ -42,23 +43,39 @@ export function SessionProvider(props: Props) { Router.push('/login', { pathname: '/login', query: { url: redirectUrl } }); }, [error]); + if (session && props.adminOnly && !session.isAdmin) { + return ( + window.history.back()}> + Go Back + + } + /> + ); + } + if (session && allowedRoles && props.course) { const { courses, isAdmin } = session; const id = props.course.id; - const roles = courses?.[id]?.roles ?? []; - if (!allowedRoles.some(role => roles.includes(role)) && !isAdmin) { - return ( - window.history.back()}> - Go Back - - } - /> - ); + if (!isAdmin) { + const roles = courses?.[id]?.roles ?? []; + if (!allowedRoles.some(role => roles.includes(role))) { + return ( + window.history.back()}> + Go Back + + } + /> + ); + } } } if (session) { diff --git a/client/src/modules/Course/pages/Student/CrossCheckSubmit/index.tsx b/client/src/modules/Course/pages/Student/CrossCheckSubmit/index.tsx index 48bc313b38..56e1ba8e2e 100644 --- a/client/src/modules/Course/pages/Student/CrossCheckSubmit/index.tsx +++ b/client/src/modules/Course/pages/Student/CrossCheckSubmit/index.tsx @@ -12,18 +12,11 @@ import { NoSubmissionAvailable } from 'modules/Course/components/NoSubmissionAva import { useRouter } from 'next/router'; import { useEffect, useMemo, useState } from 'react'; import { useAsync } from 'react-use'; -import { - CourseService, - CrossCheckComment, - CrossCheckCriteria, - CrossCheckReview, - Feedback, - TaskSolution, -} from 'services/course'; +import { CourseService, CrossCheckComment, CrossCheckCriteria, CrossCheckReview, TaskSolution } from 'services/course'; import { CoursePageProps } from 'services/models'; import { urlWithIpPattern } from 'services/validators'; import { getQueryString } from 'utils/queryParams-utils'; -import { CrossCheckMessageDtoRoleEnum } from 'api'; +import { CoursesTasksApi, CrossCheckFeedbackDto, CrossCheckMessageDtoRoleEnum } from 'api'; const colSizes = { xs: 24, sm: 18, md: 12, lg: 10 }; @@ -46,8 +39,9 @@ const createUrlRule = (): Rule => { export function CrossCheckSubmit(props: CoursePageProps) { const [form] = Form.useForm(); const courseService = useMemo(() => new CourseService(props.course.id), [props.course.id]); + const teamDistributionApi = useMemo(() => new CoursesTasksApi(), []); const solutionReviewSettings = useSolutionReviewSettings(); - const [feedback, setFeedback] = useState(null); + const [feedback, setFeedback] = useState(null); const [submittedSolution, setSubmittedSolution] = useState(null as TaskSolution | null); const router = useRouter(); const queryTaskId = router.query.taskId ? +router.query.taskId : null; @@ -139,8 +133,8 @@ export function CrossCheckSubmit(props: CoursePageProps) { return; } - const [feedback, submittedSolution, taskDetails] = await Promise.all([ - courseService.getCrossCheckFeedback(props.session.githubId, courseTask.id), + const [{ data: feedback }, submittedSolution, taskDetails] = await Promise.all([ + teamDistributionApi.getMyCrossCheckFeedbacks(props.course.id, courseTask.id), courseService.getCrossCheckTaskSolution(props.session.githubId, courseTask.id).catch(() => null), courseService.getCrossCheckTaskDetails(courseTask.id), ]); diff --git a/client/src/modules/CrossCheck/components/CrossCheckCriteriaForm.tsx b/client/src/modules/CrossCheck/components/CrossCheckCriteriaForm.tsx index 6b8b2bb51b..73bc8ab8bc 100644 --- a/client/src/modules/CrossCheck/components/CrossCheckCriteriaForm.tsx +++ b/client/src/modules/CrossCheck/components/CrossCheckCriteriaForm.tsx @@ -5,7 +5,7 @@ import { isEqual } from 'lodash'; import { SubtaskCriteria } from './criteria/SubtaskCriteria'; import { TitleCriteria } from './criteria/TitleCriteria'; import { PenaltyCriteria } from './criteria/PenaltyCriteria'; -import { CrossCheckCriteriaData, SolutionReviewType } from 'services/course'; +import { CrossCheckCriteriaDataDto, CrossCheckSolutionReviewDto } from 'api'; const { Text, Title } = Typography; @@ -18,9 +18,9 @@ export interface CriteriaFormProps { maxScore: number | undefined; score: number; setScore: (value: number) => void; - criteriaData: CrossCheckCriteriaData[]; - setCriteriaData: (newData: CrossCheckCriteriaData[]) => void; - initialData: SolutionReviewType; + criteriaData: CrossCheckCriteriaDataDto[]; + setCriteriaData: (newData: CrossCheckCriteriaDataDto[]) => void; + initialData: CrossCheckSolutionReviewDto; setIsSkipped: (value: boolean) => void; isSkipped: boolean; } @@ -39,7 +39,7 @@ export function CrossCheckCriteriaForm({ const maxScoreValue = maxScore ?? 100; const maxScoreLabel = maxScoreValue ? ` (Max ${maxScoreValue} points)` : ''; - const penaltyData: CrossCheckCriteriaData[] = + const penaltyData: CrossCheckCriteriaDataDto[] = criteriaData?.filter(item => item.type.toLowerCase() === TaskType.Penalty) ?? []; useEffect(() => { @@ -57,7 +57,7 @@ export function CrossCheckCriteriaForm({ } }, [criteriaData, initialData]); - function updateCriteriaData(updatedEntry: CrossCheckCriteriaData) { + function updateCriteriaData(updatedEntry: CrossCheckCriteriaDataDto) { const index = criteriaData.findIndex(item => item.key === updatedEntry.key); const updatedData = [...criteriaData]; updatedData.splice(index, 1, updatedEntry); @@ -112,10 +112,10 @@ export function CrossCheckCriteriaForm({ Criteria {criteriaData ?.filter( - (item: CrossCheckCriteriaData) => + (item: CrossCheckCriteriaDataDto) => item.type.toLowerCase() === TaskType.Title || item.type.toLowerCase() === TaskType.Subtask, ) - .map((item: CrossCheckCriteriaData) => { + .map((item: CrossCheckCriteriaDataDto) => { return item.type.toLowerCase() === TaskType.Title ? ( ) : ( @@ -127,7 +127,7 @@ export function CrossCheckCriteriaForm({ {!!penaltyData?.length && ( <> Penalty - {penaltyData?.map((item: CrossCheckCriteriaData) => ( + {penaltyData?.map((item: CrossCheckCriteriaDataDto) => ( ))} diff --git a/client/src/modules/CrossCheck/components/CrossCheckHistory.tsx b/client/src/modules/CrossCheck/components/CrossCheckHistory.tsx index 1d24fe9c08..b7c5e01f1e 100644 --- a/client/src/modules/CrossCheck/components/CrossCheckHistory.tsx +++ b/client/src/modules/CrossCheck/components/CrossCheckHistory.tsx @@ -1,16 +1,15 @@ import { Dispatch, SetStateAction } from 'react'; import { ClockCircleOutlined, EditFilled, EditOutlined } from '@ant-design/icons'; import { Button, Col, notification, Row, Spin, Tag, Timeline, Typography } from 'antd'; -import { SolutionReviewType } from 'services/course'; import { useSolutionReviewSettings } from 'modules/CrossCheck/hooks'; import { markdownLabel } from 'components/Forms/PreparedComment'; import { SolutionReview } from 'modules/CrossCheck/components/SolutionReview'; import { SolutionReviewSettingsPanel } from 'modules/CrossCheck/components/SolutionReviewSettingsPanel'; -import { CrossCheckMessageDtoRoleEnum } from 'api'; +import { CrossCheckMessageDtoRoleEnum, CrossCheckSolutionReviewDto } from 'api'; type CrossCheckHistoryState = { loading: boolean; - data: SolutionReviewType[]; + data: CrossCheckSolutionReviewDto[]; }; type Props = { diff --git a/client/src/modules/CrossCheck/components/SolutionReview/SolutionReview.tsx b/client/src/modules/CrossCheck/components/SolutionReview/SolutionReview.tsx index 8343c8ce26..1e261c3154 100644 --- a/client/src/modules/CrossCheck/components/SolutionReview/SolutionReview.tsx +++ b/client/src/modules/CrossCheck/components/SolutionReview/SolutionReview.tsx @@ -4,7 +4,7 @@ import PreparedComment, { markdownLabel } from 'components/Forms/PreparedComment import { ScoreIcon } from 'components/Icons/ScoreIcon'; import { SolutionReviewSettings } from 'modules/CrossCheck/constants'; import { useEffect, useMemo, useState } from 'react'; -import { CourseService, CrossCheckCriteriaData, SolutionReviewType } from 'services/course'; +import { CourseService } from 'services/course'; import { formatDateTime } from 'services/formatter'; import { CrossCheckCriteriaModal } from '../criteria/CrossCheckCriteriaModal'; import { StudentDiscord } from '../../../../components/StudentDiscord'; @@ -13,7 +13,7 @@ import { Message } from './Message'; import { MessageSendingPanel } from './MessageSendingPanel'; import { UserAvatar } from './UserAvatar'; import { Username } from './Username'; -import { CrossCheckMessageDtoRoleEnum } from 'api'; +import { CrossCheckCriteriaDataDto, CrossCheckMessageDtoRoleEnum, CrossCheckSolutionReviewDto } from 'api'; const { Text } = Typography; @@ -25,7 +25,7 @@ export type SolutionReviewProps = { reviewNumber: number; settings: SolutionReviewSettings; courseTaskId: number | null; - review: SolutionReviewType; + review: CrossCheckSolutionReviewDto; isActiveReview: boolean; isMessageSendingPanelVisible?: boolean; currentRole: CrossCheckMessageDtoRoleEnum; @@ -50,9 +50,9 @@ function SolutionReview(props: SolutionReviewProps) { const { id, dateTime, author, comment, score, messages, criteria } = review; const [isModalVisible, setIsModalVisible] = useState(false); - const [modalData, setModaldata] = useState([]); + const [modalData, setModaldata] = useState([]); - const showModal = (modalData: CrossCheckCriteriaData[]) => { + const showModal = (modalData: CrossCheckCriteriaDataDto[]) => { setIsModalVisible(true); setModaldata(modalData); }; @@ -219,18 +219,18 @@ function SolutionReview(props: SolutionReviewProps) { diff --git a/client/src/modules/CrossCheck/components/criteria/CrossCheckCriteria.tsx b/client/src/modules/CrossCheck/components/criteria/CrossCheckCriteria.tsx new file mode 100644 index 0000000000..a502409af9 --- /dev/null +++ b/client/src/modules/CrossCheck/components/criteria/CrossCheckCriteria.tsx @@ -0,0 +1,74 @@ +import { Typography } from 'antd'; +import { TaskType } from '../CrossCheckCriteriaForm'; +import { CrossCheckCriteriaDataDto } from 'api'; + +const { Text, Title } = Typography; + +type Props = { + criteria: CrossCheckCriteriaDataDto[] | null; +}; + +export function CrossCheckCriteria({ criteria }: Props) { + if (!criteria?.length) return null; + const penaltyData = criteria.filter( + criteriaItem => criteriaItem.type.toLocaleLowerCase() === TaskType.Penalty && criteriaItem.point, + ); + + return ( + <> + {criteria + .filter(criteriaItem => criteriaItem.type.toLocaleLowerCase() === TaskType.Subtask) + .map(criteriaItem => ( +
+
+ {criteriaItem.text} +
+ + {criteriaItem.textComment && ( +
+ Comment: + {criteriaItem.textComment?.split('\n').map((textLine, k) => ( +

+ {textLine} +

+ ))} +
+ )} +
+ Points for criteria: {`${criteriaItem.point ?? 0}/${criteriaItem.max}`} +
+
+ ))} + {penaltyData?.length ? ( +
+ Penalty + {penaltyData?.map(criteriaItem => ( +
+ + {criteriaItem.text} {criteriaItem.point ?? 0} + +
+ ))} +
+ ) : null} + + ); +} diff --git a/client/src/modules/CrossCheck/components/criteria/CrossCheckCriteriaModal.tsx b/client/src/modules/CrossCheck/components/criteria/CrossCheckCriteriaModal.tsx index ffcd950309..8541b93c01 100644 --- a/client/src/modules/CrossCheck/components/criteria/CrossCheckCriteriaModal.tsx +++ b/client/src/modules/CrossCheck/components/criteria/CrossCheckCriteriaModal.tsx @@ -1,11 +1,9 @@ -import { Modal, Typography } from 'antd'; -import { CrossCheckCriteriaData } from 'services/course'; -import { TaskType } from '../CrossCheckCriteriaForm'; - -const { Text, Title } = Typography; +import { Modal } from 'antd'; +import { CrossCheckCriteria } from './CrossCheckCriteria'; +import { CrossCheckCriteriaDataDto } from 'api'; type Props = { - modalInfo: CrossCheckCriteriaData[] | null; + modalInfo: CrossCheckCriteriaDataDto[] | null; isModalVisible: boolean; showModal: (isModalVisible: boolean) => void; }; @@ -19,65 +17,9 @@ export function CrossCheckCriteriaModal({ modalInfo, isModalVisible, showModal } showModal(false); }; - const penaltyData = modalInfo?.filter( - criteriaItem => criteriaItem.type.toLocaleLowerCase() === TaskType.Penalty && criteriaItem.point, - ); - return ( - {modalInfo - ?.filter(criteriaItem => criteriaItem.type.toLocaleLowerCase() === TaskType.Subtask) - .map(criteriaItem => ( -
-
- {criteriaItem.text} -
- - {criteriaItem.textComment && ( -
- Comment: - {criteriaItem.textComment?.split('\n').map((textLine, k) => ( -

- {textLine} -

- ))} -
- )} -
- Points for criteria: {`${criteriaItem.point ?? 0}/${criteriaItem.max}`} -
-
- ))} - {penaltyData?.length ? ( -
- Penalty - {penaltyData?.map(criteriaItem => ( -
- - {criteriaItem.text} {criteriaItem.point ?? 0} - -
- ))} -
- ) : null} +
); } diff --git a/client/src/modules/CrossCheck/components/criteria/PenaltyCriteria.tsx b/client/src/modules/CrossCheck/components/criteria/PenaltyCriteria.tsx index 04819451da..ac7502d6fa 100644 --- a/client/src/modules/CrossCheck/components/criteria/PenaltyCriteria.tsx +++ b/client/src/modules/CrossCheck/components/criteria/PenaltyCriteria.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Radio, RadioChangeEvent, Typography } from 'antd'; -import { CrossCheckCriteriaData } from 'services/course'; +import { CrossCheckCriteriaDataDto } from 'api'; const { Text } = Typography; const { Group } = Radio; @@ -11,8 +11,8 @@ enum HasPenalty { } interface PenaltyCriteriaProps { - penaltyData: CrossCheckCriteriaData; - updateCriteriaData: (updatedEntry: CrossCheckCriteriaData) => void; + penaltyData: CrossCheckCriteriaDataDto; + updateCriteriaData: (updatedEntry: CrossCheckCriteriaDataDto) => void; } export function PenaltyCriteria({ penaltyData, updateCriteriaData }: PenaltyCriteriaProps) { diff --git a/client/src/modules/CrossCheck/components/criteria/SubtaskCriteria.tsx b/client/src/modules/CrossCheck/components/criteria/SubtaskCriteria.tsx index c33f9792e6..07b24022b5 100644 --- a/client/src/modules/CrossCheck/components/criteria/SubtaskCriteria.tsx +++ b/client/src/modules/CrossCheck/components/criteria/SubtaskCriteria.tsx @@ -1,15 +1,15 @@ import { Input, Typography, InputNumber, Slider } from 'antd'; import { useMemo } from 'react'; -import { CrossCheckCriteriaData } from 'services/course'; import isUndefined from 'lodash/isUndefined'; import isNil from 'lodash/isNil'; +import { CrossCheckCriteriaDataDto } from 'api'; const { TextArea } = Input; const { Text } = Typography; interface SubtaskCriteriaProps { - subtaskData: CrossCheckCriteriaData; - updateCriteriaData: (updatedEntry: CrossCheckCriteriaData) => void; + subtaskData: CrossCheckCriteriaDataDto; + updateCriteriaData: (updatedEntry: CrossCheckCriteriaDataDto) => void; } export function SubtaskCriteria({ subtaskData, updateCriteriaData }: SubtaskCriteriaProps) { diff --git a/client/src/modules/CrossCheck/components/criteria/TitleCriteria.tsx b/client/src/modules/CrossCheck/components/criteria/TitleCriteria.tsx index e50737f70e..6950d039d4 100644 --- a/client/src/modules/CrossCheck/components/criteria/TitleCriteria.tsx +++ b/client/src/modules/CrossCheck/components/criteria/TitleCriteria.tsx @@ -1,9 +1,9 @@ import { Typography } from 'antd'; +import { CrossCheckCriteriaDataDto } from 'api'; import React from 'react'; -import { CrossCheckCriteriaData } from 'services/course'; interface TitleCriteriaProps { - titleData: CrossCheckCriteriaData; + titleData: CrossCheckCriteriaDataDto; } const { Text } = Typography; diff --git a/client/src/components/BadReview/BadReviewControllers.tsx b/client/src/modules/CrossCheckPairs/components/BadReview/BadReviewControllers.tsx similarity index 100% rename from client/src/components/BadReview/BadReviewControllers.tsx rename to client/src/modules/CrossCheckPairs/components/BadReview/BadReviewControllers.tsx diff --git a/client/src/components/BadReview/BadReviewTable.tsx b/client/src/modules/CrossCheckPairs/components/BadReview/BadReviewTable.tsx similarity index 100% rename from client/src/components/BadReview/BadReviewTable.tsx rename to client/src/modules/CrossCheckPairs/components/BadReview/BadReviewTable.tsx diff --git a/client/src/modules/CrossCheckPairs/components/CrossCheckPairsTable/CrossCheckPairsTable.tsx b/client/src/modules/CrossCheckPairs/components/CrossCheckPairsTable/CrossCheckPairsTable.tsx new file mode 100644 index 0000000000..26e7760331 --- /dev/null +++ b/client/src/modules/CrossCheckPairs/components/CrossCheckPairsTable/CrossCheckPairsTable.tsx @@ -0,0 +1,66 @@ +import { Table, TablePaginationConfig } from 'antd'; +import { CrossCheckPairDto } from 'api'; +import { FilterValue, SorterResult } from 'antd/lib/table/interface'; +import { + CustomColumnType, + fields, + getCrossCheckPairsColumns, +} from 'modules/CrossCheckPairs/data/getCrossCheckPairsColumns'; +import css from 'styled-jsx/css'; + +export type Filters = Omit; + +interface CustomSorterResult extends SorterResult { + column?: CustomColumnType; +} + +export type Sorter = CustomSorterResult | CustomSorterResult[]; + +type CrossCheckTableProps = { + loaded: boolean; + crossCheckPairs: CrossCheckPairDto[]; + pagination: TablePaginationConfig; + onChange: ( + pagination: TablePaginationConfig, + filters: Record, + sorter: Sorter, + ) => void; + viewComment: (value: CrossCheckPairDto) => void; +}; + +export const CrossCheckPairsTable = ({ + loaded, + crossCheckPairs, + pagination, + onChange, + viewComment, +}: CrossCheckTableProps) => { + if (!loaded) return null; + + // where 800 is approximate sum of basic columns (GitHub, Name, etc.) + const tableWidth = 800; + return ( + <> + + className="table-score" + showHeader + scroll={{ x: tableWidth, y: 'calc(100vh - 250px)' }} + pagination={pagination} + dataSource={crossCheckPairs} + size="small" + rowClassName={'cross-check-table-row'} + onChange={onChange} + key="id" + columns={getCrossCheckPairsColumns(viewComment)} + /> + + + ); +}; + +const styles = css` + :global(.cross-check-table-row, .table-score td, .table-score th) { + padding: 0 5px !important; + font-size: 11px; + } +`; diff --git a/client/src/modules/CrossCheckPairs/data/getCrossCheckPairsColumns.tsx b/client/src/modules/CrossCheckPairs/data/getCrossCheckPairsColumns.tsx new file mode 100644 index 0000000000..979dcf0df4 --- /dev/null +++ b/client/src/modules/CrossCheckPairs/data/getCrossCheckPairsColumns.tsx @@ -0,0 +1,114 @@ +import { CrossCheckPairDto } from 'api'; +import { ColumnType } from 'antd/lib/table/interface'; +import { omit } from 'lodash'; +import { dateTimeRenderer, getColumnSearchProps } from 'components/Table'; +import { GithubAvatar } from 'components/GithubAvatar'; +import { Button } from 'antd'; + +export const fields = { + task: 'task', + checker: 'checker', + student: 'student', + url: 'url', + score: 'score', + submittedDate: 'submittedDate', + reviewedDate: 'reviewedDate', +}; + +export interface CustomColumnType extends ColumnType { + sorterField?: string; +} + +const renderGithubLink = (value: string) => + value ? ( +
+ +   + + {value} + +
+ ) : null; + +export const getCrossCheckPairsColumns = ( + viewComment: (value: CrossCheckPairDto) => void, +): CustomColumnType[] => [ + { + title: 'Task', + fixed: 'left', + dataIndex: ['task', 'name'], + key: fields.task, + width: 100, + sorter: true, + sorterField: 'task', + ...omit(getColumnSearchProps(['task', 'name']), 'onFilter'), + }, + { + title: 'Checker', + fixed: 'left', + key: fields.checker, + dataIndex: ['checker', 'githubId'], + sorter: true, + sorterField: 'checker', + width: 150, + render: renderGithubLink, + ...omit(getColumnSearchProps(['checkerStudent', 'githubId']), 'onFilter'), + }, + { + title: 'Student', + key: fields.student, + dataIndex: ['student', 'githubId'], + sorter: true, + sorterField: 'student', + width: 150, + render: renderGithubLink, + ...omit(getColumnSearchProps(['student', 'githubId']), 'onFilter'), + }, + { + title: 'Url', + dataIndex: 'url', + key: fields.url, + width: 150, + sorter: true, + sorterField: 'url', + ...getColumnSearchProps('url'), + }, + { + title: 'Score', + dataIndex: 'score', + key: fields.score, + width: 80, + sorter: true, + sorterField: 'score', + render: value => <>{value ?? '(Empty)'}, + }, + { + title: 'Submitted Date', + dataIndex: 'submittedDate', + key: fields.submittedDate, + width: 80, + sorter: true, + sorterField: 'submittedDate', + render: dateTimeRenderer, + }, + { + title: 'Reviewed Date', + dataIndex: 'reviewedDate', + key: fields.reviewedDate, + width: 80, + sorter: true, + sorterField: 'reviewedDate', + render: (_, record) => dateTimeRenderer(record.reviewedDate), + }, + { + title: 'Comment', + dataIndex: 'comment', + key: 'comment', + width: 60, + render: (_, record) => ( + + ), + }, +]; diff --git a/client/src/modules/CrossCheckPairs/pages/CrossCheckPairs/CrossCheckPairs.tsx b/client/src/modules/CrossCheckPairs/pages/CrossCheckPairs/CrossCheckPairs.tsx new file mode 100644 index 0000000000..fc1227b225 --- /dev/null +++ b/client/src/modules/CrossCheckPairs/pages/CrossCheckPairs/CrossCheckPairs.tsx @@ -0,0 +1,172 @@ +import { Collapse, Modal, Space, TablePaginationConfig } from 'antd'; +import { Comment } from '@ant-design/compatible'; +import { FilterValue } from 'antd/lib/table/interface'; +import { IPaginationInfo } from 'common/types/pagination'; +import { AdminPageLayout } from 'components/PageLayout'; +import { dateTimeRenderer } from 'components/Table'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { CourseService, CourseTaskDetails } from 'services/course'; +import { CoursePageProps } from 'services/models'; +import { CoursesTasksApi, CrossCheckMessageDtoRoleEnum, CrossCheckPairDto } from 'api'; +import PreparedComment from 'components/Forms/PreparedComment'; +import { Message } from 'modules/CrossCheck/components/SolutionReview/Message'; +import { CrossCheckCriteria } from 'modules/CrossCheck/components/criteria/CrossCheckCriteria'; +import { BadReviewControllers } from 'modules/CrossCheckPairs/components/BadReview/BadReviewControllers'; +import { + Filters, + Sorter, + CrossCheckPairsTable, +} from 'modules/CrossCheckPairs/components/CrossCheckPairsTable/CrossCheckPairsTable'; + +enum OrderDirection { + ASC = 'ASC', + DESC = 'DESC', +} + +const DEFAULT_ORDER_BY = 'task'; +const DEFAULT_ORDER_DIRECTION = OrderDirection.ASC; + +const api = new CoursesTasksApi(); + +export default function Page(props: CoursePageProps) { + const [modal, contextHolder] = Modal.useModal(); + const courseId = props.course.id; + const courseService = useMemo(() => new CourseService(courseId), [props.course]); + + const [loading, setLoading] = useState(false); + const [courseTasks, setCourseTasks] = useState([]); + const [crossCheckList, setCrossCheckList] = useState({ + content: [] as CrossCheckPairDto[], + pagination: { current: 1, pageSize: 50 } as IPaginationInfo, + orderBy: { field: DEFAULT_ORDER_BY, order: DEFAULT_ORDER_DIRECTION }, + }); + const [loaded, setLoaded] = useState(false); + + const loadInitialData = useCallback(async () => { + try { + setLoading(true); + const [{ data: crossCheckData }, tasksFromReq] = await Promise.all([ + api.getCrossCheckPairs( + courseId, + crossCheckList.pagination.pageSize, + crossCheckList.pagination.current, + crossCheckList.orderBy.field, + crossCheckList.orderBy.order, + ), + courseService.getCourseTasksDetails(), + ]); + setCourseTasks(tasksFromReq.filter(task => task.pairsCount)); + setCrossCheckList({ + content: crossCheckData.items, + pagination: crossCheckData.pagination, + orderBy: crossCheckList.orderBy, + }); + setLoaded(true); + } finally { + setLoading(false); + } + }, []); + + const getCourseScore = useCallback( + async ( + pagination: TablePaginationConfig, + filters: Record, + sorter: Sorter, + ) => { + if (Array.isArray(sorter)) { + return; + } + + const orderBy = { + field: sorter.column?.sorterField ?? DEFAULT_ORDER_BY, + order: sorter.order === 'descend' ? OrderDirection.DESC : OrderDirection.ASC, + }; + + setLoading(true); + try { + const { data } = await api.getCrossCheckPairs( + courseId, + pagination.pageSize as IPaginationInfo['pageSize'], + pagination.current as IPaginationInfo['current'], + orderBy.field, + orderBy.order, + filters.checker?.toString(), + filters.student?.toString(), + filters.url?.toString(), + filters.task?.toString(), + ); + setCrossCheckList({ + content: data.items, + pagination: data.pagination, + orderBy: { + field: orderBy.field, + order: orderBy.order, + }, + }); + } finally { + setLoading(false); + } + }, + [crossCheckList.content], + ); + + useEffect(() => { + loadInitialData(); + }, []); + + const handleViewComment = ({ historicalScores, checker, messages }: CrossCheckPairDto) => { + modal.info({ + width: 1020, + title: `Comment from ${checker.githubId}`, + content: historicalScores.map((historicalScore, index) => ( + + + {dateTimeRenderer(historicalScore.dateTime)} + + {index === 0 && + messages.length > 0 && + messages.map(message => ( + + ))} + + } + /> + {historicalScore.criteria?.length ? ( + + + + + + ) : null} + + )), + }); + }; + + return ( + + {contextHolder} + + + + ); +} diff --git a/client/src/modules/CrossCheckPairs/pages/CrossCheckPairs/index.ts b/client/src/modules/CrossCheckPairs/pages/CrossCheckPairs/index.ts new file mode 100644 index 0000000000..635de8d630 --- /dev/null +++ b/client/src/modules/CrossCheckPairs/pages/CrossCheckPairs/index.ts @@ -0,0 +1 @@ +export { default as CrossCheckPairs } from './CrossCheckPairs'; diff --git a/client/src/modules/Mentor/components/MentorEndorsement/MentorEndorsement.tsx b/client/src/modules/Mentor/components/MentorEndorsement/MentorEndorsement.tsx new file mode 100644 index 0000000000..c8cb3f253b --- /dev/null +++ b/client/src/modules/Mentor/components/MentorEndorsement/MentorEndorsement.tsx @@ -0,0 +1,75 @@ +import { Input, Modal, Spin, Typography } from 'antd'; +import { useMemo } from 'react'; +import { ProfileApi } from 'api'; +import { useAsync } from 'react-use'; +import isNull from 'lodash/isNull'; +import isObject from 'lodash/isObject'; +import omitBy from 'lodash/omitBy'; + +export interface Props { + githubId: string | null; + onClose: () => void; +} + +const api = new ProfileApi(); + +export function MentorEndorsement(props: Props) { + const { value, loading } = useAsync(async () => { + if (props.githubId) { + const { data } = await api.getEndorsement(props.githubId); + return data; + } + }, [props.githubId]); + + const data = useMemo(() => (value?.data ? cleanData(value.data) : null), [value?.data]); + return ( + + +
+ {value ? ( + <> + Generated Text + + {value?.summary.split('\n').map(i => ( + <> + {i} +
+ + ))} +
+ + ) : null} +
+ {data ? ( +
+ Data Model + +
+ ) : null} +
+
+ ); +} + +function removeNull(obj: object) { + return omitBy(obj, isNull); +} + +function cleanData(obj: object) { + const cleanedObj = removeNull(obj); + + // Recursively clean nested objects + for (const key in cleanedObj) { + if (isObject(cleanedObj[key])) { + cleanedObj[key] = cleanData(cleanedObj[key]); + } + } + + return cleanedObj; +} diff --git a/client/src/modules/Mentor/components/MentorEndorsement/index.tsx b/client/src/modules/Mentor/components/MentorEndorsement/index.tsx new file mode 100644 index 0000000000..afa9d9cea5 --- /dev/null +++ b/client/src/modules/Mentor/components/MentorEndorsement/index.tsx @@ -0,0 +1 @@ +export { MentorEndorsement } from './MentorEndorsement'; diff --git a/client/src/modules/Prompts/components/PromptModal.tsx b/client/src/modules/Prompts/components/PromptModal.tsx new file mode 100644 index 0000000000..63745961f9 --- /dev/null +++ b/client/src/modules/Prompts/components/PromptModal.tsx @@ -0,0 +1,52 @@ +import { Form, Input, InputNumber, message, Modal } from 'antd'; +import { useEffect } from 'react'; +import { PromptDto, PromptsApi } from 'api'; + +type Props = { + open: boolean; + onCancel: () => void; + loadData: () => Promise; + data?: PromptDto; +}; +const disciplineService = new PromptsApi(); + +export function PromptModal({ open, onCancel, loadData, data }: Props) { + const [form] = Form.useForm(); + + useEffect(() => form.resetFields, [open]); + + const initialValues = data ?? { temperature: 0.5 }; + + const submitForm = async () => { + try { + const value = await form.validateFields(); + + if (data) { + await disciplineService.updatePrompt(data.id, value); + } else { + await disciplineService.createPrompt(value); + } + + await loadData(); + onCancel(); + } catch (e) { + message.error('Something went wrong. Please try again later.'); + } + }; + + return ( + +
+ + + + + + + + + +
+
+ ); +} diff --git a/client/src/modules/Prompts/components/PromptTable.tsx b/client/src/modules/Prompts/components/PromptTable.tsx new file mode 100644 index 0000000000..52540ef2fa --- /dev/null +++ b/client/src/modules/Prompts/components/PromptTable.tsx @@ -0,0 +1,34 @@ +import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; +import { Button, Space, Table } from 'antd'; +import { PromptDto } from 'api'; + +const { Column } = Table; + +type Props = { + data: PromptDto[]; + handleUpdate: (record: PromptDto) => void; + handleDelete: (record: PromptDto) => Promise; +}; + +export const PromptTable = ({ data, handleDelete, handleUpdate }: Props) => { + return ( + + + ( + + + + + )} + /> +
+ ); +}; diff --git a/client/src/modules/Prompts/pages/PromptPage.tsx b/client/src/modules/Prompts/pages/PromptPage.tsx new file mode 100644 index 0000000000..a83a1dcc7d --- /dev/null +++ b/client/src/modules/Prompts/pages/PromptPage.tsx @@ -0,0 +1,55 @@ +import { Button, Layout, message } from 'antd'; +import { PromptDto, PromptsApi } from 'api'; +import { AdminPageLayout } from 'components/PageLayout'; +import { useModalForm } from 'hooks'; +import { SessionContext } from 'modules/Course/contexts'; +import { useCallback, useContext, useState } from 'react'; +import { useAsync } from 'react-use'; +import { PromptModal } from '../components/PromptModal'; +import { PromptTable } from '../components/PromptTable'; + +const api = new PromptsApi(); + +export const PromptsPage = () => { + const session = useContext(SessionContext); + const [prompts, setPrompts] = useState([] as PromptDto[]); + const { open, formData, toggle } = useModalForm(); + + const [loading, setLoading] = useState(false); + + const loadDisciplines = useCallback(async () => { + try { + setLoading(true); + const { data } = await api.getPrompts(); + setPrompts(data); + } catch (e) { + message.error('Something went wrong. Please try again later.'); + } finally { + setLoading(false); + } + }, []); + + const handleDelete = async (record: PromptDto) => { + await api.deletePrompt(record.id); + await loadDisciplines(); + }; + + const handleModalShowUpdate = (record: PromptDto) => { + toggle(record); + }; + + useAsync(loadDisciplines, []); + + return ( + + + + + + + toggle()} loadData={loadDisciplines} data={formData} /> + + ); +}; diff --git a/client/src/pages/admin/prompts.tsx b/client/src/pages/admin/prompts.tsx new file mode 100644 index 0000000000..216135bc7f --- /dev/null +++ b/client/src/pages/admin/prompts.tsx @@ -0,0 +1,10 @@ +import { SessionProvider } from 'modules/Course/contexts'; +import { PromptsPage } from 'modules/Prompts/pages/PromptPage'; + +export default function () { + return ( + + + + ); +} diff --git a/client/src/pages/course/admin/cross-check-table.tsx b/client/src/pages/course/admin/cross-check-table.tsx index 25b89d0503..72bceec908 100644 --- a/client/src/pages/course/admin/cross-check-table.tsx +++ b/client/src/pages/course/admin/cross-check-table.tsx @@ -1,336 +1,5 @@ -import { Button, Modal, Table, TablePaginationConfig } from 'antd'; -import { Comment } from '@ant-design/compatible'; -import { ColumnType, FilterValue, SorterResult } from 'antd/lib/table/interface'; -import { IPaginationInfo } from 'common/types/pagination'; -import { BadReviewControllers } from 'components/BadReview/BadReviewControllers'; -import { GithubAvatar } from 'components/GithubAvatar'; -import { AdminPageLayout } from 'components/PageLayout'; -import { dateTimeRenderer, getColumnSearchProps } from 'components/Table'; import withCourseData from 'components/withCourseData'; -import { withSession } from 'components/withSession'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { CourseService, CourseTaskDetails } from 'services/course'; -import { CoursePageProps } from 'services/models'; -import css from 'styled-jsx/css'; -import { CoursesTasksApi, CrossCheckMessageDtoRoleEnum, CrossCheckPairDto } from 'api'; -import PreparedComment from 'components/Forms/PreparedComment'; -import omit from 'lodash/omit'; -import { Message } from 'modules/CrossCheck/components/SolutionReview/Message'; +import withSession from 'components/withSession'; +import { CrossCheckPairs } from 'modules/CrossCheckPairs/pages/CrossCheckPairs'; -const fields = { - task: 'task', - checker: 'checker', - student: 'student', - url: 'url', - score: 'score', - submittedDate: 'submittedDate', - reviewedDate: 'reviewedDate', -}; - -interface CustomColumnType extends ColumnType { - sorterField?: string; -} - -interface CustomSorterResult extends SorterResult { - column?: CustomColumnType; -} - -type Sorter = CustomSorterResult | CustomSorterResult[]; - -type Filters = Omit; - -enum OrderDirection { - ASC = 'ASC', - DESC = 'DESC', -} - -const DEFAULT_ORDER_BY = 'task'; -const DEFAULT_ORDER_DIRECTION = OrderDirection.ASC; - -const api = new CoursesTasksApi(); - -export function Page(props: CoursePageProps) { - const [modal, contextHolder] = Modal.useModal(); - const courseId = props.course.id; - const courseService = useMemo(() => new CourseService(courseId), [props.course]); - - const [loading, setLoading] = useState(false); - const [courseTasks, setCourseTasks] = useState([]); - const [crossCheckList, setCrossCheckList] = useState({ - content: [] as CrossCheckPairDto[], - pagination: { current: 1, pageSize: 100 } as IPaginationInfo, - orderBy: { field: DEFAULT_ORDER_BY, order: DEFAULT_ORDER_DIRECTION }, - }); - const [loaded, setLoaded] = useState(false); - - const loadInitialData = useCallback(async () => { - try { - setLoading(true); - const [{ data: crossCheckData }, tasksFromReq] = await Promise.all([ - api.getCrossCheckPairs( - courseId, - crossCheckList.pagination.pageSize, - crossCheckList.pagination.current, - crossCheckList.orderBy.field, - crossCheckList.orderBy.order, - ), - courseService.getCourseTasksDetails(), - ]); - setCourseTasks(tasksFromReq.filter(task => task.pairsCount)); - setCrossCheckList({ - content: crossCheckData.items, - pagination: crossCheckData.pagination, - orderBy: crossCheckList.orderBy, - }); - setLoaded(true); - } finally { - setLoading(false); - } - }, []); - - const getCourseScore = useCallback( - async ( - pagination: TablePaginationConfig, - filters: Record, - sorter: Sorter, - ) => { - if (Array.isArray(sorter)) { - return; - } - - const orderBy = { - field: sorter.column?.sorterField ?? DEFAULT_ORDER_BY, - order: sorter.order === 'descend' ? OrderDirection.DESC : OrderDirection.ASC, - }; - - setLoading(true); - try { - const { data } = await api.getCrossCheckPairs( - courseId, - pagination.pageSize as IPaginationInfo['pageSize'], - pagination.current as IPaginationInfo['current'], - orderBy.field, - orderBy.order, - filters.checker?.toString(), - filters.student?.toString(), - filters.url?.toString(), - filters.task?.toString(), - ); - setCrossCheckList({ - content: data.items, - pagination: data.pagination, - orderBy: { - field: orderBy.field, - order: orderBy.order, - }, - }); - } finally { - setLoading(false); - } - }, - [crossCheckList.content], - ); - - useEffect(() => { - loadInitialData(); - }, []); - - return ( - - {contextHolder} - - {renderTable( - loaded, - crossCheckList.content, - crossCheckList.pagination, - getCourseScore, - ({ historicalScores, checker, messages }) => { - modal.info({ - width: 1020, - title: `Comment from ${checker.githubId}`, - content: historicalScores.map((historicalScore, index) => ( - - {dateTimeRenderer(historicalScore.dateTime)} - - {index === 0 && - messages.length > 0 && - messages.map(message => ( - - ))} - - } - /> - )), - }); - }, - )} - - - ); -} - -const getColumns = (viewComment: (value: CrossCheckPairDto) => void): CustomColumnType[] => [ - { - title: 'Task', - fixed: 'left', - dataIndex: ['task', 'name'], - key: fields.task, - width: 100, - sorter: true, - sorterField: 'task', - ...omit(getColumnSearchProps(['task', 'name']), 'onFilter'), - }, - { - title: 'Checker', - fixed: 'left', - key: fields.checker, - dataIndex: ['checker', 'githubId'], - sorter: true, - sorterField: 'checker', - width: 150, - render: (value: string) => ( -
- {value ? ( - <> - -   - - {value} - - - ) : null} -
- ), - ...omit(getColumnSearchProps(['checkerStudent', 'githubId']), 'onFilter'), - }, - { - title: 'Student', - key: fields.student, - dataIndex: ['student', 'githubId'], - sorter: true, - sorterField: 'student', - width: 150, - render: (value: string) => ( -
- {value ? ( - <> - -   - - {value} - - - ) : null} -
- ), - ...omit(getColumnSearchProps(['student', 'githubId']), 'onFilter'), - }, - { - title: 'Url', - dataIndex: 'url', - key: fields.url, - width: 150, - sorter: true, - sorterField: 'url', - ...getColumnSearchProps('url'), - }, - { - title: 'Score', - dataIndex: 'score', - key: fields.score, - width: 80, - sorter: true, - sorterField: 'score', - render: value => <>{value ?? '(Empty)'}, - }, - { - title: 'Submitted Date', - dataIndex: 'submittedDate', - key: fields.submittedDate, - width: 80, - sorter: true, - sorterField: 'submittedDate', - render: dateTimeRenderer, - }, - { - title: 'Reviewed Date', - dataIndex: 'reviewedDate', - key: fields.reviewedDate, - width: 80, - sorter: true, - sorterField: 'reviewedDate', - render: (_, record) => dateTimeRenderer(record.reviewedDate), - }, - { - title: 'Comment', - dataIndex: 'comment', - key: 'comment', - width: 60, - render: (_, record) => ( - - ), - }, -]; - -function renderTable( - loaded: boolean, - crossCheckPairs: CrossCheckPairDto[], - pagination: TablePaginationConfig, - handleChange: ( - pagination: TablePaginationConfig, - filters: Record, - sorter: Sorter, - ) => void, - viewComment: (value: CrossCheckPairDto) => void, -) { - if (!loaded) { - return null; - } - // where 800 is approximate sum of basic columns (GitHub, Name, etc.) - const tableWidth = 800; - return ( - - className="table-score" - showHeader - scroll={{ x: tableWidth, y: 'calc(100vh - 250px)' }} - pagination={pagination} - dataSource={crossCheckPairs} - onChange={handleChange} - key="id" - columns={getColumns(viewComment)} - /> - ); -} - -const styles = css` - :global(.rs-table-row-disabled) { - opacity: 0.25; - } - - :global(.table-score td, .table-score th) { - padding: 0 5px !important; - font-size: 11px; - } - - :global(.table-score td a) { - line-height: 24px; - } -`; - -export default withCourseData(withSession(Page)); +export default withCourseData(withSession(CrossCheckPairs)); diff --git a/client/src/pages/course/admin/mentors.tsx b/client/src/pages/course/admin/mentors.tsx index e477d310c5..4242aeaa71 100644 --- a/client/src/pages/course/admin/mentors.tsx +++ b/client/src/pages/course/admin/mentors.tsx @@ -1,15 +1,17 @@ -import { Button, Divider, message, Row, Statistic, Table, Popconfirm } from 'antd'; -import { withSession } from 'components/withSession'; +import { DownOutlined, FileExcelOutlined, SyncOutlined } from '@ant-design/icons'; +import { Button, Divider, Dropdown, MenuProps, Modal, Row, Space, Statistic, Table, message } from 'antd'; import { AdminPageLayout } from 'components/PageLayout'; import { AssignStudentModal } from 'components/Student'; -import { getColumnSearchProps, numberSorter, stringSorter, PersonCell } from 'components/Table'; +import { PersonCell, getColumnSearchProps, numberSorter, stringSorter } from 'components/Table'; import withCourseData from 'components/withCourseData'; +import { Session, withSession } from 'components/withSession'; +import { MentorEndorsement } from 'modules/Mentor/components/MentorEndorsement'; +import { MenuInfo } from 'rc-menu/lib/interface'; import { useMemo, useState } from 'react'; import { useAsync } from 'react-use'; import { CourseService, MentorDetails } from 'services/course'; import { relativeDays } from 'services/formatter'; import { CoursePageProps } from 'services/models'; -import { SyncOutlined, FileExcelOutlined } from '@ant-design/icons'; type Stats = { recordCount: number; @@ -17,11 +19,40 @@ type Stats = { students: { studentsGroupName: string; totalCount: number }[]; }; +function getItems(mentor: MentorDetails, session: Session): MenuProps['items'] { + return [ + { + label: 'Add student', + key: 'student', + disabled: !mentor.isActive, + }, + { + label: 'Expel', + key: 'expel', + disabled: !mentor.isActive, + }, + { + label: 'Restore', + key: 'restore', + disabled: mentor.isActive, + }, + session.isAdmin + ? { + label: 'Endorsement', + key: 'endorsment', + } + : null, + ].filter(Boolean) as MenuProps['items']; +} + function Page(props: CoursePageProps) { const courseId = props.course.id; const [loading, setLoading] = useState(false); const [stats, setStats] = useState(null as Stats | null); const [mentors, setMentors] = useState([] as MentorDetails[]); + const [endorsementGithubId, setEndorsementGithubId] = useState(null); + const [currentMentor, setCurrentMentor] = useState(null); + const [modal, contextHolder] = Modal.useModal(); const service = useMemo(() => new CourseService(courseId), [courseId]); @@ -98,6 +129,31 @@ function Page(props: CoursePageProps) { } }; + const handleMenuClick = async (menuItem: MenuInfo, mentor: MentorDetails) => { + switch (menuItem.key) { + case 'student': { + setCurrentMentor(mentor.githubId); + break; + } + case 'expel': { + modal.confirm({ + onOk: () => handleExpel(mentor), + title: 'Are you sure you want to expel this mentor?', + }); + break; + } + case 'restore': + modal.confirm({ + onOk: () => handleRestore(mentor), + title: 'Do you want to restore the mentor?', + }); + break; + case 'endorsment': + setEndorsementGithubId(mentor.githubId); + break; + } + }; + const exportToCsv = () => (window.location.href = `/api/course/${courseId}/mentors/details/csv`); return ( @@ -229,36 +285,32 @@ function Page(props: CoursePageProps) { render: (value: string) => (value ? `${relativeDays(value)} days ago` : null), }, { - title: 'Actions', dataIndex: 'actions', - render: (_: string, mentor: MentorDetails) => - mentor.isActive ? ( - <> - handleExpel(mentor)} - title="Do you want to exclude a mentor from this course?" - > - - - - - ) : ( - <> - handleRestore(mentor)} - title="Do you want to restore mentor for this course?" - > - - - - ), + width: 120, + render: (_: string, mentor: MentorDetails) => { + const items = getItems(mentor, props.session); + return ( + handleMenuClick(e, mentor) }}> + + + ); + }, }, ]} /> + setEndorsementGithubId(null)} githubId={endorsementGithubId} /> + setCurrentMentor(null)} + courseId={courseId} + open={Boolean(currentMentor)} + mentorGithuId={currentMentor} + /> + {contextHolder} ); } diff --git a/client/src/pages/course/student/cross-check-review.tsx b/client/src/pages/course/student/cross-check-review.tsx index 24bc847b75..58e9089a0f 100644 --- a/client/src/pages/course/student/cross-check-review.tsx +++ b/client/src/pages/course/student/cross-check-review.tsx @@ -1,6 +1,11 @@ import { EyeFilled, EyeInvisibleFilled } from '@ant-design/icons'; import { Button, Checkbox, Col, Form, message, Modal, Row, Typography } from 'antd'; -import { CrossCheckMessageDtoRoleEnum, TasksCriteriaApi } from 'api'; +import { + CrossCheckCriteriaDataDto, + CrossCheckMessageDtoRoleEnum, + CrossCheckSolutionReviewDto, + TasksCriteriaApi, +} from 'api'; import { CourseTaskSelect } from 'components/Forms'; import MarkdownInput from 'components/Forms/MarkdownInput'; import { markdownLabel } from 'components/Forms/PreparedComment'; @@ -16,7 +21,7 @@ import { CrossCheckHistory } from 'modules/CrossCheck/components/CrossCheckHisto import { useRouter } from 'next/router'; import { useEffect, useMemo, useState } from 'react'; import { useAsync, useLocalStorage } from 'react-use'; -import { CourseService, CrossCheckCriteriaData, CrossCheckStatus, SolutionReviewType } from 'services/course'; +import { CourseService, CrossCheckStatus } from 'services/course'; import { CoursePageProps } from 'services/models'; import { getQueryString } from 'utils/queryParams-utils'; @@ -44,12 +49,12 @@ function Page(props: CoursePageProps) { const [submissionDisabled, setSubmissionDisabled] = useState(true); const [historicalCommentSelected, setHistoricalCommentSelected] = useState(form.getFieldValue('comment')); const [isUsernameVisible = false, setIsUsernameVisible] = useLocalStorage(LocalStorage.IsUsernameVisible); - const [state, setState] = useState<{ loading: boolean; data: SolutionReviewType[] }>({ + const [state, setState] = useState<{ loading: boolean; data: CrossCheckSolutionReviewDto[] }>({ loading: false, data: [], }); - const [criteriaData, setCriteriaData] = useState([]); + const [criteriaData, setCriteriaData] = useState([]); const [score, setScore] = useState(0); const [isSkipped, setIsSkipped] = useState(false); diff --git a/client/src/services/check.ts b/client/src/services/check.ts index 5d79b7302a..50733caa9d 100644 --- a/client/src/services/check.ts +++ b/client/src/services/check.ts @@ -1,6 +1,6 @@ -import { checkType, IBadReview } from './../components/BadReview/BadReviewControllers'; import globalAxios, { AxiosInstance } from 'axios'; import { message } from 'antd'; +import { IBadReview, checkType } from 'modules/CrossCheckPairs/components/BadReview/BadReviewControllers'; type routesType = Exclude; diff --git a/client/src/services/course.ts b/client/src/services/course.ts index 9ac562d768..e4bb79def3 100644 --- a/client/src/services/course.ts +++ b/client/src/services/course.ts @@ -15,6 +15,7 @@ import { EventDto, CriteriaDto, CrossCheckMessageDto, + CrossCheckCriteriaDataDto, } from 'api'; import { optionalQueryString } from 'utils/optionalQueryString'; @@ -25,40 +26,12 @@ export enum CrossCheckStatus { } export type CrossCheckCriteriaType = 'title' | 'subtask' | 'penalty'; -export interface CrossCheckCriteriaData { - key: string; - max?: number; - text: string; - type: CrossCheckCriteriaType; - point?: number; - textComment?: string; -} export interface CrossCheckMessageAuthor { id: number; githubId: string; } -export type SolutionReviewType = { - id: number; - dateTime: number; - comment: string; - criteria?: CrossCheckCriteriaData[]; - author: { - id: number; - name: string; - githubId: string; - discord: Discord | null; - } | null; - score: number; - messages: CrossCheckMessageDto[]; -}; - -export type Feedback = { - url?: string; - reviews?: SolutionReviewType[]; -}; - export interface Verification { id: number; createdDate: string; @@ -371,7 +344,7 @@ export class CourseService { anonymous: boolean; review: CrossCheckReview[]; comments: CrossCheckComment[]; - criteria: CrossCheckCriteriaData[]; + criteria: CrossCheckCriteriaDataDto[]; }, ) { await this.axios.post(`/student/${githubId}/task/${courseTaskId}/cross-check/result`, data); @@ -391,7 +364,7 @@ export class CourseService { comment: string; dateTime: number; anonymous: boolean; - criteria: CrossCheckCriteriaData[]; + criteria: CrossCheckCriteriaDataDto[]; }[]; author: { id: number; @@ -531,13 +504,6 @@ export class CourseService { return result.data.data; } - async getCrossCheckFeedback(githubId: string, courseTaskId: number) { - const result = await this.axios.get<{ data: Feedback }>( - `/student/${githubId}/task/${courseTaskId}/cross-check/feedback`, - ); - return result.data.data; - } - async createCrossCheckDistribution(courseTaskId: number) { const result = await this.axios.post(`/task/${courseTaskId}/cross-check/distribution`); return result.data; diff --git a/docker-compose.yml b/docker-compose.yml index 7f777abc36..2679ea7735 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,6 +74,7 @@ services: RSSHCOOL_AWS_REST_API_KEY: ${RSSHCOOL_AWS_REST_API_KEY} RSSHCOOL_USERS_CLOUD_USERNAME: ${RSSHCOOL_USERS_CLOUD_USERNAME} RSSHCOOL_USERS_CLOUD_PASSWORD: ${RSSHCOOL_USERS_CLOUD_PASSWORD} + RSSHCOOL_OPENAI_API_KEY: ${RSSHCOOL_OPENAI_API_KEY} restart: on-failure networks: - shared-network diff --git a/nestjs/package-lock.json b/nestjs/package-lock.json index 2711810472..ac8dc0c6a5 100644 --- a/nestjs/package-lock.json +++ b/nestjs/package-lock.json @@ -25,10 +25,11 @@ "axios": "1.3.4", "cache-manager": "4.1.0", "class-transformer": "0.5.1", - "class-validator": "0.14.0", + "class-validator": "0.13.2", "cookie-parser": "1.4.6", "dayjs": "1.11.7", "dotenv": "16.0.3", + "eta": "^3.0.1", "express": "4.18.2", "handlebars": "4.7.7", "ical-generator": "3.6.1", @@ -37,6 +38,7 @@ "lodash": "4.17.21", "nanoid": "3.3.4", "nestjs-pino": "3.1.2", + "openai": "3.3.0", "passport": "0.6.0", "passport-custom": "1.1.1", "passport-github2": "0.1.12", @@ -55,10 +57,11 @@ "@nestjs/schematics": "9.0.4", "@nestjs/testing": "9.3.9", "@openapitools/openapi-generator-cli": "2.6.0", + "@total-typescript/ts-reset": "0.4.2", "@types/cache-manager": "4.0.2", "@types/cookie-parser": "1.4.3", "@types/express": "4.17.17", - "@types/jest": "29.4.0", + "@types/jest": "29.5.3", "@types/json2csv": "5.0.3", "@types/jsonwebtoken": "9.0.1", "@types/lodash": "4.14.191", @@ -72,12 +75,12 @@ "eslint": "8.35.0", "eslint-config-prettier": "8.5.0", "eslint-plugin-prettier": "4.2.1", - "jest": "^29.4.3", + "jest": "^29.6.1", "jest-junit": "15.0.0", "pino-pretty": "9.4.0", "prettier": "2.8.8", "supertest": "6.3.3", - "ts-jest": "29.0.5", + "ts-jest": "29.1.1", "ts-loader": "9.4.2", "ts-node": "10.9.1", "tsconfig-paths": "4.1.0", @@ -1741,9 +1744,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1972,12 +1975,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2074,12 +2077,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2404,16 +2407,16 @@ } }, "node_modules/@jest/console": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.4.3.tgz", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.1.tgz", + "integrity": "sha512-Aj772AYgwTSr5w8qnyoJ0eDYvN6bMsH3ORH1ivMotrInHLKdUz6BDlaEXHdM6kODaBIkNIyQGzsMvRdOv7VG7Q==", "dev": true, "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", "slash": "^3.0.0" }, "engines": { @@ -2421,37 +2424,37 @@ } }, "node_modules/@jest/core": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.4.3.tgz", - "integrity": "sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.1.tgz", + "integrity": "sha512-CcowHypRSm5oYQ1obz1wfvkjZZ2qoQlrKKvlfPwh5jUXVU12TWr2qMeH8chLMuTFzHh5a1g2yaqlqDICbr+ukQ==", "dev": true, "dependencies": { - "@jest/console": "^29.4.3", - "@jest/reporters": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.6.1", + "@jest/reporters": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.4.3", - "jest-config": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-message-util": "^29.4.3", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-resolve-dependencies": "^29.4.3", - "jest-runner": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "jest-watcher": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-resolve-dependencies": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", + "jest-watcher": "^29.6.1", "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -2468,37 +2471,37 @@ } }, "node_modules/@jest/environment": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.4.3.tgz", - "integrity": "sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^29.4.3" + "jest-mock": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.4.3.tgz", - "integrity": "sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-N5xlPrAYaRNyFgVf2s9Uyyvr795jnB6rObuPx4QFvNJz8aAjpZUDfO4bh5G/xuplMID8PrnuF1+SfSyDxhsgYg==", "dev": true, "dependencies": { - "expect": "^29.4.3", - "jest-snapshot": "^29.4.3" + "expect": "^29.6.1", + "jest-snapshot": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.4.3.tgz", - "integrity": "sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.1.tgz", + "integrity": "sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw==", "dev": true, "dependencies": { "jest-get-type": "^29.4.3" @@ -2508,49 +2511,49 @@ } }, "node_modules/@jest/fake-timers": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.4.3.tgz", - "integrity": "sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.4.3", - "jest-mock": "^29.4.3", - "jest-util": "^29.4.3" + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.4.3.tgz", - "integrity": "sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.1.tgz", + "integrity": "sha512-2VjpaGy78JY9n9370H8zGRCFbYVWwjY6RdDMhoJHa1sYfwe6XM/azGN0SjY8kk7BOZApIejQ1BFPyH7FPG0w3A==", "dev": true, "dependencies": { - "@jest/environment": "^29.4.3", - "@jest/expect": "^29.4.3", - "@jest/types": "^29.4.3", - "jest-mock": "^29.4.3" + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/types": "^29.6.1", + "jest-mock": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.4.3.tgz", - "integrity": "sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.1.tgz", + "integrity": "sha512-9zuaI9QKr9JnoZtFQlw4GREQbxgmNYXU6QuWtmuODvk5nvPUeBYapVR/VYMyi2WSx3jXTLJTJji8rN6+Cm4+FA==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/console": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -2562,9 +2565,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -2583,24 +2586,24 @@ } }, "node_modules/@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", + "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, @@ -2609,13 +2612,13 @@ } }, "node_modules/@jest/test-result": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.4.3.tgz", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.1.tgz", + "integrity": "sha512-Ynr13ZRcpX6INak0TPUukU8GWRfm/vAytE3JbJNGAvINySWYdfE7dGZMbk36oVuK4CigpbhMn8eg1dixZ7ZJOw==", "dev": true, "dependencies": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.6.1", + "@jest/types": "^29.6.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -2624,14 +2627,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.4.3.tgz", - "integrity": "sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.1.tgz", + "integrity": "sha512-oBkC36PCDf/wb6dWeQIhaviU0l5u6VCsXa119yqdUosYAt7/FbQU2M2UoziO3igj/HBDEgp57ONQ3fm0v9uyyg==", "dev": true, "dependencies": { - "@jest/test-result": "^29.4.3", + "@jest/test-result": "^29.6.1", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", + "jest-haste-map": "^29.6.1", "slash": "^3.0.0" }, "engines": { @@ -2639,22 +2642,22 @@ } }, "node_modules/@jest/transform": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.4.3.tgz", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.1.tgz", + "integrity": "sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", + "jest-haste-map": "^29.6.1", "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", + "jest-util": "^29.6.1", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -2665,12 +2668,12 @@ } }, "node_modules/@jest/types": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.4.3.tgz", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.0", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -2743,9 +2746,9 @@ "devOptional": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", @@ -3448,27 +3451,27 @@ "dev": true }, "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "dependencies": { - "@sinonjs/commons": "^2.0.0" + "@sinonjs/commons": "^3.0.0" } }, "node_modules/@smithy/protocol-http": { @@ -3499,6 +3502,12 @@ "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" }, + "node_modules/@total-typescript/ts-reset": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@total-typescript/ts-reset/-/ts-reset-0.4.2.tgz", + "integrity": "sha512-vqd7ZUDSrXFVT1n8b2kc3LnklncDQFPvR58yUS1kEP23/nHPAO9l1lMjUfnPrXYYk4Hj54rrLKMW5ipwk7k09A==", + "dev": true + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -3524,9 +3533,9 @@ "devOptional": true }, "node_modules/@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -3556,12 +3565,12 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", "dev": true, "dependencies": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "node_modules/@types/body-parser": { @@ -3687,9 +3696,9 @@ } }, "node_modules/@types/jest": { - "version": "29.4.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.4.0.tgz", - "integrity": "sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==", + "version": "29.5.3", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz", + "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -3816,9 +3825,9 @@ } }, "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "dev": true }, "node_modules/@types/qs": { @@ -3874,11 +3883,6 @@ "@types/superagent": "*" } }, - "node_modules/@types/validator": { - "version": "13.7.12", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.12.tgz", - "integrity": "sha512-YVtyAPqpefU+Mm/qqnOANW6IkqKpCSrarcyV269C8MA8Ux0dbkEuQwM/4CjL47kVEM2LgBef/ETfkH+c6+moFA==" - }, "node_modules/@types/yargs": { "version": "17.0.22", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz", @@ -4531,15 +4535,15 @@ } }, "node_modules/babel-jest": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.4.3.tgz", - "integrity": "sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.1.tgz", + "integrity": "sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A==", "dev": true, "dependencies": { - "@jest/transform": "^29.4.3", + "@jest/transform": "^29.6.1", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.4.3", + "babel-preset-jest": "^29.5.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -4568,9 +4572,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.3.tgz", - "integrity": "sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", "dev": true, "dependencies": { "@babel/template": "^7.3.3", @@ -4606,12 +4610,12 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.4.3.tgz", - "integrity": "sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "^29.4.3", + "babel-plugin-jest-hoist": "^29.5.0", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { @@ -5021,9 +5025,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, "node_modules/class-transformer": { @@ -5032,12 +5036,11 @@ "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, "node_modules/class-validator": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", - "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz", + "integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==", "dependencies": { - "@types/validator": "^13.7.10", - "libphonenumber-js": "^1.10.14", + "libphonenumber-js": "^1.9.43", "validator": "^13.7.0" } }, @@ -5177,9 +5180,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "node_modules/color-convert": { @@ -6059,6 +6062,17 @@ "node": ">=0.10.0" } }, + "node_modules/eta": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eta/-/eta-3.0.1.tgz", + "integrity": "sha512-yzx1qWLPVSHhOn0CnoODFtcnTrLlSS4ch/LWHfATwOM4hE8RfrD64aqHvWHUFwq7d1+DhcjM7Iin4L2HJK5egA==", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -6116,16 +6130,17 @@ } }, "node_modules/expect": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.4.3.tgz", - "integrity": "sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.4.3", + "@jest/expect-utils": "^29.6.1", + "@types/node": "*", "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3" + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7303,17 +7318,17 @@ } }, "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/istanbul-lib-source-maps": { @@ -7340,9 +7355,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -7361,15 +7376,15 @@ } }, "node_modules/jest": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.4.3.tgz", - "integrity": "sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.1.tgz", + "integrity": "sha512-Nirw5B4nn69rVUZtemCQhwxOBhm0nsp3hmtF4rzCeWD7BkjAXRIji7xWQfnTNbz9g0aVsBX6aZK3n+23LM6uDw==", "dev": true, "dependencies": { - "@jest/core": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/core": "^29.6.1", + "@jest/types": "^29.6.1", "import-local": "^3.0.2", - "jest-cli": "^29.4.3" + "jest-cli": "^29.6.1" }, "bin": { "jest": "bin/jest.js" @@ -7387,9 +7402,9 @@ } }, "node_modules/jest-changed-files": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.4.3.tgz", - "integrity": "sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", "dev": true, "dependencies": { "execa": "^5.0.0", @@ -7400,28 +7415,29 @@ } }, "node_modules/jest-circus": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.4.3.tgz", - "integrity": "sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.1.tgz", + "integrity": "sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.4.3", - "@jest/expect": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", + "jest-each": "^29.6.1", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", "p-limit": "^3.1.0", - "pretty-format": "^29.4.3", + "pretty-format": "^29.6.1", + "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -7430,21 +7446,21 @@ } }, "node_modules/jest-cli": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.4.3.tgz", - "integrity": "sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.1.tgz", + "integrity": "sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing==", "dev": true, "dependencies": { - "@jest/core": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/core": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", + "jest-config": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "prompts": "^2.0.1", "yargs": "^17.3.1" }, @@ -7464,31 +7480,31 @@ } }, "node_modules/jest-config": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.4.3.tgz", - "integrity": "sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.1.tgz", + "integrity": "sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.4.3", - "@jest/types": "^29.4.3", - "babel-jest": "^29.4.3", + "@jest/test-sequencer": "^29.6.1", + "@jest/types": "^29.6.1", + "babel-jest": "^29.6.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.4.3", - "jest-environment-node": "^29.4.3", + "jest-circus": "^29.6.1", + "jest-environment-node": "^29.6.1", "jest-get-type": "^29.4.3", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-runner": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.4.3", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -7509,15 +7525,15 @@ } }, "node_modules/jest-diff": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.4.3.tgz", - "integrity": "sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.1.tgz", + "integrity": "sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg==", "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.4.3", "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7536,33 +7552,33 @@ } }, "node_modules/jest-each": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.4.3.tgz", - "integrity": "sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.1.tgz", + "integrity": "sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ==", "dev": true, "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", "jest-get-type": "^29.4.3", - "jest-util": "^29.4.3", - "pretty-format": "^29.4.3" + "jest-util": "^29.6.1", + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.4.3.tgz", - "integrity": "sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.1.tgz", + "integrity": "sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.4.3", - "@jest/fake-timers": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^29.4.3", - "jest-util": "^29.4.3" + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7578,20 +7594,20 @@ } }, "node_modules/jest-haste-map": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.4.3.tgz", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.1.tgz", + "integrity": "sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig==", "dev": true, "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -7627,46 +7643,46 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.4.3.tgz", - "integrity": "sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz", + "integrity": "sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ==", "dev": true, "dependencies": { "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.4.3.tgz", - "integrity": "sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz", + "integrity": "sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.4.3", + "jest-diff": "^29.6.1", "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.4.3.tgz", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.1.tgz", + "integrity": "sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -7675,14 +7691,14 @@ } }, "node_modules/jest-mock": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.4.3.tgz", - "integrity": "sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-util": "^29.4.3" + "jest-util": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7715,17 +7731,17 @@ } }, "node_modules/jest-resolve": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.4.3.tgz", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.1.tgz", + "integrity": "sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", + "jest-haste-map": "^29.6.1", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -7735,43 +7751,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.3.tgz", - "integrity": "sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz", + "integrity": "sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw==", "dev": true, "dependencies": { "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.4.3" + "jest-snapshot": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.4.3.tgz", - "integrity": "sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.1.tgz", + "integrity": "sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ==", "dev": true, "dependencies": { - "@jest/console": "^29.4.3", - "@jest/environment": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.6.1", + "@jest/environment": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-leak-detector": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-util": "^29.4.3", - "jest-watcher": "^29.4.3", - "jest-worker": "^29.4.3", + "jest-environment-node": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-leak-detector": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-resolve": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-util": "^29.6.1", + "jest-watcher": "^29.6.1", + "jest-worker": "^29.6.1", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -7799,31 +7815,31 @@ } }, "node_modules/jest-runtime": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.4.3.tgz", - "integrity": "sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.4.3", - "@jest/fake-timers": "^29.4.3", - "@jest/globals": "^29.4.3", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.1.tgz", + "integrity": "sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/globals": "^29.6.1", + "@jest/source-map": "^29.6.0", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-mock": "^29.4.3", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -7832,47 +7848,44 @@ } }, "node_modules/jest-snapshot": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.4.3.tgz", - "integrity": "sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.1.tgz", + "integrity": "sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/babel__traverse": "^7.0.6", + "@jest/expect-utils": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.4.3", + "expect": "^29.6.1", "graceful-fs": "^4.2.9", - "jest-diff": "^29.4.3", + "jest-diff": "^29.6.1", "jest-get-type": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", "natural-compare": "^1.4.0", - "pretty-format": "^29.4.3", - "semver": "^7.3.5" + "pretty-format": "^29.6.1", + "semver": "^7.5.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.4.3.tgz", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -7884,17 +7897,17 @@ } }, "node_modules/jest-validate": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.4.3.tgz", - "integrity": "sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.1.tgz", + "integrity": "sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA==", "dev": true, "dependencies": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.4.3", "leven": "^3.1.0", - "pretty-format": "^29.4.3" + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7913,18 +7926,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.4.3.tgz", - "integrity": "sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.1.tgz", + "integrity": "sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA==", "dev": true, "dependencies": { - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.4.3", + "jest-util": "^29.6.1", "string-length": "^4.0.1" }, "engines": { @@ -7932,13 +7945,13 @@ } }, "node_modules/jest-worker": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.4.3.tgz", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.1.tgz", + "integrity": "sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.4.3", + "jest-util": "^29.6.1", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -8288,29 +8301,20 @@ } }, "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -8691,6 +8695,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.3.0.tgz", + "integrity": "sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==", + "dependencies": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + } + }, + "node_modules/openai/node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -9458,12 +9479,12 @@ } }, "node_modules/pretty-format": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.3.tgz", - "integrity": "sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.0", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -9549,6 +9570,22 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -9767,9 +9804,9 @@ } }, "node_modules/resolve.exports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.0.tgz", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, "engines": { "node": ">=10" @@ -9945,9 +9982,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -10620,9 +10657,9 @@ } }, "node_modules/ts-jest": { - "version": "29.0.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", - "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -10631,7 +10668,7 @@ "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" }, "bin": { @@ -10645,7 +10682,7 @@ "@jest/types": "^29.0.0", "babel-jest": "^29.0.0", "jest": "^29.0.0", - "typescript": ">=4.3" + "typescript": ">=4.3 <6" }, "peerDependenciesMeta": { "@babel/core": { @@ -12983,9 +13020,9 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true }, "@babel/helper-simple-access": { @@ -13156,12 +13193,12 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -13228,12 +13265,12 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/template": { @@ -13483,124 +13520,124 @@ "dev": true }, "@jest/console": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.4.3.tgz", - "integrity": "sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.1.tgz", + "integrity": "sha512-Aj772AYgwTSr5w8qnyoJ0eDYvN6bMsH3ORH1ivMotrInHLKdUz6BDlaEXHdM6kODaBIkNIyQGzsMvRdOv7VG7Q==", "dev": true, "requires": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", "slash": "^3.0.0" } }, "@jest/core": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.4.3.tgz", - "integrity": "sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.1.tgz", + "integrity": "sha512-CcowHypRSm5oYQ1obz1wfvkjZZ2qoQlrKKvlfPwh5jUXVU12TWr2qMeH8chLMuTFzHh5a1g2yaqlqDICbr+ukQ==", "dev": true, "requires": { - "@jest/console": "^29.4.3", - "@jest/reporters": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.6.1", + "@jest/reporters": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.4.3", - "jest-config": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-message-util": "^29.4.3", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-resolve-dependencies": "^29.4.3", - "jest-runner": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", - "jest-watcher": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-resolve-dependencies": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", + "jest-watcher": "^29.6.1", "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "strip-ansi": "^6.0.0" } }, "@jest/environment": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.4.3.tgz", - "integrity": "sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "requires": { - "@jest/fake-timers": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^29.4.3" + "jest-mock": "^29.6.1" } }, "@jest/expect": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.4.3.tgz", - "integrity": "sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-N5xlPrAYaRNyFgVf2s9Uyyvr795jnB6rObuPx4QFvNJz8aAjpZUDfO4bh5G/xuplMID8PrnuF1+SfSyDxhsgYg==", "dev": true, "requires": { - "expect": "^29.4.3", - "jest-snapshot": "^29.4.3" + "expect": "^29.6.1", + "jest-snapshot": "^29.6.1" } }, "@jest/expect-utils": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.4.3.tgz", - "integrity": "sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.1.tgz", + "integrity": "sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw==", "dev": true, "requires": { "jest-get-type": "^29.4.3" } }, "@jest/fake-timers": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.4.3.tgz", - "integrity": "sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "requires": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.4.3", - "jest-mock": "^29.4.3", - "jest-util": "^29.4.3" + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" } }, "@jest/globals": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.4.3.tgz", - "integrity": "sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.1.tgz", + "integrity": "sha512-2VjpaGy78JY9n9370H8zGRCFbYVWwjY6RdDMhoJHa1sYfwe6XM/azGN0SjY8kk7BOZApIejQ1BFPyH7FPG0w3A==", "dev": true, "requires": { - "@jest/environment": "^29.4.3", - "@jest/expect": "^29.4.3", - "@jest/types": "^29.4.3", - "jest-mock": "^29.4.3" + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/types": "^29.6.1", + "jest-mock": "^29.6.1" } }, "@jest/reporters": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.4.3.tgz", - "integrity": "sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.1.tgz", + "integrity": "sha512-9zuaI9QKr9JnoZtFQlw4GREQbxgmNYXU6QuWtmuODvk5nvPUeBYapVR/VYMyi2WSx3jXTLJTJji8rN6+Cm4+FA==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/console": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -13612,9 +13649,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -13622,66 +13659,66 @@ } }, "@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", "dev": true, "requires": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" } }, "@jest/source-map": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", + "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.15", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "@jest/test-result": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.4.3.tgz", - "integrity": "sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.1.tgz", + "integrity": "sha512-Ynr13ZRcpX6INak0TPUukU8GWRfm/vAytE3JbJNGAvINySWYdfE7dGZMbk36oVuK4CigpbhMn8eg1dixZ7ZJOw==", "dev": true, "requires": { - "@jest/console": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.6.1", + "@jest/types": "^29.6.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.4.3.tgz", - "integrity": "sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.1.tgz", + "integrity": "sha512-oBkC36PCDf/wb6dWeQIhaviU0l5u6VCsXa119yqdUosYAt7/FbQU2M2UoziO3igj/HBDEgp57ONQ3fm0v9uyyg==", "dev": true, "requires": { - "@jest/test-result": "^29.4.3", + "@jest/test-result": "^29.6.1", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", + "jest-haste-map": "^29.6.1", "slash": "^3.0.0" } }, "@jest/transform": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.4.3.tgz", - "integrity": "sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.1.tgz", + "integrity": "sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg==", "dev": true, "requires": { "@babel/core": "^7.11.6", - "@jest/types": "^29.4.3", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", + "jest-haste-map": "^29.6.1", "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", + "jest-util": "^29.6.1", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -13689,12 +13726,12 @@ } }, "@jest/types": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.4.3.tgz", - "integrity": "sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", "dev": true, "requires": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.0", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -13754,9 +13791,9 @@ "devOptional": true }, "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dev": true, "requires": { "@jridgewell/resolve-uri": "3.1.0", @@ -14188,27 +14225,27 @@ } }, "@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "requires": { - "@sinonjs/commons": "^2.0.0" + "@sinonjs/commons": "^3.0.0" } }, "@smithy/protocol-http": { @@ -14233,6 +14270,12 @@ "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" }, + "@total-typescript/ts-reset": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@total-typescript/ts-reset/-/ts-reset-0.4.2.tgz", + "integrity": "sha512-vqd7ZUDSrXFVT1n8b2kc3LnklncDQFPvR58yUS1kEP23/nHPAO9l1lMjUfnPrXYYk4Hj54rrLKMW5ipwk7k09A==", + "dev": true + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -14258,9 +14301,9 @@ "devOptional": true }, "@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, "requires": { "@babel/parser": "^7.20.7", @@ -14290,12 +14333,12 @@ } }, "@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", "dev": true, "requires": { - "@babel/types": "^7.3.0" + "@babel/types": "^7.20.7" } }, "@types/body-parser": { @@ -14421,9 +14464,9 @@ } }, "@types/jest": { - "version": "29.4.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.4.0.tgz", - "integrity": "sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==", + "version": "29.5.3", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz", + "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", "dev": true, "requires": { "expect": "^29.0.0", @@ -14550,9 +14593,9 @@ } }, "@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "dev": true }, "@types/qs": { @@ -14608,11 +14651,6 @@ "@types/superagent": "*" } }, - "@types/validator": { - "version": "13.7.12", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.12.tgz", - "integrity": "sha512-YVtyAPqpefU+Mm/qqnOANW6IkqKpCSrarcyV269C8MA8Ux0dbkEuQwM/4CjL47kVEM2LgBef/ETfkH+c6+moFA==" - }, "@types/yargs": { "version": "17.0.22", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz", @@ -15098,15 +15136,15 @@ } }, "babel-jest": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.4.3.tgz", - "integrity": "sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.1.tgz", + "integrity": "sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A==", "dev": true, "requires": { - "@jest/transform": "^29.4.3", + "@jest/transform": "^29.6.1", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.4.3", + "babel-preset-jest": "^29.5.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -15126,9 +15164,9 @@ } }, "babel-plugin-jest-hoist": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.3.tgz", - "integrity": "sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", "dev": true, "requires": { "@babel/template": "^7.3.3", @@ -15158,12 +15196,12 @@ } }, "babel-preset-jest": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.4.3.tgz", - "integrity": "sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^29.4.3", + "babel-plugin-jest-hoist": "^29.5.0", "babel-preset-current-node-syntax": "^1.0.0" } }, @@ -15450,9 +15488,9 @@ "dev": true }, "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, "class-transformer": { @@ -15461,12 +15499,11 @@ "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, "class-validator": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", - "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz", + "integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==", "requires": { - "@types/validator": "^13.7.10", - "libphonenumber-js": "^1.10.14", + "libphonenumber-js": "^1.9.43", "validator": "^13.7.0" } }, @@ -15568,9 +15605,9 @@ "dev": true }, "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "color-convert": { @@ -16239,6 +16276,11 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "eta": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eta/-/eta-3.0.1.tgz", + "integrity": "sha512-yzx1qWLPVSHhOn0CnoODFtcnTrLlSS4ch/LWHfATwOM4hE8RfrD64aqHvWHUFwq7d1+DhcjM7Iin4L2HJK5egA==" + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -16278,16 +16320,17 @@ "dev": true }, "expect": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.4.3.tgz", - "integrity": "sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g==", "dev": true, "requires": { - "@jest/expect-utils": "^29.4.3", + "@jest/expect-utils": "^29.6.1", + "@types/node": "*", "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3" + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1" } }, "express": { @@ -17137,13 +17180,13 @@ } }, "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "requires": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, @@ -17167,9 +17210,9 @@ } }, "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -17182,21 +17225,21 @@ "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==" }, "jest": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.4.3.tgz", - "integrity": "sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.1.tgz", + "integrity": "sha512-Nirw5B4nn69rVUZtemCQhwxOBhm0nsp3hmtF4rzCeWD7BkjAXRIji7xWQfnTNbz9g0aVsBX6aZK3n+23LM6uDw==", "dev": true, "requires": { - "@jest/core": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/core": "^29.6.1", + "@jest/types": "^29.6.1", "import-local": "^3.0.2", - "jest-cli": "^29.4.3" + "jest-cli": "^29.6.1" } }, "jest-changed-files": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.4.3.tgz", - "integrity": "sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", "dev": true, "requires": { "execa": "^5.0.0", @@ -17204,92 +17247,93 @@ } }, "jest-circus": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.4.3.tgz", - "integrity": "sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.1.tgz", + "integrity": "sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ==", "dev": true, "requires": { - "@jest/environment": "^29.4.3", - "@jest/expect": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", + "jest-each": "^29.6.1", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", "p-limit": "^3.1.0", - "pretty-format": "^29.4.3", + "pretty-format": "^29.6.1", + "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "jest-cli": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.4.3.tgz", - "integrity": "sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.1.tgz", + "integrity": "sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing==", "dev": true, "requires": { - "@jest/core": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/core": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", + "jest-config": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "prompts": "^2.0.1", "yargs": "^17.3.1" } }, "jest-config": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.4.3.tgz", - "integrity": "sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.1.tgz", + "integrity": "sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ==", "dev": true, "requires": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.4.3", - "@jest/types": "^29.4.3", - "babel-jest": "^29.4.3", + "@jest/test-sequencer": "^29.6.1", + "@jest/types": "^29.6.1", + "babel-jest": "^29.6.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.4.3", - "jest-environment-node": "^29.4.3", + "jest-circus": "^29.6.1", + "jest-environment-node": "^29.6.1", "jest-get-type": "^29.4.3", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-runner": "^29.4.3", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.4.3", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" } }, "jest-diff": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.4.3.tgz", - "integrity": "sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.1.tgz", + "integrity": "sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg==", "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^29.4.3", "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" + "pretty-format": "^29.6.1" } }, "jest-docblock": { @@ -17302,30 +17346,30 @@ } }, "jest-each": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.4.3.tgz", - "integrity": "sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.1.tgz", + "integrity": "sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ==", "dev": true, "requires": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", "jest-get-type": "^29.4.3", - "jest-util": "^29.4.3", - "pretty-format": "^29.4.3" + "jest-util": "^29.6.1", + "pretty-format": "^29.6.1" } }, "jest-environment-node": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.4.3.tgz", - "integrity": "sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.1.tgz", + "integrity": "sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ==", "dev": true, "requires": { - "@jest/environment": "^29.4.3", - "@jest/fake-timers": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^29.4.3", - "jest-util": "^29.4.3" + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" } }, "jest-get-type": { @@ -17335,12 +17379,12 @@ "dev": true }, "jest-haste-map": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.4.3.tgz", - "integrity": "sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.1.tgz", + "integrity": "sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig==", "dev": true, "requires": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", @@ -17348,8 +17392,8 @@ "fsevents": "^2.3.2", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.4.3", - "jest-util": "^29.4.3", - "jest-worker": "^29.4.3", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", "micromatch": "^4.0.4", "walker": "^1.0.8" } @@ -17375,53 +17419,53 @@ } }, "jest-leak-detector": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.4.3.tgz", - "integrity": "sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz", + "integrity": "sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ==", "dev": true, "requires": { "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" + "pretty-format": "^29.6.1" } }, "jest-matcher-utils": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.4.3.tgz", - "integrity": "sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz", + "integrity": "sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^29.4.3", + "jest-diff": "^29.6.1", "jest-get-type": "^29.4.3", - "pretty-format": "^29.4.3" + "pretty-format": "^29.6.1" } }, "jest-message-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.4.3.tgz", - "integrity": "sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.1.tgz", + "integrity": "sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.4.3", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "jest-mock": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.4.3.tgz", - "integrity": "sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "requires": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-util": "^29.4.3" + "jest-util": "^29.6.1" } }, "jest-pnp-resolver": { @@ -17438,57 +17482,57 @@ "dev": true }, "jest-resolve": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.4.3.tgz", - "integrity": "sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.1.tgz", + "integrity": "sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg==", "dev": true, "requires": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", + "jest-haste-map": "^29.6.1", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.4.3", - "jest-validate": "^29.4.3", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "jest-resolve-dependencies": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.3.tgz", - "integrity": "sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz", + "integrity": "sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw==", "dev": true, "requires": { "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.4.3" + "jest-snapshot": "^29.6.1" } }, "jest-runner": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.4.3.tgz", - "integrity": "sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.1.tgz", + "integrity": "sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ==", "dev": true, "requires": { - "@jest/console": "^29.4.3", - "@jest/environment": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/console": "^29.6.1", + "@jest/environment": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-leak-detector": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-runtime": "^29.4.3", - "jest-util": "^29.4.3", - "jest-watcher": "^29.4.3", - "jest-worker": "^29.4.3", + "jest-environment-node": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-leak-detector": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-resolve": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-util": "^29.6.1", + "jest-watcher": "^29.6.1", + "jest-worker": "^29.6.1", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -17512,74 +17556,71 @@ } }, "jest-runtime": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.4.3.tgz", - "integrity": "sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q==", - "dev": true, - "requires": { - "@jest/environment": "^29.4.3", - "@jest/fake-timers": "^29.4.3", - "@jest/globals": "^29.4.3", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.1.tgz", + "integrity": "sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/globals": "^29.6.1", + "@jest/source-map": "^29.6.0", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-mock": "^29.4.3", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.4.3", - "jest-snapshot": "^29.4.3", - "jest-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "jest-snapshot": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.4.3.tgz", - "integrity": "sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.1.tgz", + "integrity": "sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A==", "dev": true, "requires": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.4.3", - "@jest/transform": "^29.4.3", - "@jest/types": "^29.4.3", - "@types/babel__traverse": "^7.0.6", + "@jest/expect-utils": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.4.3", + "expect": "^29.6.1", "graceful-fs": "^4.2.9", - "jest-diff": "^29.4.3", + "jest-diff": "^29.6.1", "jest-get-type": "^29.4.3", - "jest-haste-map": "^29.4.3", - "jest-matcher-utils": "^29.4.3", - "jest-message-util": "^29.4.3", - "jest-util": "^29.4.3", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", "natural-compare": "^1.4.0", - "pretty-format": "^29.4.3", - "semver": "^7.3.5" + "pretty-format": "^29.6.1", + "semver": "^7.5.3" } }, "jest-util": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.4.3.tgz", - "integrity": "sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "requires": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -17588,17 +17629,17 @@ } }, "jest-validate": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.4.3.tgz", - "integrity": "sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.1.tgz", + "integrity": "sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA==", "dev": true, "requires": { - "@jest/types": "^29.4.3", + "@jest/types": "^29.6.1", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.4.3", "leven": "^3.1.0", - "pretty-format": "^29.4.3" + "pretty-format": "^29.6.1" }, "dependencies": { "camelcase": { @@ -17610,29 +17651,29 @@ } }, "jest-watcher": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.4.3.tgz", - "integrity": "sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.1.tgz", + "integrity": "sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA==", "dev": true, "requires": { - "@jest/test-result": "^29.4.3", - "@jest/types": "^29.4.3", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.4.3", + "jest-util": "^29.6.1", "string-length": "^4.0.1" } }, "jest-worker": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.4.3.tgz", - "integrity": "sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.1.tgz", + "integrity": "sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA==", "dev": true, "requires": { "@types/node": "*", - "jest-util": "^29.4.3", + "jest-util": "^29.6.1", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -17893,20 +17934,12 @@ } }, "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "semver": "^7.5.3" } }, "make-error": { @@ -18192,6 +18225,25 @@ "mimic-fn": "^2.1.0" } }, + "openai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.3.0.tgz", + "integrity": "sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==", + "requires": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + }, + "dependencies": { + "axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "requires": { + "follow-redirects": "^1.14.8" + } + } + } + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -18728,12 +18780,12 @@ } }, "pretty-format": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.4.3.tgz", - "integrity": "sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "requires": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.0", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -18800,6 +18852,12 @@ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true }, + "pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true + }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -18956,9 +19014,9 @@ "dev": true }, "resolve.exports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.0.tgz", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true }, "restore-cursor": { @@ -19073,9 +19131,9 @@ "dev": true }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { "lru-cache": "^6.0.0" }, @@ -19597,9 +19655,9 @@ "dev": true }, "ts-jest": { - "version": "29.0.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", - "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", "dev": true, "requires": { "bs-logger": "0.x", @@ -19608,7 +19666,7 @@ "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" } }, diff --git a/nestjs/package.json b/nestjs/package.json index 2a65c497d2..e69c81022b 100644 --- a/nestjs/package.json +++ b/nestjs/package.json @@ -40,10 +40,11 @@ "axios": "1.3.4", "cache-manager": "4.1.0", "class-transformer": "0.5.1", - "class-validator": "0.14.0", + "class-validator": "0.13.2", "cookie-parser": "1.4.6", "dayjs": "1.11.7", "dotenv": "16.0.3", + "eta": "^3.0.1", "express": "4.18.2", "handlebars": "4.7.7", "ical-generator": "3.6.1", @@ -52,6 +53,7 @@ "lodash": "4.17.21", "nanoid": "3.3.4", "nestjs-pino": "3.1.2", + "openai": "3.3.0", "passport": "0.6.0", "passport-custom": "1.1.1", "passport-github2": "0.1.12", @@ -70,10 +72,11 @@ "@nestjs/schematics": "9.0.4", "@nestjs/testing": "9.3.9", "@openapitools/openapi-generator-cli": "2.6.0", + "@total-typescript/ts-reset": "0.4.2", "@types/cache-manager": "4.0.2", "@types/cookie-parser": "1.4.3", "@types/express": "4.17.17", - "@types/jest": "29.4.0", + "@types/jest": "29.5.3", "@types/json2csv": "5.0.3", "@types/jsonwebtoken": "9.0.1", "@types/lodash": "4.14.191", @@ -87,12 +90,12 @@ "eslint": "8.35.0", "eslint-config-prettier": "8.5.0", "eslint-plugin-prettier": "4.2.1", - "jest": "^29.4.3", + "jest": "^29.6.1", "jest-junit": "15.0.0", "pino-pretty": "9.4.0", "prettier": "2.8.8", "supertest": "6.3.3", - "ts-jest": "29.0.5", + "ts-jest": "29.1.1", "ts-loader": "9.4.2", "ts-node": "10.9.1", "tsconfig-paths": "4.1.0", diff --git a/nestjs/src/app.module.ts b/nestjs/src/app.module.ts index e212b58a48..9c6099b3c7 100644 --- a/nestjs/src/app.module.ts +++ b/nestjs/src/app.module.ts @@ -28,6 +28,7 @@ import { GratitudesModule } from './gratitudes'; import { CloudApiModule } from './cloud-api/cloud-api.module'; import { EventsModule } from './events/events.module'; import { TasksModule } from './tasks/tasks.module'; +import { PromptsModule } from './prompts/prompts.module'; @Module({ imports: [ @@ -60,6 +61,7 @@ import { TasksModule } from './tasks/tasks.module'; CloudApiModule, EventsModule, TasksModule, + PromptsModule, ], controllers: [], providers: [Logger, ConfigService], diff --git a/nestjs/src/config/config.service.ts b/nestjs/src/config/config.service.ts index 37e838f0be..4995ed5d59 100644 --- a/nestjs/src/config/config.service.ts +++ b/nestjs/src/config/config.service.ts @@ -47,6 +47,7 @@ export class ConfigService { public readonly host: string; public readonly isDev = process.env.NODE_ENV !== 'production'; public readonly secure: Secure; + public readonly openai: { apiKey: string }; constructor(conf: NestConfigService) { this.auth = { @@ -66,6 +67,10 @@ export class ConfigService { }, }; + this.openai = { + apiKey: conf.get('RSSHCOOL_OPENAI_API_KEY') || '', + }; + this.awsServices = { restApiUrl: process.env.RSSHCOOL_AWS_REST_API_URL || '', restApiKey: process.env.RSSHCOOL_AWS_REST_API_KEY || '', diff --git a/nestjs/src/core/jwt/jwt.service.ts b/nestjs/src/core/jwt/jwt.service.ts index a9ff11a00b..ba5a89bd49 100644 --- a/nestjs/src/core/jwt/jwt.service.ts +++ b/nestjs/src/core/jwt/jwt.service.ts @@ -12,12 +12,14 @@ export class JwtService { } public createToken(payload: any) { - const jwt: string = sign(JSON.parse(JSON.stringify(payload)), this.secretKey, { expiresIn: JWT_TOKEN_EXPIRATION }); + const jwt: string = sign(JSON.parse(JSON.stringify(payload)) as object, this.secretKey, { + expiresIn: JWT_TOKEN_EXPIRATION, + }); return jwt; } public createPublicCalendarToken(payload: T) { - const jwt: string = sign(JSON.parse(JSON.stringify(payload)), this.secretKey, { expiresIn: '365d' }); + const jwt: string = sign(JSON.parse(JSON.stringify(payload)) as object, this.secretKey, { expiresIn: '365d' }); return jwt; } diff --git a/nestjs/src/courses/course-schedule/course-schedule.service.ts b/nestjs/src/courses/course-schedule/course-schedule.service.ts index 080f7a8130..935d9d5838 100644 --- a/nestjs/src/courses/course-schedule/course-schedule.service.ts +++ b/nestjs/src/courses/course-schedule/course-schedule.service.ts @@ -285,7 +285,7 @@ export class CourseScheduleService { ...(technicalScreeningResults .find(task => task.courseTaskId === courseTaskId) ?.stageInterviewFeedbacks.map(feedback => JSON.parse(feedback.json)) - .map(json => json?.resume?.score ?? 0) ?? []), + .map((json: any) => json?.resume?.score ?? 0) ?? []), ); const currentScore = isFinite(scoreRaw) ? scoreRaw : null; return currentScore; @@ -359,7 +359,7 @@ export class CourseScheduleService { private getEventStatus(courseEvent: CourseEvent) { const startTime = (courseEvent.dateTime as Date).getTime(); - const endTime = courseEvent.endTime ?? startTime + (courseEvent.duration ?? 60) * 1000 * 60; + const endTime = Number(courseEvent.endTime) ?? startTime + (courseEvent.duration ?? 60) * 1000 * 60; if (endTime < Date.now()) { return CourseScheduleItemStatus.Archived; } diff --git a/nestjs/src/courses/cross-checks/course-cross-checks.controller.ts b/nestjs/src/courses/cross-checks/course-cross-checks.controller.ts index 8be436dfec..a749514552 100644 --- a/nestjs/src/courses/cross-checks/course-cross-checks.controller.ts +++ b/nestjs/src/courses/cross-checks/course-cross-checks.controller.ts @@ -15,17 +15,19 @@ import { ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiQuery } fr import { CourseGuard, CourseRole, CurrentRequest, DefaultGuard, RequiredRoles, Role, RoleGuard } from '../../auth'; import { CourseTasksService } from '../course-tasks'; import { OrderField, OrderDirection, CourseCrossCheckService } from './course-cross-checks.service'; -import { CrossCheckPairResponseDto } from './dto'; +import { CrossCheckFeedbackDto, CrossCheckPairResponseDto } from './dto'; import { AvailableReviewStatsDto } from './dto/available-review-stats.dto'; import { parseAsync } from 'json2csv'; import { Response } from 'express'; +import { StudentId } from 'src/core/decorators'; +import { FeedbackGuard } from './cross-check-feedback.guard'; @Controller('courses/:courseId/cross-checks') @ApiTags('courses tasks') @UseGuards(DefaultGuard, CourseGuard) export class CourseCrossCheckController { constructor( - private crossCheckPairsService: CourseCrossCheckService, + private courseCrossCheckService: CourseCrossCheckService, private courseTasksService: CourseTasksService, ) {} @@ -53,7 +55,7 @@ export class CourseCrossCheckController { @Query('url') url: string, @Query('task') task: string, ) { - const { items, pagination } = await this.crossCheckPairsService.findPairs( + const { items, pagination } = await this.courseCrossCheckService.findPairs( courseId, { pageSize, current }, { checker, student, url, task }, @@ -76,7 +78,7 @@ export class CourseCrossCheckController { if (!studentId) throw new BadRequestException(); const crossChecks = await this.courseTasksService.getAvailableCrossChecks(courseId); if (crossChecks.length === 0) return []; - const res = await this.crossCheckPairsService.getAvailableCrossChecksStats(crossChecks, studentId); + const res = await this.courseCrossCheckService.getAvailableCrossChecksStats(crossChecks, studentId); return res.map(e => new AvailableReviewStatsDto(e)); } @@ -92,7 +94,7 @@ export class CourseCrossCheckController { ) { const [courseTask, solutionUrls] = await Promise.all([ this.courseTasksService.getById(courseTaskId), - this.crossCheckPairsService.getSolutionsUrls(courseId, courseTaskId), + this.courseCrossCheckService.getSolutionsUrls(courseId, courseTaskId), ]); const parsedData = await parseAsync(solutionUrls, { fields: ['githubId', 'solutionUrl'] }); @@ -102,4 +104,22 @@ export class CourseCrossCheckController { res.end(parsedData); } + + @Get(':courseTaskId/feedbacks/my') + @ApiOperation({ operationId: 'getMyCrossCheckFeedbacks' }) + @ApiForbiddenResponse() + @ApiResponse({ type: CrossCheckFeedbackDto }) + @RequiredRoles([CourseRole.Manager, Role.Admin, CourseRole.Student], true) + @UseGuards(DefaultGuard, RoleGuard, FeedbackGuard) + public async getMyCrossCheckFeedbacks( + @StudentId() studentId: number, + @Param('courseId', ParseIntPipe) _courseId: number, + @Param('courseTaskId', ParseIntPipe) courseTaskId: number, + ) { + const [crossCheckSolutionReviews, taskSolution] = await Promise.all([ + this.courseCrossCheckService.getCrossCheckSolutionReviews(studentId, courseTaskId), + this.courseCrossCheckService.getTaskSolution(studentId, courseTaskId), + ]); + return new CrossCheckFeedbackDto(crossCheckSolutionReviews, taskSolution); + } } diff --git a/nestjs/src/courses/cross-checks/course-cross-checks.service.spec.ts b/nestjs/src/courses/cross-checks/course-cross-checks.service.spec.ts index f5c0b945c9..0dae791d5a 100644 --- a/nestjs/src/courses/cross-checks/course-cross-checks.service.spec.ts +++ b/nestjs/src/courses/cross-checks/course-cross-checks.service.spec.ts @@ -3,6 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { TaskSolutionChecker } from '@entities/taskSolutionChecker'; import { CourseCrossCheckService } from './course-cross-checks.service'; import { TaskSolution } from '@entities/taskSolution'; +import { TaskSolutionResult } from '@entities/taskSolutionResult'; const mockRawData = [ { @@ -45,6 +46,10 @@ describe('CourseCrossCheckService', () => { provide: getRepositoryToken(TaskSolutionChecker), useValue: {}, }, + { + provide: getRepositoryToken(TaskSolutionResult), + useValue: {}, + }, ], }).compile(); diff --git a/nestjs/src/courses/cross-checks/course-cross-checks.service.ts b/nestjs/src/courses/cross-checks/course-cross-checks.service.ts index 9ef9969ce0..00cfd2138a 100644 --- a/nestjs/src/courses/cross-checks/course-cross-checks.service.ts +++ b/nestjs/src/courses/cross-checks/course-cross-checks.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { Task } from '@entities/task'; import { User } from '@entities/user'; import { InjectRepository } from '@nestjs/typeorm'; @@ -6,8 +6,17 @@ import { TaskSolutionChecker } from '@entities/taskSolutionChecker'; import { Repository } from 'typeorm'; import { Student } from '@entities/student'; import { TaskSolution } from '@entities/taskSolution'; -import { CrossCheckMessage, ScoreRecord, TaskSolutionResult } from '@entities/taskSolutionResult'; +import { + CrossCheckMessage, + CrossCheckMessageAuthorRole, + ScoreRecord, + TaskSolutionResult, +} from '@entities/taskSolutionResult'; import { CourseTask } from '@entities/courseTask'; +import { UsersService } from 'src/users/users.service'; +import { PersonDto } from 'src/core/dto'; +import { CrossCheckCriteriaDataDto, CrossCheckMessageDto } from './dto'; +import { Discord } from 'src/profile/dto'; export type CrossCheckPair = { id: number; @@ -69,6 +78,21 @@ const orderFieldMapping: Record = { reviewedDate: 'tsr.updatedDate', }; +export type CrossCheckSolutionReview = { + id: number; + dateTime: number; + comment: string; + criteria?: CrossCheckCriteriaDataDto[]; + author: { + id: number; + name: string; + githubId: string; + discord: Discord | null; + } | null; + score: number; + messages: CrossCheckMessageDto[]; +}; + @Injectable() export class CourseCrossCheckService { constructor( @@ -76,6 +100,8 @@ export class CourseCrossCheckService { private readonly taskSolutionCheckerRepository: Repository, @InjectRepository(TaskSolution) private readonly taskSolutionRepository: Repository, + @InjectRepository(TaskSolutionResult) + private readonly TaskSolutionResultRepository: Repository, ) {} public async findPairs( @@ -222,4 +248,84 @@ export class CourseCrossCheckService { }) .filter(el => el.checksCount !== 0); } + + public isCrossCheckTask(courseTask: Partial) { + return courseTask.checker === 'crossCheck'; + } + + public async getCrossCheckSolutionReviews( + studentId: number, + courseTaskId: number, + ): Promise { + const taskSolutionResults = await this.TaskSolutionResultRepository.createQueryBuilder('tsr') + .select(['tsr.id', 'tsr.comment', 'tsr.anonymous', 'tsr.score', 'tsr.messages', 'tsr.historicalScores']) + .innerJoin('tsr.checker', 'checker') + .innerJoin('checker.user', 'user') + .addSelect(['checker.id', ...UsersService.getPrimaryUserFields('user')]) + .where('"tsr"."studentId" = :studentId', { studentId }) + .andWhere('"tsr"."courseTaskId" = :courseTaskId', { courseTaskId }) + .getMany(); + + return taskSolutionResults.map(taskSolutionResult => this.transformToCrossCheckSolutionReview(taskSolutionResult)); + } + + private transformToCrossCheckSolutionReview(taskSolutionResult: TaskSolutionResult): CrossCheckSolutionReview { + const author = this.extractAuthor(taskSolutionResult); + const { dateTime, criteria } = this.getLastCheck(taskSolutionResult); + const messages = this.getMessages(taskSolutionResult); + + return { + dateTime, + author, + messages, + id: taskSolutionResult.id, + comment: taskSolutionResult.comment ?? '', + score: taskSolutionResult.score, + criteria, + }; + } + + private extractAuthor(taskSolutionResult: TaskSolutionResult) { + if (taskSolutionResult.anonymous) { + return null; + } + + return { + id: taskSolutionResult.checker.user.id, + name: PersonDto.getName(taskSolutionResult.checker.user), + githubId: taskSolutionResult.checker.user.githubId, + discord: taskSolutionResult.checker.user.discord, + }; + } + + private getLastCheck(taskSolutionResult: TaskSolutionResult) { + const [lastCheck] = taskSolutionResult.historicalScores.sort((a, b) => b.dateTime - a.dateTime); + + if (!lastCheck) { + throw new BadRequestException('No historical scores found'); + } + + return lastCheck; + } + + private getMessages(taskSolutionResult: TaskSolutionResult) { + if (taskSolutionResult.anonymous) { + return taskSolutionResult.messages.map(message => ({ + ...message, + author: message.role === CrossCheckMessageAuthorRole.Reviewer ? null : message.author, + })); + } + + return taskSolutionResult.messages; + } + + public async getTaskSolution(studentId: number, courseTaskId: number) { + const taskSolution = await this.taskSolutionRepository + .createQueryBuilder('ts') + .where('"ts"."studentId" = :studentId', { studentId }) + .andWhere('"ts"."courseTaskId" = :courseTaskId', { courseTaskId }) + .getOne(); + + return taskSolution; + } } diff --git a/nestjs/src/courses/cross-checks/cross-check-feedback.guard.ts b/nestjs/src/courses/cross-checks/cross-check-feedback.guard.ts new file mode 100644 index 0000000000..7f985dc724 --- /dev/null +++ b/nestjs/src/courses/cross-checks/cross-check-feedback.guard.ts @@ -0,0 +1,39 @@ +import { Injectable, CanActivate, ExecutionContext, BadRequestException, UnauthorizedException } from '@nestjs/common'; +import { CourseCrossCheckService } from './course-cross-checks.service'; +import { CourseRole } from '@entities/session'; +import { CourseTasksService } from '../course-tasks'; + +@Injectable() +export class FeedbackGuard implements CanActivate { + constructor( + private readonly courseCrossCheckService: CourseCrossCheckService, + private readonly courseTasksService: CourseTasksService, + ) {} + + canActivate(context: ExecutionContext): boolean | Promise { + const request = context.switchToHttp().getRequest(); + const { courseId, courseTaskId } = request.params; + const studentId = request.user.courses[courseId]?.studentId; + const isManager = request.user.isAdmin || request.user.courses[courseId]?.roles.includes(CourseRole.Manager); + + if (!studentId && !isManager) { + throw new UnauthorizedException('Not a valid student for this course'); + } + + return this.validateTask(courseTaskId); + } + + async validateTask(courseTaskId: number): Promise { + const courseTask = await this.courseTasksService.getById(courseTaskId); + + if (courseTask == null) { + throw new BadRequestException('not valid student or course task'); + } + + if (!this.courseCrossCheckService.isCrossCheckTask(courseTask)) { + throw new BadRequestException('not supported task'); + } + + return true; + } +} diff --git a/nestjs/src/courses/cross-checks/dto/check-tasks-pairs.dto.ts b/nestjs/src/courses/cross-checks/dto/check-tasks-pairs.dto.ts index ab5c1fb867..2ac87fa30a 100644 --- a/nestjs/src/courses/cross-checks/dto/check-tasks-pairs.dto.ts +++ b/nestjs/src/courses/cross-checks/dto/check-tasks-pairs.dto.ts @@ -7,11 +7,13 @@ import { CrossCheckMessageAuthorRole, ScoreRecord, } from '@entities/taskSolutionResult'; +import { CrossCheckCriteriaDataDto } from './cross-check-criteria-data.dto'; export class HistoricalScoreDto { constructor(historicalScore: ScoreRecord) { this.comment = historicalScore.comment; this.dateTime = new Date(historicalScore.dateTime); + this.criteria = historicalScore.criteria; } @ApiProperty() @@ -19,6 +21,9 @@ export class HistoricalScoreDto { @ApiProperty() public dateTime: Date; + + @ApiProperty({ type: [CrossCheckCriteriaDataDto], required: false }) + public criteria?: CrossCheckCriteriaDataDto[]; } export class CrossCheckMessageAuthorDto { diff --git a/nestjs/src/courses/cross-checks/dto/cross-check-criteria-data.dto.ts b/nestjs/src/courses/cross-checks/dto/cross-check-criteria-data.dto.ts new file mode 100644 index 0000000000..79a969b6c7 --- /dev/null +++ b/nestjs/src/courses/cross-checks/dto/cross-check-criteria-data.dto.ts @@ -0,0 +1,32 @@ +import { CrossCheckCriteriaType } from '@entities/taskCriteria'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class CrossCheckCriteriaDataDto { + @ApiProperty() + @IsString() + key: string; + + @ApiProperty({ required: false }) + @IsOptional() + @IsNumber() + max?: number; + + @ApiProperty() + @IsString() + text: string; + + @ApiProperty({ enum: CrossCheckCriteriaType }) + @IsEnum(CrossCheckCriteriaType) + type: CrossCheckCriteriaType; + + @ApiProperty({ required: false }) + @IsOptional() + @IsNumber() + point?: number; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + textComment?: string; +} diff --git a/nestjs/src/courses/cross-checks/dto/cross-check-feedback.dto.ts b/nestjs/src/courses/cross-checks/dto/cross-check-feedback.dto.ts new file mode 100644 index 0000000000..5eee4181e5 --- /dev/null +++ b/nestjs/src/courses/cross-checks/dto/cross-check-feedback.dto.ts @@ -0,0 +1,56 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { CrossCheckMessageDto } from './check-tasks-pairs.dto'; +import { Discord } from 'src/profile/dto'; +import { CrossCheckSolutionReview } from '../course-cross-checks.service'; +import { TaskSolution } from '@entities/taskSolution'; +import { CrossCheckCriteriaDataDto } from './cross-check-criteria-data.dto'; + +export class CrossCheckAuthorDto { + @ApiProperty({ required: true }) + public id: number; + + @ApiProperty({ required: true }) + public name: string; + + @ApiProperty({ required: true }) + public githubId: string; + + @ApiProperty({ nullable: true, type: Discord }) + discord: Discord | null; +} + +export class CrossCheckSolutionReviewDto { + @ApiProperty({ required: true }) + public id: number; + + @ApiProperty({ required: true }) + public dateTime: number; + + @ApiProperty({ required: true }) + public comment: string; + + @ApiProperty({ required: false, type: [CrossCheckCriteriaDataDto] }) + public criteria?: CrossCheckCriteriaDataDto[]; + + @ApiProperty({ nullable: true, type: CrossCheckAuthorDto }) + public author: CrossCheckAuthorDto | null; + + @ApiProperty({ required: true }) + public score: number; + + @ApiProperty({ required: true, type: [CrossCheckMessageDto] }) + public messages: CrossCheckMessageDto[]; +} + +export class CrossCheckFeedbackDto { + constructor(crossCheckSolutionReviews: CrossCheckSolutionReview[], taskSolution: TaskSolution | null) { + this.reviews = crossCheckSolutionReviews; + this.url = taskSolution?.url; + } + + @ApiProperty({ required: false }) + public url?: string; + + @ApiProperty({ required: false, type: [CrossCheckSolutionReviewDto] }) + public reviews?: CrossCheckSolutionReviewDto[]; +} diff --git a/nestjs/src/courses/cross-checks/dto/index.ts b/nestjs/src/courses/cross-checks/dto/index.ts index 0faefe7e69..a582bc4bc5 100644 --- a/nestjs/src/courses/cross-checks/dto/index.ts +++ b/nestjs/src/courses/cross-checks/dto/index.ts @@ -1 +1,3 @@ export * from './check-tasks-pairs.dto'; +export * from './cross-check-feedback.dto'; +export * from './cross-check-criteria-data.dto'; diff --git a/nestjs/src/courses/interviews/interviews.service.ts b/nestjs/src/courses/interviews/interviews.service.ts index 34d03a4ee3..391ccf68ef 100644 --- a/nestjs/src/courses/interviews/interviews.service.ts +++ b/nestjs/src/courses/interviews/interviews.service.ts @@ -49,7 +49,7 @@ export class InterviewsService { .map(({ stageInterviewFeedbacks }) => stageInterviewFeedbacks.map(feedback => ({ date: feedback.updatedDate, - rating: InterviewsService.getInterviewRatings(JSON.parse(feedback.json)).rating, + rating: InterviewsService.getInterviewRatings(JSON.parse(feedback.json) as StageInterviewFeedbackJson).rating, })), ) .reduce((acc, cur) => acc.concat(cur), []) @@ -68,7 +68,7 @@ export class InterviewsService { 'student.id', 'student.totalScore', 'student.mentorId', - ...this.getPrimaryUserFields(), + ...UsersService.getPrimaryUserFields(), 'taskChecker.id', ]) .where('is.courseId = :courseId', { courseId }) @@ -98,7 +98,7 @@ export class InterviewsService { .leftJoin('student.stageInterviews', 'si') .leftJoin('si.stageInterviewFeedbacks', 'sif') .addSelect([ - ...this.getPrimaryUserFields(), + ...UsersService.getPrimaryUserFields(), 'si.id', 'si.isGoodCandidate', 'si.isCompleted', @@ -166,18 +166,6 @@ export class InterviewsService { return { rating, htmlCss, common, dataStructures }; } - private getPrimaryUserFields(modelName = 'user') { - return [ - `${modelName}.id`, - `${modelName}.firstName`, - `${modelName}.lastName`, - `${modelName}.githubId`, - `${modelName}.cityName`, - `${modelName}.countryName`, - `${modelName}.discord`, - ]; - } - private isGoodCandidate(stageInterviews: StageInterview[]) { return stageInterviews.some(i => i.isCompleted && i.isGoodCandidate); } diff --git a/nestjs/src/courses/score/score.service.ts b/nestjs/src/courses/score/score.service.ts index fcc8a94cb3..00438e4996 100644 --- a/nestjs/src/courses/score/score.service.ts +++ b/nestjs/src/courses/score/score.service.ts @@ -12,6 +12,7 @@ import { orderByFieldMapping, OrderDirection, OrderField, ScoreQueryDto } from ' import { InterviewsService } from '../interviews'; import { ScoreDto, ScoreStudentDto } from './dto/score.dto'; import { TaskResult } from '@entities/taskResult'; +import { UsersService } from 'src/users/users.service'; const defaultFilter: Partial = { activeOnly: 'false', @@ -101,7 +102,7 @@ export class ScoreService { let query = this.studentRepository .createQueryBuilder('student') .innerJoin('student.user', 'user') - .addSelect(this.getPrimaryUserFields()) + .addSelect(UsersService.getPrimaryUserFields()) .leftJoin('student.mentor', 'mentor', 'mentor."isExpelled" = FALSE') .addSelect(['mentor.id', 'mentor.userId']) .leftJoin('student.taskResults', 'tr') @@ -111,7 +112,7 @@ export class ScoreService { .leftJoin('student.taskInterviewResults', 'tir') .addSelect(['tir.id', 'tir.score', 'tir.courseTaskId', 'tr.studentId', 'tir.updatedDate']) .leftJoin('mentor.user', 'mu') - .addSelect(this.getPrimaryUserFields('mu')) + .addSelect(UsersService.getPrimaryUserFields('mu')) .leftJoin('student.stageInterviews', 'si') .leftJoin('si.stageInterviewFeedbacks', 'sif') .addSelect(['sif.stageInterviewId', 'sif.json', 'sif.updatedDate', 'si.isCompleted', 'si.id', 'si.courseTaskId']) @@ -150,14 +151,4 @@ export class ScoreService { orderBy.direction.toUpperCase() as Uppercase, ); } - - private getPrimaryUserFields = (modelName = 'user') => [ - `${modelName}.id`, - `${modelName}.firstName`, - `${modelName}.lastName`, - `${modelName}.githubId`, - `${modelName}.cityName`, - `${modelName}.countryName`, - `${modelName}.discord`, - ]; } diff --git a/nestjs/src/profile/dto/endorsement.dto.ts b/nestjs/src/profile/dto/endorsement.dto.ts new file mode 100644 index 0000000000..685bc20f62 --- /dev/null +++ b/nestjs/src/profile/dto/endorsement.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class EndorsementDto { + constructor(profile: { content: string; data: object } | null) { + this.summary = profile?.content ?? 'We do not have enough data to generate endorsement.'; + this.data = profile?.data ?? null; + } + + @ApiProperty({ type: String }) + public summary: string; + + @ApiProperty({ type: Object, nullable: true }) + public data: object | null; +} diff --git a/nestjs/src/profile/endorsement.service.ts b/nestjs/src/profile/endorsement.service.ts new file mode 100644 index 0000000000..b3aae3f048 --- /dev/null +++ b/nestjs/src/profile/endorsement.service.ts @@ -0,0 +1,98 @@ +import { Course } from '@entities/course'; +import { Feedback, Mentor, Student, TaskInterviewResult, User } from '@entities/index'; +import { Prompt } from '@entities/prompt'; +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; +import { OpenAIApi, Configuration } from 'openai'; +import { Eta } from 'eta'; +import { ConfigService } from 'src/config'; + +const eta = new Eta(); + +@Injectable() +export class EndorsementService { + private openAI: OpenAIApi; + + private logger = new Logger(EndorsementService.name); + + constructor( + @InjectRepository(Course) + private courseRepository: Repository, + @InjectRepository(Mentor) + private mentorRepository: Repository, + @InjectRepository(Student) + private studentRepository: Repository, + @InjectRepository(Prompt) + private promptRepository: Repository, + @InjectRepository(Feedback) + private feedbackRepository: Repository, + @InjectRepository(User) + private userRepository: Repository, + @InjectRepository(TaskInterviewResult) + private taskInterviewResultRepository: Repository, + private readonly configService: ConfigService, + ) { + this.openAI = new OpenAIApi(new Configuration(this.configService.openai)); + } + + public async getEndorsement(githubId: string): Promise<{ content: string; data: object } | null> { + try { + const prompt = await this.getEndorsementPrompt(githubId); + if (!prompt) { + return null; + } + this.logger.log(`Endorsement prompt found`); + const result = await this.openAI.createChatCompletion({ + model: 'gpt-3.5-turbo', + messages: [{ role: 'user', content: prompt.text }], + temperature: prompt.temperature ?? 0.5, + }); + this.logger.log(`Open AI response received`, result); + const content = result.data.choices[0]?.message?.content ?? ''; + return { content, data: prompt.data }; + } catch (error) { + this.logger.error(error); + return null; + } + } + + async getEndorsementPrompt(githubId: string) { + const user = await this.userRepository.findOne({ where: { githubId } }); + if (!user) { + this.logger.warn(`User not found for githubId: ${githubId}`); + return null; + } + + const [prompt, mentors, feedbacks] = await Promise.all([ + this.promptRepository.findOne({ where: { type: 'endorsement' } }), + this.mentorRepository.find({ where: { userId: user.id } }), + this.feedbackRepository.find({ where: { toUserId: user.id } }), + ]); + + if (!prompt?.text || mentors.length === 0) { + this.logger.warn(`No prompt text of mentors found for githubId: ${githubId}`); + return null; + } + + const courses = await this.courseRepository.find({ + where: { id: In(mentors.map(m => m.courseId)) }, + relations: ['discipline'], + }); + const mentorIds = mentors.map(m => m.id); + const [studentsCount, interviewsCount] = await Promise.all([ + this.studentRepository.count({ where: { mentorId: In(mentorIds) } }), + this.taskInterviewResultRepository.count({ where: { mentorId: In(mentorIds) } }), + ]); + + const data = { + user, + courses, + mentors, + studentsCount, + interviewsCount, + feedbacks, + }; + return { text: eta.renderString(prompt.text, data), temperature: prompt.temperature, data }; + } +} diff --git a/nestjs/src/profile/profile.controller.ts b/nestjs/src/profile/profile.controller.ts index 833d1b6806..90118835ca 100644 --- a/nestjs/src/profile/profile.controller.ts +++ b/nestjs/src/profile/profile.controller.ts @@ -7,12 +7,18 @@ import { ProfileInfoDto, ProfileCourseDto, UpdateUserDto, UpdateProfileInfoDto } import { ProfileDto } from './dto/profile.dto'; import { ProfileService } from './profile.service'; import { PersonalProfileDto } from './dto/personal-profile.dto'; +import { EndorsementService } from './endorsement.service'; +import { EndorsementDto } from './dto/endorsement.dto'; @Controller('profile') @ApiTags('profile') @UseGuards(DefaultGuard) export class ProfileController { - constructor(private readonly profileService: ProfileService, private readonly coursesService: CoursesService) {} + constructor( + private readonly profileService: ProfileService, + private readonly endormentService: EndorsementService, + private readonly coursesService: CoursesService, + ) {} @Get(':username/courses') @ApiOperation({ operationId: 'getUserCourses' }) @@ -81,4 +87,14 @@ export class ProfileController { return new PersonalProfileDto(user); } + + @Get(':username/endorsement') + @ApiOperation({ operationId: 'getEndorsement' }) + @ApiResponse({ type: EndorsementDto }) + @UseGuards(DefaultGuard, RoleGuard) + @RequiredRoles([Role.Admin]) + public async getEndorsement(@Param('username') githubId: string) { + const endorsement = await this.endormentService.getEndorsement(githubId); + return new EndorsementDto(endorsement); + } } diff --git a/nestjs/src/profile/profile.module.ts b/nestjs/src/profile/profile.module.ts index 870ddbadb8..2dc74cb781 100644 --- a/nestjs/src/profile/profile.module.ts +++ b/nestjs/src/profile/profile.module.ts @@ -9,15 +9,30 @@ import { User } from '@entities/user'; import { ProfilePermissions } from '@entities/profilePermissions'; import { UsersNotificationsModule } from 'src/users-notifications/users-notifications.module'; import { Resume } from '@entities/resume'; +import { EndorsementService } from './endorsement.service'; +import { Feedback, Mentor, Prompt, Student, TaskInterviewResult } from '@entities/index'; +import { ConfigModule } from 'src/config'; @Module({ imports: [ - TypeOrmModule.forFeature([Course, NotificationUserConnection, User, ProfilePermissions, Resume]), + TypeOrmModule.forFeature([ + Course, + NotificationUserConnection, + User, + ProfilePermissions, + Resume, + Student, + Mentor, + Prompt, + Feedback, + TaskInterviewResult, + ]), UsersNotificationsModule, CoursesModule, + ConfigModule, ], controllers: [ProfileController], - providers: [ProfileService], + providers: [ProfileService, EndorsementService], exports: [ProfileService], }) export class ProfileModule {} diff --git a/nestjs/src/prompts/dto/create-prompt.dto.ts b/nestjs/src/prompts/dto/create-prompt.dto.ts new file mode 100644 index 0000000000..75890601bb --- /dev/null +++ b/nestjs/src/prompts/dto/create-prompt.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class CreatePromptDto { + @IsString() + @ApiProperty() + type: string; + + @IsString() + @ApiProperty() + text: string; + + @IsNumber() + @ApiProperty() + temperature: number; +} diff --git a/nestjs/src/prompts/dto/index.ts b/nestjs/src/prompts/dto/index.ts new file mode 100644 index 0000000000..1548bbe298 --- /dev/null +++ b/nestjs/src/prompts/dto/index.ts @@ -0,0 +1,3 @@ +export * from './prompt.dto'; +export * from './create-prompt.dto'; +export * from './update-prompt.dto'; diff --git a/nestjs/src/prompts/dto/prompt.dto.ts b/nestjs/src/prompts/dto/prompt.dto.ts new file mode 100644 index 0000000000..b6fc00f674 --- /dev/null +++ b/nestjs/src/prompts/dto/prompt.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Prompt } from '@entities/prompt'; + +export class PromptDto { + constructor(prompt: Prompt) { + this.id = prompt.id; + this.type = prompt.type; + this.text = prompt.text; + this.temperature = prompt.temperature; + } + + @ApiProperty() + id: number; + + @ApiProperty() + type: string; + + @ApiProperty() + text: string; + + @ApiProperty() + temperature: number; +} diff --git a/nestjs/src/prompts/dto/update-prompt.dto.ts b/nestjs/src/prompts/dto/update-prompt.dto.ts new file mode 100644 index 0000000000..9bb22bdb90 --- /dev/null +++ b/nestjs/src/prompts/dto/update-prompt.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class UpdatePromptDto { + @IsOptional() + @IsNumber() + @ApiProperty() + temperature?: number; + + @IsOptional() + @IsString() + @ApiProperty() + type?: string; + + @IsOptional() + @IsString() + @ApiProperty() + text?: string; +} diff --git a/nestjs/src/prompts/prompts.controller.ts b/nestjs/src/prompts/prompts.controller.ts new file mode 100644 index 0000000000..7773aec1c8 --- /dev/null +++ b/nestjs/src/prompts/prompts.controller.ts @@ -0,0 +1,47 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { DefaultGuard, RequiredRoles, Role, RoleGuard } from '../auth'; +import { CreatePromptDto, PromptDto, UpdatePromptDto } from './dto'; +import { PromptsService } from './prompts.service'; + +@Controller('prompts') +@ApiTags('prompts') +@UseGuards(DefaultGuard, RoleGuard) +export class PromptsController { + constructor(private readonly promptsService: PromptsService) {} + + @Post('/') + @RequiredRoles([Role.Admin]) + @ApiOperation({ operationId: 'createPrompt' }) + @ApiOkResponse({ type: PromptDto }) + public async create(@Body() dto: CreatePromptDto) { + const result = await this.promptsService.create(dto); + return new PromptDto(result); + } + + @Get('/') + @RequiredRoles([Role.Admin]) + @ApiOperation({ operationId: 'getPrompts' }) + @ApiOkResponse({ type: [PromptDto] }) + public async getAll(): Promise { + const data = await this.promptsService.findAll(); + return data.map(item => new PromptDto(item)); + } + + @Delete('/:id') + @RequiredRoles([Role.Admin]) + @ApiOperation({ operationId: 'deletePrompt' }) + @ApiOkResponse({}) + public async remove(@Param('id', ParseIntPipe) id: number) { + return this.promptsService.remove(id); + } + + @Patch('/:id') + @RequiredRoles([Role.Admin]) + @ApiOperation({ operationId: 'updatePrompt' }) + @ApiOkResponse({ type: PromptDto }) + public async update(@Param('id', ParseIntPipe) id: number, @Body() alert: UpdatePromptDto) { + const result = await this.promptsService.update(id, alert); + return new PromptDto(result); + } +} diff --git a/nestjs/src/prompts/prompts.module.ts b/nestjs/src/prompts/prompts.module.ts new file mode 100644 index 0000000000..63011b27a5 --- /dev/null +++ b/nestjs/src/prompts/prompts.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { PromptsController } from './prompts.controller'; +import { PromptsService } from './prompts.service'; +import { Prompt } from '@entities/prompt'; + +@Module({ + imports: [TypeOrmModule.forFeature([Prompt])], + controllers: [PromptsController], + providers: [PromptsService], +}) +export class PromptsModule {} diff --git a/nestjs/src/prompts/prompts.service.ts b/nestjs/src/prompts/prompts.service.ts new file mode 100644 index 0000000000..0b4688ec76 --- /dev/null +++ b/nestjs/src/prompts/prompts.service.ts @@ -0,0 +1,34 @@ +import { Prompt } from '@entities/prompt'; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { CreatePromptDto } from './dto/create-prompt.dto'; +import { UpdatePromptDto } from './dto/update-prompt.dto'; + +@Injectable() +export class PromptsService { + constructor( + @InjectRepository(Prompt) + private promptsRepository: Repository, + ) {} + + public async create(dto: CreatePromptDto) { + const { text, type, temperature } = dto; + const { id } = await this.promptsRepository.save({ text, type, temperature }); + return this.promptsRepository.findOneByOrFail({ id }); + } + + public async findAll(): Promise { + const items = await this.promptsRepository.find(); + return items; + } + + public async update(id: number, dto: UpdatePromptDto) { + await this.promptsRepository.update(id, dto); + return this.promptsRepository.findOneByOrFail({ id }); + } + + public async remove(id: number) { + await this.promptsRepository.delete(id); + } +} diff --git a/nestjs/src/reset.d.ts b/nestjs/src/reset.d.ts new file mode 100644 index 0000000000..12bd3edc94 --- /dev/null +++ b/nestjs/src/reset.d.ts @@ -0,0 +1 @@ +import '@total-typescript/ts-reset'; diff --git a/nestjs/src/spec.json b/nestjs/src/spec.json index d2df3ad2d3..6867d4ec17 100644 --- a/nestjs/src/spec.json +++ b/nestjs/src/spec.json @@ -687,6 +687,24 @@ "tags": ["courses tasks"] } }, + "/courses/{courseId}/cross-checks/{courseTaskId}/feedbacks/my": { + "get": { + "operationId": "getMyCrossCheckFeedbacks", + "summary": "", + "parameters": [ + { "name": "courseId", "required": true, "in": "path", "schema": { "type": "number" } }, + { "name": "courseTaskId", "required": true, "in": "path", "schema": { "type": "number" } } + ], + "responses": { + "403": { "description": "" }, + "default": { + "description": "", + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CrossCheckFeedbackDto" } } } + } + }, + "tags": ["courses tasks"] + } + }, "/course/{courseId}/students/score": { "get": { "operationId": "getScore", @@ -1514,6 +1532,20 @@ "tags": ["profile"] } }, + "/profile/{username}/endorsement": { + "get": { + "operationId": "getEndorsement", + "summary": "", + "parameters": [{ "name": "username", "required": true, "in": "path", "schema": { "type": "string" } }], + "responses": { + "default": { + "description": "", + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EndorsementDto" } } } + } + }, + "tags": ["profile"] + } + }, "/disciplines": { "post": { "operationId": "createDiscipline", @@ -2121,6 +2153,65 @@ }, "tags": ["tasks-criteria"] } + }, + "/prompts": { + "post": { + "operationId": "createPrompt", + "summary": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreatePromptDto" } } } + }, + "responses": { + "200": { + "description": "", + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PromptDto" } } } + } + }, + "tags": ["prompts"] + }, + "get": { + "operationId": "getPrompts", + "summary": "", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { "type": "array", "items": { "$ref": "#/components/schemas/PromptDto" } } + } + } + } + }, + "tags": ["prompts"] + } + }, + "/prompts/{id}": { + "delete": { + "operationId": "deletePrompt", + "summary": "", + "parameters": [{ "name": "id", "required": true, "in": "path", "schema": { "type": "number" } }], + "responses": { "200": { "description": "" } }, + "tags": ["prompts"] + }, + "patch": { + "operationId": "updatePrompt", + "summary": "", + "parameters": [{ "name": "id", "required": true, "in": "path", "schema": { "type": "number" } }], + "requestBody": { + "required": true, + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdatePromptDto" } } } + }, + "responses": { + "200": { + "description": "", + "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PromptDto" } } } + } + }, + "tags": ["prompts"] + } } }, "info": { "title": "", "description": "", "version": "1.0.0", "contact": {} }, @@ -2724,9 +2815,25 @@ "properties": { "studentsActiveCount": { "type": "number" }, "studentsTotalCount": { "type": "number" } }, "required": ["studentsActiveCount", "studentsTotalCount"] }, + "CrossCheckCriteriaDataDto": { + "type": "object", + "properties": { + "key": { "type": "string" }, + "max": { "type": "number" }, + "text": { "type": "string" }, + "type": { "type": "string", "enum": ["title", "subtask", "penalty"] }, + "point": { "type": "number" }, + "textComment": { "type": "string" } + }, + "required": ["key", "text", "type"] + }, "HistoricalScoreDto": { "type": "object", - "properties": { "comment": { "type": "string" }, "dateTime": { "format": "date-time", "type": "string" } }, + "properties": { + "comment": { "type": "string" }, + "dateTime": { "format": "date-time", "type": "string" }, + "criteria": { "type": "array", "items": { "$ref": "#/components/schemas/CrossCheckCriteriaDataDto" } } + }, "required": ["comment", "dateTime"] }, "CrossCheckMessageAuthorDto": { @@ -2803,6 +2910,45 @@ }, "required": ["name", "id", "checksCount", "completedChecksCount"] }, + "Discord": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "username": { "type": "string" }, + "discriminator": { "type": "string" } + }, + "required": ["id", "username", "discriminator"] + }, + "CrossCheckAuthorDto": { + "type": "object", + "properties": { + "id": { "type": "number" }, + "name": { "type": "string" }, + "githubId": { "type": "string" }, + "discord": { "nullable": true, "allOf": [{ "$ref": "#/components/schemas/Discord" }] } + }, + "required": ["id", "name", "githubId", "discord"] + }, + "CrossCheckSolutionReviewDto": { + "type": "object", + "properties": { + "id": { "type": "number" }, + "dateTime": { "type": "number" }, + "comment": { "type": "string" }, + "criteria": { "type": "array", "items": { "$ref": "#/components/schemas/CrossCheckCriteriaDataDto" } }, + "author": { "nullable": true, "allOf": [{ "$ref": "#/components/schemas/CrossCheckAuthorDto" }] }, + "score": { "type": "number" }, + "messages": { "type": "array", "items": { "$ref": "#/components/schemas/CrossCheckMessageDto" } } + }, + "required": ["id", "dateTime", "comment", "author", "score", "messages"] + }, + "CrossCheckFeedbackDto": { + "type": "object", + "properties": { + "url": { "type": "string" }, + "reviews": { "type": "array", "items": { "$ref": "#/components/schemas/CrossCheckSolutionReviewDto" } } + } + }, "MentorDto": { "type": "object", "properties": { "id": { "type": "number" }, "githubId": { "type": "string" }, "name": { "type": "string" } }, @@ -3023,15 +3169,6 @@ "minTotalScore" ] }, - "Discord": { - "type": "object", - "properties": { - "id": { "type": "string" }, - "username": { "type": "string" }, - "discriminator": { "type": "string" } - }, - "required": ["id", "username", "discriminator"] - }, "TeamDistributionStudentDto": { "type": "object", "properties": { @@ -3558,6 +3695,11 @@ }, "required": ["userId", "githubId", "primaryEmail", "isActiveStudent"] }, + "EndorsementDto": { + "type": "object", + "properties": { "summary": { "type": "string" }, "data": { "type": "object", "nullable": true } }, + "required": ["summary", "data"] + }, "CreateDisciplineDto": { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"] }, "DisciplineDto": { "type": "object", @@ -4197,6 +4339,34 @@ "type": "object", "properties": { "criteria": { "type": "array", "items": { "$ref": "#/components/schemas/CriteriaDto" } } }, "required": ["criteria"] + }, + "CreatePromptDto": { + "type": "object", + "properties": { + "type": { "type": "string" }, + "text": { "type": "string" }, + "temperature": { "type": "number" } + }, + "required": ["type", "text", "temperature"] + }, + "PromptDto": { + "type": "object", + "properties": { + "id": { "type": "number" }, + "type": { "type": "string" }, + "text": { "type": "string" }, + "temperature": { "type": "number" } + }, + "required": ["id", "type", "text", "temperature"] + }, + "UpdatePromptDto": { + "type": "object", + "properties": { + "temperature": { "type": "number" }, + "type": { "type": "string" }, + "text": { "type": "string" } + }, + "required": ["temperature", "type", "text"] } } } diff --git a/nestjs/src/users/users.service.ts b/nestjs/src/users/users.service.ts index 521b555e73..e5384fc964 100644 --- a/nestjs/src/users/users.service.ts +++ b/nestjs/src/users/users.service.ts @@ -57,4 +57,16 @@ export class UsersService { } return result.join(' '); } + + public static getPrimaryUserFields(modelName = 'user') { + return [ + `${modelName}.id`, + `${modelName}.firstName`, + `${modelName}.lastName`, + `${modelName}.githubId`, + `${modelName}.cityName`, + `${modelName}.countryName`, + `${modelName}.discord`, + ]; + } } diff --git a/server/src/migrations/1687009744110-Prompt.ts b/server/src/migrations/1687009744110-Prompt.ts new file mode 100644 index 0000000000..ba1917999c --- /dev/null +++ b/server/src/migrations/1687009744110-Prompt.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Prompt1687009744110 implements MigrationInterface { + name = 'Prompt1687009744110'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "prompt" ("id" SERIAL NOT NULL, "createdDate" TIMESTAMP NOT NULL DEFAULT now(), "updatedDate" TIMESTAMP NOT NULL DEFAULT now(), "type" character varying(256) NOT NULL, "text" character varying NOT NULL, CONSTRAINT "UQ_8b52c9f9bf5ffaba2f772c65456" UNIQUE ("type"), CONSTRAINT "PK_d8e3aa07a95560a445ad50fb931" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "prompt"`); + } +} diff --git a/server/src/migrations/1691520611773-Temperature.ts b/server/src/migrations/1691520611773-Temperature.ts new file mode 100644 index 0000000000..9aa3bc62da --- /dev/null +++ b/server/src/migrations/1691520611773-Temperature.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Temperature1691520611773 implements MigrationInterface { + name = 'Temperature1691520611773'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "prompt" ADD "temperature" integer NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "prompt" DROP COLUMN "temperature"`); + } +} diff --git a/server/src/migrations/1691524327332-Temperature.ts b/server/src/migrations/1691524327332-Temperature.ts new file mode 100644 index 0000000000..d2544814d3 --- /dev/null +++ b/server/src/migrations/1691524327332-Temperature.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Temperature1691524327332 implements MigrationInterface { + name = 'Temperature1691524327332'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "prompt" DROP COLUMN "temperature"`); + await queryRunner.query(`ALTER TABLE "prompt" ADD "temperature" double precision NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "prompt" DROP COLUMN "temperature"`); + await queryRunner.query(`ALTER TABLE "prompt" ADD "temperature" integer NOT NULL`); + } +} diff --git a/server/src/migrations/index.ts b/server/src/migrations/index.ts index 4e679a67d5..a5427f15ef 100644 --- a/server/src/migrations/index.ts +++ b/server/src/migrations/index.ts @@ -47,6 +47,9 @@ import { UserGroup1675245424426 } from './1675245424426-UserGroup'; import { User1676139987317 } from './1676139987317-User'; import { Course1675345245770 } from './1675345245770-Course'; import { MentorRegistry1685197747051 } from './1685197747051-MentorRegistry'; +import { Prompt1687009744110 } from './1687009744110-Prompt'; +import { Temperature1691520611773 } from './1691520611773-Temperature'; +import { Temperature1691524327332 } from './1691524327332-Temperature'; export const migrations = [ UserMigration1630340371992, @@ -98,4 +101,7 @@ export const migrations = [ User1676139987317, Course1675345245770, MentorRegistry1685197747051, + Prompt1687009744110, + Temperature1691520611773, + Temperature1691524327332, ]; diff --git a/server/src/models/index.ts b/server/src/models/index.ts index bcbc9e9425..177cfac925 100644 --- a/server/src/models/index.ts +++ b/server/src/models/index.ts @@ -21,6 +21,7 @@ import { NotificationUserConnection } from './notificationUserConnection'; import { NotificationUserSettings } from './notificationUserSettings'; import { PrivateFeedback } from './privateFeedback'; import { ProfilePermissions } from './profilePermissions'; +import { Prompt } from './prompt'; import { Registry } from './registry'; import { RepositoryEvent } from './repositoryEvent'; import { Resume } from './resume'; @@ -70,6 +71,7 @@ export { NotificationUserSettings, PrivateFeedback, ProfilePermissions, + Prompt, Registry, RepositoryEvent, Resume, @@ -118,6 +120,7 @@ export const models = [ NotificationUserSettings, PrivateFeedback, ProfilePermissions, + Prompt, Registry, RepositoryEvent, Resume, diff --git a/server/src/models/prompt.ts b/server/src/models/prompt.ts new file mode 100644 index 0000000000..bd417068d3 --- /dev/null +++ b/server/src/models/prompt.ts @@ -0,0 +1,23 @@ +import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn, Unique } from 'typeorm'; + +@Entity() +@Unique(['type']) +export class Prompt { + @PrimaryGeneratedColumn() + id: number; + + @CreateDateColumn() + createdDate: number; + + @UpdateDateColumn() + updatedDate: number; + + @Column({ type: 'varchar', length: 256 }) + type: string; + + @Column({ type: 'float' }) + temperature: number; + + @Column() + text: string; +} diff --git a/server/src/models/taskSolutionResult.ts b/server/src/models/taskSolutionResult.ts index ad0c043294..6de781faf0 100644 --- a/server/src/models/taskSolutionResult.ts +++ b/server/src/models/taskSolutionResult.ts @@ -11,12 +11,13 @@ import { import { Student } from './student'; import { CourseTask } from './courseTask'; import { TaskSolutionReview } from './taskSolution'; +import { CrossCheckCriteriaType } from './taskCriteria'; export interface CrossCheckCriteriaData { - key: number; + key: string; max?: number; text: string; - type: string; + type: CrossCheckCriteriaType; point?: number; comment?: string; } diff --git a/server/src/routes/course/crossCheck/getFeedback.ts b/server/src/routes/course/crossCheck/getFeedback.ts deleted file mode 100644 index 2edd02a5e2..0000000000 --- a/server/src/routes/course/crossCheck/getFeedback.ts +++ /dev/null @@ -1,27 +0,0 @@ -import Router from '@koa/router'; -import { BAD_REQUEST, OK } from 'http-status-codes'; -import { ILogger } from '../../../logger'; -import { taskResultsService, CrossCheckService } from '../../../services'; -import { setResponse } from '../../utils'; - -export const getFeedback = (_: ILogger) => async (ctx: Router.RouterContext) => { - const { githubId, courseId, courseTaskId } = ctx.params; - const crossCheckService = new CrossCheckService(courseTaskId); - const { student, courseTask } = await crossCheckService.getStudentAndTask(courseId, githubId); - - if (student == null || courseTask == null) { - setResponse(ctx, BAD_REQUEST, { message: 'not valid student or course task' }); - return; - } - if (!CrossCheckService.isCrossCheckTask(courseTask)) { - setResponse(ctx, BAD_REQUEST, { message: 'not supported task' }); - return; - } - - const feedback = await taskResultsService.getTaskSolutionFeedback(student.id, courseTaskId); - const response = { - url: feedback.url, - reviews: feedback.reviews, - }; - setResponse(ctx, OK, response); -}; diff --git a/server/src/routes/course/crossCheck/index.ts b/server/src/routes/course/crossCheck/index.ts index 25520cc718..8fde4e674b 100644 --- a/server/src/routes/course/crossCheck/index.ts +++ b/server/src/routes/course/crossCheck/index.ts @@ -6,7 +6,6 @@ export * from './createMessage'; export * from './updateMessage'; export * from './deleteSolution'; export * from './getAssignments'; -export * from './getFeedback'; export * from './getResult'; export * from './getSolution'; export * from './getTaskDetails'; diff --git a/server/src/routes/course/index.ts b/server/src/routes/course/index.ts index 6e73c7a0af..d94941566c 100644 --- a/server/src/routes/course/index.ts +++ b/server/src/routes/course/index.ts @@ -262,7 +262,6 @@ function addStudentCrossCheckApi(router: Router, logger: ILogger) { router.get(`${baseUrl}/cross-check/solution`, courseGuard, validateGithubId, crossCheck.getSolution(logger)); router.post(`${baseUrl}/cross-check/result`, courseGuard, validateGithubId, crossCheck.createResult(logger)); router.get(`${baseUrl}/cross-check/result`, courseGuard, validateGithubId, crossCheck.getResult(logger)); - router.get(`${baseUrl}/cross-check/feedback`, courseGuard, ...validators, crossCheck.getFeedback(logger)); router.get(`${baseUrl}/cross-check/assignments`, courseGuard, ...validators, crossCheck.getAssignments(logger)); router.post( `/taskSolutionResult/:taskSolutionResultId/task/:courseTaskId/cross-check/messages`, diff --git a/server/src/services/course.service.ts b/server/src/services/course.service.ts index f94d0f53ad..934180c601 100644 --- a/server/src/services/course.service.ts +++ b/server/src/services/course.service.ts @@ -546,9 +546,11 @@ export async function getEvents(courseId: number) { export async function getTaskSolutionsWithoutChecker(courseTaskId: number) { const records = await getRepository(TaskSolution) .createQueryBuilder('ts') + .leftJoin('student', 's', 's."id" = ts.studentId') .leftJoin('task_solution_checker', 'tsc', 'tsc."taskSolutionId" = ts.id') .where(`ts."courseTaskId" = :courseTaskId`, { courseTaskId }) .andWhere('tsc.id IS NULL') + .andWhere('s.isExpelled = false') .getMany(); return records; } diff --git a/server/src/services/taskResults.service.ts b/server/src/services/taskResults.service.ts index 1c6b274c87..7531c56c51 100644 --- a/server/src/services/taskResults.service.ts +++ b/server/src/services/taskResults.service.ts @@ -10,8 +10,6 @@ import { } from '../models'; import { getRepository } from 'typeorm'; import { getPrimaryUserFields } from './course.service'; -import { createName } from './user.service'; -import { CrossCheckMessageAuthorRole } from '../models/taskSolutionResult'; export async function getTaskResult(studentId: number, courseTaskId: number) { return getRepository(TaskResult) @@ -187,51 +185,6 @@ export async function getCrossCheckData( }; } -export async function getTaskSolutionFeedback(studentId: number, courseTaskId: number) { - const comments = ( - await getRepository(TaskSolutionResult) - .createQueryBuilder('tsr') - .select(['tsr.id', 'tsr.comment', 'tsr.anonymous', 'tsr.score', 'tsr.messages', 'tsr.historicalScores']) - .innerJoin('tsr.checker', 'checker') - .innerJoin('checker.user', 'user') - .addSelect(['checker.id', ...getPrimaryUserFields('user')]) - .where('"tsr"."studentId" = :studentId', { studentId }) - .andWhere('"tsr"."courseTaskId" = :courseTaskId', { courseTaskId }) - .getMany() - ).map(c => { - const author = !c.anonymous - ? { - id: c.checker.user.id, - name: createName(c.checker.user), - githubId: c.checker.user.githubId, - discord: c.checker.user.discord, - } - : null; - const [{ dateTime, criteria }] = c.historicalScores.sort((a, b) => b.dateTime - a.dateTime); - const messages = !c.anonymous - ? c.messages - : c.messages.map(message => ({ - ...message, - author: message.role === CrossCheckMessageAuthorRole.Reviewer ? null : message.author, - })); - return { - dateTime, - author, - messages, - id: c.id, - comment: c.comment, - score: c.score, - criteria, - }; - }); - const taskSolution = await getRepository(TaskSolution) - .createQueryBuilder('ts') - .where('"ts"."studentId" = :studentId', { studentId }) - .andWhere('"ts"."courseTaskId" = :courseTaskId', { courseTaskId }) - .getOne(); - return { url: taskSolution?.url, reviews: comments }; -} - type TaskArtefactInput = { studentId: number; courseTaskId: number;