diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx index fedc78c4db..110b78efe2 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx @@ -8,7 +8,6 @@ import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider'; import { JobType, PayMethod } from '../../../types'; import { CvatJobRequestForm } from './CvatJobRequestForm'; import { FortuneJobRequestForm } from './FortuneJobRequestForm'; -import { HCaptchaJobRequestForm } from './HCaptchaJobRequestForm'; export const CreateJob = () => { const { payMethod, jobRequest, updateJobRequest } = useCreateJobPageUI(); @@ -65,9 +64,6 @@ export const CreateJob = () => { Fortune )} {!IS_MAINNET && CVAT} - {/* {!IS_MAINNET && ( - hCaptcha - )} */} { {jobRequest.jobType === JobType.FORTUNE && } {jobRequest.jobType === JobType.CVAT && } - {jobRequest.jobType === JobType.HCAPTCHA && } ); }; diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx index 4b8a106d13..f9fce014bb 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx @@ -218,13 +218,7 @@ export const CryptoPayForm = ({ } // create job - const { - jobType, - chainId, - fortuneRequest, - cvatRequest, - hCaptchaRequest, - } = jobRequest; + const { jobType, chainId, fortuneRequest, cvatRequest } = jobRequest; if (jobType === JobType.FORTUNE && fortuneRequest) { await jobService.createFortuneJob( chainId, @@ -241,8 +235,6 @@ export const CryptoPayForm = ({ Number(amount), fundTokenSymbol, ); - } else if (jobType === JobType.HCAPTCHA && hCaptchaRequest) { - await jobService.createHCaptchaJob(chainId, hCaptchaRequest); } onFinish(); } catch (err) { diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx index d2e2e5e164..82502c9b9b 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx @@ -28,11 +28,7 @@ import { TokenSelect } from '../../../components/TokenSelect'; import { CURRENCY } from '../../../constants/payment'; import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider'; import { useSnackbar } from '../../../providers/SnackProvider'; -import { - createCvatJob, - createFortuneJob, - createHCaptchaJob, -} from '../../../services/job'; +import { createCvatJob, createFortuneJob } from '../../../services/job'; import { confirmFiatPayment, createFiatPayment, @@ -247,8 +243,7 @@ export const FiatPayForm = ({ } // create job - const { jobType, chainId, fortuneRequest, cvatRequest, hCaptchaRequest } = - jobRequest; + const { jobType, chainId, fortuneRequest, cvatRequest } = jobRequest; if (!chainId) return; if (jobType === JobType.FORTUNE && fortuneRequest) { @@ -267,8 +262,6 @@ export const FiatPayForm = ({ amount, tokenSymbol, ); - } else if (jobType === JobType.HCAPTCHA && hCaptchaRequest) { - await createHCaptchaJob(chainId, hCaptchaRequest); } // Update balance and finish payment diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/HCaptchaJobRequestForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/HCaptchaJobRequestForm.tsx deleted file mode 100644 index f12750a53d..0000000000 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/HCaptchaJobRequestForm.tsx +++ /dev/null @@ -1,748 +0,0 @@ -import AddIcon from '@mui/icons-material/Add'; -import CloseIcon from '@mui/icons-material/Close'; -import CloudIcon from '@mui/icons-material/Cloud'; -import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; -import InfoIcon from '@mui/icons-material/Info'; -import { - Autocomplete, - Box, - Button, - Chip, - FormControl, - FormHelperText, - Grid, - IconButton, - InputAdornment, - InputLabel, - MenuItem, - Select, - TextField, - Tooltip, -} from '@mui/material'; -import Typography from '@mui/material/Typography'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { useFormik } from 'formik'; -import { useEffect, useState } from 'react'; -import { useSearchParams } from 'react-router-dom'; -import { - Accordion, - AccordionDetails, - AccordionSummary, -} from '../../../components/Accordion'; -import { CollectionsFilledIcon } from '../../../components/Icons/CollectionsFilledIcon'; -import languages from '../../../data/languages.json'; -import locations from '../../../data/locations.json'; -import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider'; -import { getQualifications } from '../../../services/qualification'; -import { HCaptchaJobType, Qualification } from '../../../types'; -import { HCaptchaJobRequesteValidationSchema } from './schema'; - -export const HCaptchaJobRequestForm = () => { - const { jobRequest, updateJobRequest, goToPrevStep, goToNextStep } = - useCreateJobPageUI(); - const [expanded, setExpanded] = useState(['panel1']); - const [searchParams] = useSearchParams(); - const [qualificationsOptions, setQualificationsOptions] = useState< - Qualification[] - >([]); - - const initialValues = { - dataUrl: '', - accuracyTarget: 85, - completionDate: null, - minRequests: null, - maxRequests: null, - qualifications: [], - // Advanced - workerLanguage: null, - workerLocation: null, - targetBrowser: 'desktop', - // Annotation - type: HCaptchaJobType.COMPARISON, - taskBidPrice: null, - label: '', - labelingPrompt: null, - groundTruths: null, - minWorkerConfidence: 85, - images: [], - }; - - const handleChange = - (panel: string) => (event: React.SyntheticEvent, newExpanded: boolean) => { - if (newExpanded) { - setExpanded([...expanded, panel]); - } else { - setExpanded(expanded.filter((item) => item !== panel)); - } - }; - - const handleNext = (data: any) => { - const hCaptchaRequest: any = { - dataUrl: data.dataUrl, - accuracyTarget: Number(data.accuracyTarget) / 100, - minRequests: Number(data.minRequests), - maxRequests: Number(data.maxRequests), - - qualifications: (data.qualifications as Qualification[]).map( - (qualification) => qualification.reference, - ), - advanced: { - workerLanguage: data.workerLanguage, - workerLocation: data.workerLocation, - targetBrowser: data.targetBrowser, - }, - annotations: { - typeOfJob: data.type, - taskBidPrice: Number(data.taskBidPrice), - label: data.label ?? '', - labelingPrompt: data.labelingPrompt, - groundTruths: data.groundTruths, - exampleImages: data.images, - }, - }; - if (data.completionDate) { - hCaptchaRequest['completionDate'] = data.completionDate; - } - updateJobRequest({ - ...jobRequest, - hCaptchaRequest, - }); - goToNextStep(); - }; - - const { - errors, - touched, - values, - dirty, - isValid, - handleSubmit, - handleBlur, - setFieldValue, - } = useFormik({ - initialValues, - validationSchema: HCaptchaJobRequesteValidationSchema, - onSubmit: handleNext, - }); - - useEffect(() => { - const type = searchParams.get('type'); - if ( - type && - Object.values(HCaptchaJobType).includes(type as HCaptchaJobType) - ) { - setFieldValue('type', type); - } - const fetchData = async () => { - if (jobRequest.chainId !== undefined) { - try { - setQualificationsOptions(await getQualifications(jobRequest.chainId)); - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error fetching data:', error); - } - } - }; - - fetchData(); - }, [jobRequest.chainId]); - - return ( - -
- - - - - General - - - - - - - - - Select job type - - - - - - - - - - - - ), - }} - value={values.taskBidPrice} - onChange={(e) => - setFieldValue('taskBidPrice', e.target.value) - } - onBlur={handleBlur} - error={ - touched.taskBidPrice && Boolean(errors.taskBidPrice) - } - helperText={errors.taskBidPrice} - type="number" - /> - - - - - - - - - Unsure what job type to choose? - - - - - - - - - setFieldValue('minRequests', e.target.value) - } - onBlur={handleBlur} - error={touched.minRequests && Boolean(errors.minRequests)} - type="number" - /> - - - - - - setFieldValue('maxRequests', e.target.value) - } - onBlur={handleBlur} - error={touched.maxRequests && Boolean(errors.maxRequests)} - type="number" - /> - - - - - - - - Qualifications - - - - - - - - - option.title} - value={values.qualifications} - onChange={(event, newValues) => { - setFieldValue('qualifications', newValues); - }} - selectOnFocus - onBlur={handleBlur} - handleHomeEndKeys - renderTags={(value, getTagProps) => - value.map((option, index) => ( - - )) - } - renderInput={(params) => ( - - - - - - - ), - }} - /> - - )} - /> - - - - - - - - - - - Job annotation details - - - - - - - - - - ), - }} - value={values.dataUrl} - onChange={(e) => setFieldValue('dataUrl', e.target.value)} - onBlur={handleBlur} - error={touched.dataUrl && Boolean(errors.dataUrl)} - helperText={errors.dataUrl} - /> - - - {(values.type === HCaptchaJobType.POLYGON || - values.type === HCaptchaJobType.BOUNDING_BOX) && ( - - - setFieldValue('label', e.target.value)} - onBlur={handleBlur} - error={touched.label && Boolean(errors.label)} - /> - - - )} - - - - setFieldValue('labelingPrompt', e.target.value) - } - onBlur={handleBlur} - error={ - touched.labelingPrompt && Boolean(errors.labelingPrompt) - } - /> - - - {values.type !== HCaptchaJobType.CATEGORIZATION && ( - - - - Add Image - - Add up to 3 images to be shown by the capcha - - - - {values.images.map((image, index) => ( - - - {index + 1} - - { - const newImages: string[] = [...values.images]; - newImages[index] = e.target.value; - setFieldValue('images', newImages); - }} - onBlur={handleBlur} - error={Boolean(errors.images?.[index])} - /> - { - const newImages: string[] = [...values.images]; - newImages.splice(index, 1); - setFieldValue('images', newImages); - }} - > - - - - ))} - - - - - - - )} - - - - setFieldValue('groundTruths', e.target.value) - } - onBlur={handleBlur} - error={touched.groundTruths && Boolean(errors.groundTruths)} - /> - - - - - - - - - - ), - }} - value={values.accuracyTarget} - onChange={(e) => - setFieldValue('accuracyTarget', e.target.value) - } - onBlur={handleBlur} - error={ - touched.accuracyTarget && Boolean(errors.accuracyTarget) - } - helperText={errors.accuracyTarget} - type="number" - /> - - - - - - - - - - ), - }} - value={values.minWorkerConfidence} - onChange={(e) => - setFieldValue('minWorkerConfidence', e.target.value) - } - onBlur={handleBlur} - error={ - touched.minWorkerConfidence && - Boolean(errors.minWorkerConfidence) - } - type="number" - /> - - - - - - - - - - Advanced (optional) - - - - - - - - setFieldValue('completionDate', newValue) - } - slotProps={{ openPickerIcon: { sx: { color: '#858EC6' } } }} - /> - - - - - - Worker language - - - {errors.workerLanguage && ( - - {errors.workerLanguage} - - )} - - - - - - Target browser - - - {errors.targetBrowser && ( - - {errors.targetBrowser} - - )} - - - - - - Worker location - - - {errors.workerLocation && ( - - {errors.workerLocation} - - )} - - - - - - - - - -
-
- ); -}; diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts b/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts index 827358218a..85884c5fba 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/schema.ts @@ -62,30 +62,3 @@ export const FortuneJobRequestValidationSchema = Yup.object().shape({ .moreThan(0, 'FortunesRequested must be greater than 0'), qualifications: Yup.array().of(Yup.object()), }); - -export const HCaptchaJobRequesteValidationSchema = Yup.object().shape({ - taskBidPrice: Yup.number() - .required('Task Bid Price is required') - .moreThan(0, 'Task Bid Price must be greater than 0'), - minRequests: Yup.number() - .required('Min Requests is required') - .moreThan(0, 'Min Requests must be greater than 0'), - maxRequests: Yup.number() - .required('Max Requests is required') - .moreThan( - Yup.ref('minRequests'), - 'Max Requests must be greater than min requests', - ), - dataUrl: Yup.string().required('Data URL is required').url('Invalid URL'), - labelingPrompt: Yup.string().required('Labeling Prompt is required'), - groundTruths: Yup.string() - .required('Ground Truth URL is required') - .url('Invalid URL'), - accuracyTarget: Yup.number() - .required('Accuracy target is required') - .moreThan(0, 'Accuracy target must be greater than 0') - .max(100, 'Accuracy target must be less than or equal to 100'), - targetBrowser: Yup.string().required('Target Browser is required'), - images: Yup.array().of(Yup.string().url('Invalid Image URL')), - qualifications: Yup.array().of(Yup.object()), -}); diff --git a/packages/apps/job-launcher/client/src/services/job.ts b/packages/apps/job-launcher/client/src/services/job.ts index fae6ea60d4..ceb719f492 100644 --- a/packages/apps/job-launcher/client/src/services/job.ts +++ b/packages/apps/job-launcher/client/src/services/job.ts @@ -6,7 +6,6 @@ import { CvatRequest, JobStatus, JobDetailsResponse, - HCaptchaRequest, FortuneFinalResult, } from '../types'; import api from '../utils/api'; @@ -56,16 +55,6 @@ export const createCvatJob = async ( await api.post('/job/cvat', body); }; -export const createHCaptchaJob = async ( - chainId: number, - data: HCaptchaRequest, -) => { - await api.post('/job/hCaptcha', { - chainId, - ...data, - }); -}; - export const getJobList = async ({ chainId = ChainId.ALL, status, diff --git a/packages/apps/job-launcher/client/src/types/index.ts b/packages/apps/job-launcher/client/src/types/index.ts index 6add95a163..0ca3d5a11b 100644 --- a/packages/apps/job-launcher/client/src/types/index.ts +++ b/packages/apps/job-launcher/client/src/types/index.ts @@ -98,14 +98,6 @@ export enum CvatJobType { IMAGE_SKELETONS_FROM_BOXES = 'image_skeletons_from_boxes', } -export enum HCaptchaJobType { - POLYGON = 'polygon', - CATEGORIZATION = 'categorization', - POINT = 'point', - BOUNDING_BOX = 'bounding_box', - COMPARISON = 'comparison', -} - export type FortuneRequest = { title: string; fortunesRequested: number; @@ -225,34 +217,11 @@ export type CvatRequest = { accuracyTarget: number; }; -export type HCaptchaRequest = { - dataUrl: string; - accuracyTarget: number; - completionDate: Date; - minRequests: number; - maxRequests: number; - qualifications?: string[]; - advanced: { - workerLanguage: string; - workerLocation: string; - targetBrowser: string; - }; - annotations: { - typeOfJob: string; - taskBidPrice: number; - label: string; - labelingPrompt: string; - groundTruths: string; - exampleImages: string[]; - }; -}; - export type JobRequest = { jobType: JobType; chainId?: ChainId; fortuneRequest?: FortuneRequest; cvatRequest?: CvatRequest; - hCaptchaRequest?: HCaptchaRequest; }; export enum JobStatus { diff --git a/packages/apps/job-launcher/server/src/modules/job/fixtures.ts b/packages/apps/job-launcher/server/src/modules/job/fixtures.ts index f95f9fdc70..cf8f0ddf88 100644 --- a/packages/apps/job-launcher/server/src/modules/job/fixtures.ts +++ b/packages/apps/job-launcher/server/src/modules/job/fixtures.ts @@ -8,10 +8,9 @@ import { CvatJobType, EscrowFundToken, FortuneJobType, - JobCaptchaShapeType, } from '../../common/enums/job'; import { PaymentCurrency } from '../../common/enums/payment'; -import { JobCaptchaDto, JobCvatDto, JobFortuneDto } from './job.dto'; +import { JobCvatDto, JobFortuneDto } from './job.dto'; import { JobEntity } from './job.entity'; import { JobStatus } from '../../common/enums/job'; @@ -67,36 +66,6 @@ export const createCvatJobDto = (overrides = {}): JobCvatDto => ({ ...overrides, }); -export const createCaptchaJobDto = (overrides = {}): JobCaptchaDto => ({ - chainId: ChainId.POLYGON_AMOY, - data: { - provider: getMockedProvider(), - region: getMockedRegion(), - bucketName: faker.lorem.word(), - path: faker.system.filePath(), - }, - accuracyTarget: faker.number.float({ min: 0.1, max: 1, fractionDigits: 4 }), - minRequests: faker.number.int({ min: 1, max: 5 }), - maxRequests: faker.number.int({ min: 6, max: 10 }), - annotations: { - typeOfJob: faker.helpers.arrayElement(Object.values(JobCaptchaShapeType)), - labelingPrompt: faker.lorem.sentence(), - groundTruths: faker.internet.url(), - exampleImages: [faker.internet.url(), faker.internet.url()], - taskBidPrice: faker.number.float({ min: 0.1, max: 10, fractionDigits: 6 }), - label: faker.lorem.word(), - }, - completionDate: faker.date.future(), - paymentCurrency: faker.helpers.arrayElement(Object.values(PaymentCurrency)), - paymentAmount: faker.number.int({ min: 1, max: 1000 }), - escrowFundToken: faker.helpers.arrayElement(Object.values(EscrowFundToken)), - exchangeOracle: faker.finance.ethereumAddress(), - recordingOracle: faker.finance.ethereumAddress(), - reputationOracle: faker.finance.ethereumAddress(), - advanced: {}, - ...overrides, -}); - export const createJobEntity = ( overrides: Partial = {}, ): JobEntity => { diff --git a/packages/apps/job-launcher/server/src/modules/job/job.controller.ts b/packages/apps/job-launcher/server/src/modules/job/job.controller.ts index e5c3bb17c8..bc7ae37fb9 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.controller.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.controller.ts @@ -37,7 +37,6 @@ import { JobFortuneDto, JobIdDto, JobListDto, - // JobCaptchaDto, JobQuickLaunchDto, } from './job.dto'; import { JobService } from './job.service'; @@ -176,49 +175,6 @@ export class JobController { ); } - // @ApiOperation({ - // summary: 'Create a hCaptcha job', - // description: 'Endpoint to create a new hCaptcha job.', - // }) - // @ApiBody({ type: JobCaptchaDto }) - // @ApiResponse({ - // status: 201, - // description: 'ID of the created hCaptcha job.', - // type: Number, - // }) - // @ApiResponse({ - // status: 400, - // description: 'Bad Request. Invalid input parameters.', - // }) - // @ApiResponse({ - // status: 401, - // description: 'Unauthorized. Missing or invalid credentials.', - // }) - // @ApiResponse({ - // status: 409, - // description: 'Conflict. Conflict with the current state of the server.', - // }) - // @Post('/hCaptcha') - // public async createCaptchaJob( - // @Body() data: JobCaptchaDto, - // @Request() req: RequestWithUser, - // ): Promise { - // throw new ForbiddenError( - // 'Hcaptcha jobs disabled temporally', - // ); - // return await this.mutexManagerService.runExclusive( - // `user${req.user.id}`, - // MUTEX_TIMEOUT, - // async () => { - // return await this.jobService.createJob( - // req.user, - // JobRequestType.HCAPTCHA, - // data, - // ); - // }, - // ); - // } - @ApiOperation({ summary: 'Get a list of jobs', description: diff --git a/packages/apps/job-launcher/server/src/modules/job/job.dto.ts b/packages/apps/job-launcher/server/src/modules/job/job.dto.ts index 32a58b0a85..729fcb3ccb 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.dto.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.dto.ts @@ -1,47 +1,39 @@ +import { ChainId } from '@human-protocol/sdk'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; import { + ArrayMinSize, IsArray, + IsEthereumAddress, + IsIn, + IsNotEmpty, IsNumber, + IsNumberString, + IsObject, + IsOptional, IsPositive, IsString, IsUrl, - IsDateString, - IsOptional, - IsObject, - IsNumberString, - IsIn, - Min, Max, - IsNotEmpty, - IsEthereumAddress, + Min, ValidateNested, - IsDefined, - IsNotEmptyObject, - ArrayMinSize, } from 'class-validator'; -import { Type } from 'class-transformer'; -import { ChainId } from '@human-protocol/sdk'; +import { IsEnumCaseInsensitive } from '../../common/decorators'; import { - JobCaptchaShapeType, + CvatJobType, EscrowFundToken, JobRequestType, JobSortField, JobStatus, JobStatusFilter, - WorkerBrowser, - WorkerLanguage, - Country, - CvatJobType, JobType, } from '../../common/enums/job'; -import { Transform } from 'class-transformer'; +import { PaymentCurrency } from '../../common/enums/payment'; import { AWSRegions, StorageProviders } from '../../common/enums/storage'; import { PageOptionsDto } from '../../common/pagination/pagination.dto'; -import { IsEnumCaseInsensitive } from '../../common/decorators'; -import { PaymentCurrency } from '../../common/enums/payment'; +import { IsValidTokenDecimals } from '../../common/validators/token-decimals'; import { IsValidToken } from '../../common/validators/tokens'; import { Label, ManifestDetails } from '../manifest/manifest.dto'; -import { IsValidTokenDecimals } from '../../common/validators/token-decimals'; export class JobDto { @ApiProperty({ enum: ChainId, required: false, name: 'chain_id' }) @@ -372,112 +364,4 @@ export class GetJobsDto extends PageOptionsDto { status?: JobStatusFilter; } -export class JobCaptchaAdvancedDto { - @ApiProperty({ - enum: WorkerLanguage, - name: 'worker_language', - }) - @IsEnumCaseInsensitive(WorkerLanguage) - @IsOptional() - workerLanguage?: WorkerLanguage; - - @ApiProperty({ - enum: Country, - name: 'worker_location', - }) - @IsEnumCaseInsensitive(Country) - @IsOptional() - workerLocation?: Country; - - @ApiProperty({ - enum: WorkerBrowser, - name: 'target_browser', - }) - @IsEnumCaseInsensitive(WorkerBrowser) - @IsOptional() - targetBrowser?: WorkerBrowser; -} - -class JobCaptchaAnnotationsDto { - @ApiProperty({ - enum: JobCaptchaShapeType, - name: 'type_of_job', - }) - @IsEnumCaseInsensitive(JobCaptchaShapeType) - typeOfJob: JobCaptchaShapeType; - - @ApiProperty({ name: 'task_bid_price' }) - @IsNumber() - @IsPositive() - taskBidPrice: number; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - label?: string; - - @ApiProperty({ name: 'labeling_prompt' }) - @IsString() - labelingPrompt: string; - - @ApiProperty({ name: 'ground_truths' }) - @IsString() - groundTruths: string; - - @ApiProperty({ name: 'example_images' }) - @IsOptional() - @IsArray() - exampleImages?: string[]; -} - -export class JobCaptchaDto extends JobDto { - @ApiProperty() - @IsObject() - @ValidateNested() - @Type(() => StorageDataDto) - data: StorageDataDto; - - @ApiProperty({ name: 'accuracy_target' }) - @IsNumber() - @IsPositive() - @Max(1) - accuracyTarget: number; - - @ApiProperty({ name: 'completion_date' }) - @IsDateString() - @IsOptional() - completionDate: Date; - - @ApiProperty({ name: 'min_requests' }) - @IsNumber() - @IsPositive() - @Max(100) - minRequests: number; - - @ApiProperty({ name: 'max_requests' }) - @IsNumber() - @IsPositive() - @Max(100) - maxRequests: number; - - @ApiProperty() - @IsDefined() - @IsObject() - @ValidateNested() - @Type(() => JobCaptchaAdvancedDto) - advanced: JobCaptchaAdvancedDto; - - @ApiProperty() - @IsDefined() - @IsNotEmptyObject() - @IsObject() - @ValidateNested() - @Type(() => JobCaptchaAnnotationsDto) - annotations: JobCaptchaAnnotationsDto; -} - -export type CreateJob = - | JobQuickLaunchDto - | JobFortuneDto - | JobCvatDto - | JobCaptchaDto; +export type CreateJob = JobQuickLaunchDto | JobFortuneDto | JobCvatDto; diff --git a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts index 1946bf3a75..b4ad55106a 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.interface.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.interface.ts @@ -1,7 +1,6 @@ import { CvatJobType, JobRequestType } from '../../common/enums/job'; import { CvatDataDto, - JobCaptchaDto, JobCvatDto, JobFortuneDto, StorageDataDto, @@ -10,7 +9,7 @@ import { JobEntity } from './job.entity'; export interface RequestAction { createManifest: ( - dto: JobFortuneDto | JobCvatDto | JobCaptchaDto, + dto: JobFortuneDto | JobCvatDto, requestType: JobRequestType, fundAmount: number, decimals: number, diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index cdf7d3a25a..ef7015b8fb 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -42,7 +42,6 @@ import { getTokenDecimals } from '../../common/utils/tokens'; import { createMockCvatManifest, createMockFortuneManifest, - createMockHcaptchaManifest, } from '../manifest/fixtures'; import { ManifestService } from '../manifest/manifest.service'; import { PaymentRepository } from '../payment/payment.repository'; @@ -57,7 +56,6 @@ import { WebhookRepository } from '../webhook/webhook.repository'; import { WhitelistEntity } from '../whitelist/whitelist.entity'; import { WhitelistService } from '../whitelist/whitelist.service'; import { - createCaptchaJobDto, createCvatJobDto, createFortuneJobDto, createJobEntity, @@ -573,92 +571,6 @@ describe('JobService', () => { }); }); - describe('HCaptcha', () => { - it('should create an HCaptcha job', async () => { - const captchaJobDto = createCaptchaJobDto(); - const fundTokenDecimals = getTokenDecimals( - captchaJobDto.chainId!, - captchaJobDto.escrowFundToken, - ); - - const mockManifest = createMockHcaptchaManifest(); - mockManifestService.createManifest.mockResolvedValueOnce(mockManifest); - const mockUrl = faker.internet.url(); - const mockHash = faker.string.uuid(); - mockManifestService.uploadManifest.mockResolvedValueOnce({ - url: mockUrl, - hash: mockHash, - }); - const jobEntityMock = createJobEntity(); - mockJobRepository.createUnique = jest - .fn() - .mockResolvedValueOnce(jobEntityMock); - mockRateService.getRate - .mockResolvedValueOnce(tokenToUsdRate) - .mockResolvedValueOnce(usdToTokenRate); - - await jobService.createJob( - userMock, - HCaptchaJobType.HCAPTCHA, - captchaJobDto, - ); - - expect(mockWeb3Service.validateChainId).toHaveBeenCalledWith( - captchaJobDto.chainId, - ); - expect(mockRoutingProtocolService.selectOracles).not.toHaveBeenCalled(); - expect(mockRoutingProtocolService.validateOracles).toHaveBeenCalledWith( - captchaJobDto.chainId, - HCaptchaJobType.HCAPTCHA, - captchaJobDto.reputationOracle, - captchaJobDto.exchangeOracle, - captchaJobDto.recordingOracle, - ); - expect(mockManifestService.createManifest).toHaveBeenCalledWith( - captchaJobDto, - HCaptchaJobType.HCAPTCHA, - captchaJobDto.paymentAmount, - fundTokenDecimals, - ); - expect(mockManifestService.uploadManifest).toHaveBeenCalledWith( - captchaJobDto.chainId, - mockManifest, - [ - captchaJobDto.exchangeOracle, - captchaJobDto.reputationOracle, - captchaJobDto.recordingOracle, - ], - ); - expect(mockPaymentService.createWithdrawalPayment).toHaveBeenCalledWith( - userMock.id, - expect.any(Number), - captchaJobDto.paymentCurrency, - tokenToUsdRate, - ); - expect(mockJobRepository.updateOne).toHaveBeenCalledWith({ - chainId: captchaJobDto.chainId, - userId: userMock.id, - manifestUrl: mockUrl, - manifestHash: mockHash, - requestType: HCaptchaJobType.HCAPTCHA, - fee: expect.any(Number), - fundAmount: Number( - mul( - mul(captchaJobDto.paymentAmount, tokenToUsdRate), - usdToTokenRate, - ).toFixed(6), - ), - status: JobStatus.MODERATION_PASSED, - waitUntil: expect.any(Date), - token: captchaJobDto.escrowFundToken, - exchangeOracle: captchaJobDto.exchangeOracle, - recordingOracle: captchaJobDto.recordingOracle, - reputationOracle: captchaJobDto.reputationOracle, - payments: expect.any(Array), - }); - }); - }); - describe('JobQuickLaunchDto', () => { it('should create a job with quick launch dto', async () => { const jobQuickLaunchDto = new JobQuickLaunchDto(); diff --git a/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts b/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts index dde47c00b4..ba2b12bb13 100644 --- a/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts +++ b/packages/apps/job-launcher/server/src/modules/manifest/fixtures.ts @@ -1,25 +1,14 @@ import { faker } from '@faker-js/faker'; import { ChainId } from '@human-protocol/sdk'; -import { AuthConfigService } from '../../common/config/auth-config.service'; import { CvatConfigService } from '../../common/config/cvat-config.service'; -import { Web3ConfigService } from '../../common/config/web3-config.service'; -import { - CvatJobType, - EscrowFundToken, - JobCaptchaRequestType, - JobCaptchaShapeType, -} from '../../common/enums/job'; +import { CvatJobType, EscrowFundToken } from '../../common/enums/job'; import { PaymentCurrency } from '../../common/enums/payment'; -import { JobCaptchaDto, JobCvatDto } from '../job/job.dto'; +import { JobCvatDto } from '../job/job.dto'; import { getMockedProvider, getMockedRegion, } from '../../../test/fixtures/storage'; -import { - CvatManifestDto, - FortuneManifestDto, - HCaptchaManifestDto, -} from './manifest.dto'; +import { CvatManifestDto, FortuneManifestDto } from './manifest.dto'; import { FortuneJobType } from '../../common/enums/job'; export const mockCvatConfigService: Omit = { @@ -29,21 +18,6 @@ export const mockCvatConfigService: Omit = { skeletonsJobSizeMultiplier: faker.number.int({ min: 1, max: 1000 }), }; -export const mockAuthConfigService: Omit< - Partial, - 'configService' -> = { - hCaptchaSiteKey: faker.string.uuid(), -}; - -export const mockWeb3ConfigService: Omit< - Partial, - 'configService' -> = { - hCaptchaReputationOracleURI: faker.internet.url(), - hCaptchaRecordingOracleURI: faker.internet.url(), -}; - export function createJobCvatDto( overrides: Partial = {}, ): JobCvatDto { @@ -75,36 +49,6 @@ export function createJobCvatDto( }; } -export function createJobCaptchaDto( - overrides: Partial = {}, -): JobCaptchaDto { - return { - data: { - provider: getMockedProvider(), - region: getMockedRegion(), - bucketName: faker.lorem.word(), - path: faker.system.filePath(), - }, - accuracyTarget: faker.number.float({ min: 0.1, max: 1 }), - minRequests: faker.number.int({ min: 1, max: 5 }), - maxRequests: faker.number.int({ min: 6, max: 10 }), - annotations: { - typeOfJob: faker.helpers.arrayElement(Object.values(JobCaptchaShapeType)), - labelingPrompt: faker.lorem.sentence(), - groundTruths: faker.internet.url(), - exampleImages: [faker.internet.url(), faker.internet.url()], - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - label: faker.lorem.word(), - }, - completionDate: faker.date.future(), - paymentCurrency: faker.helpers.arrayElement(Object.values(PaymentCurrency)), - paymentAmount: faker.number.int({ min: 1, max: 1000 }), - escrowFundToken: faker.helpers.arrayElement(Object.values(EscrowFundToken)), - advanced: {}, - ...overrides, - }; -} - export function createMockFortuneManifest( overrides: Partial = {}, ): FortuneManifestDto { @@ -150,55 +94,3 @@ export function createMockCvatManifest( ...overrides, }; } - -export function createMockHcaptchaManifest( - overrides: Partial = {}, -): HCaptchaManifestDto { - return { - job_mode: faker.lorem.word(), - request_type: faker.helpers.arrayElement( - Object.values(JobCaptchaRequestType), - ), - request_config: { - shape_type: faker.helpers.arrayElement( - Object.values(JobCaptchaShapeType), - ), - min_shapes_per_image: faker.number.int({ min: 1, max: 5 }), - max_shapes_per_image: faker.number.int({ min: 6, max: 10 }), - min_points: faker.number.int({ min: 1, max: 5 }), - max_points: faker.number.int({ min: 6, max: 10 }), - minimum_selection_area_per_shape: faker.number.int({ min: 1, max: 100 }), - multiple_choice_max_choices: faker.number.int({ min: 1, max: 5 }), - multiple_choice_min_choices: faker.number.int({ min: 1, max: 5 }), - answer_type: faker.lorem.word(), - overlap_threshold: faker.number.float({ min: 0, max: 1 }), - max_length: faker.number.int({ min: 1, max: 100 }), - min_length: faker.number.int({ min: 1, max: 100 }), - }, - requester_accuracy_target: faker.number.float({ min: 0.1, max: 1 }), - requester_max_repeats: faker.number.int({ min: 1, max: 10 }), - requester_min_repeats: faker.number.int({ min: 1, max: 10 }), - requester_question_example: [faker.internet.url()], - requester_question: { en: faker.lorem.sentence() }, - taskdata_uri: faker.internet.url(), - job_total_tasks: faker.number.int({ min: 1, max: 1000 }), - task_bid_price: faker.number.float({ min: 0.01, max: 10 }), - groundtruth_uri: faker.internet.url(), - public_results: faker.datatype.boolean(), - oracle_stake: faker.number.int({ min: 1, max: 100 }), - repo_uri: faker.internet.url(), - ro_uri: faker.internet.url(), - restricted_audience: {}, - requester_restricted_answer_set: {}, - taskdata: [ - { - task_key: faker.string.uuid(), - datapoint_uri: faker.internet.url(), - datapoint_hash: faker.string.uuid(), - datapoint_text: { en: faker.lorem.sentence() }, - }, - ], - qualifications: [faker.lorem.word()], - ...overrides, - }; -} diff --git a/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.spec.ts b/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.spec.ts index e06ba03ca1..2f9363f42e 100644 --- a/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.spec.ts @@ -7,51 +7,19 @@ import { faker } from '@faker-js/faker'; import { createMock } from '@golevelup/ts-jest'; import { Encryption } from '@human-protocol/sdk'; import { Test } from '@nestjs/testing'; -import { AuthConfigService } from '../../common/config/auth-config.service'; import { CvatConfigService } from '../../common/config/cvat-config.service'; import { PGPConfigService } from '../../common/config/pgp-config.service'; -import { Web3ConfigService } from '../../common/config/web3-config.service'; -import { - HCAPTCHA_BOUNDING_BOX_MAX_POINTS, - HCAPTCHA_BOUNDING_BOX_MIN_POINTS, - HCAPTCHA_IMMO_MAX_LENGTH, - HCAPTCHA_IMMO_MIN_LENGTH, - HCAPTCHA_LANDMARK_MAX_POINTS, - HCAPTCHA_LANDMARK_MIN_POINTS, - HCAPTCHA_MAX_SHAPES_PER_IMAGE, - HCAPTCHA_MIN_SHAPES_PER_IMAGE, - HCAPTCHA_MINIMUM_SELECTION_AREA_PER_SHAPE, - HCAPTCHA_ORACLE_STAKE, - HCAPTCHA_POLYGON_MAX_POINTS, - HCAPTCHA_POLYGON_MIN_POINTS, -} from '../../common/constants'; import { ErrorJob } from '../../common/constants/errors'; -import { - CvatJobType, - FortuneJobType, - HCaptchaJobType, - JobCaptchaMode, - JobCaptchaRequestType, - JobCaptchaShapeType, -} from '../../common/enums/job'; +import { CvatJobType, FortuneJobType } from '../../common/enums/job'; import { ConflictError, ServerError, ValidationError, } from '../../common/errors'; -import { - generateBucketUrl, - listObjectsInBucket, -} from '../../common/utils/storage'; +import { generateBucketUrl } from '../../common/utils/storage'; import { StorageService } from '../storage/storage.service'; import { Web3Service } from '../web3/web3.service'; -import { - createJobCaptchaDto, - createJobCvatDto, - mockAuthConfigService, - mockCvatConfigService, - mockWeb3ConfigService, -} from './fixtures'; +import { createJobCvatDto, mockCvatConfigService } from './fixtures'; import { FortuneManifestDto } from './manifest.dto'; import { ManifestService } from './manifest.service'; import { @@ -72,19 +40,11 @@ describe('ManifestService', () => { ManifestService, { provide: Web3Service, useValue: createMock() }, { provide: StorageService, useValue: mockStorageService }, - { - provide: AuthConfigService, - useValue: mockAuthConfigService, - }, { provide: CvatConfigService, useValue: mockCvatConfigService, }, { provide: PGPConfigService, useValue: { encrypt: false } }, - { - provide: Web3ConfigService, - useValue: mockWeb3ConfigService, - }, { provide: Encryption, useValue: createMock() }, ], }).compile(); @@ -297,422 +257,6 @@ describe('ManifestService', () => { ).rejects.toThrow(new ConflictError(ErrorJob.DataNotExist)); }); }); - - describe('createHCaptchaManifest', () => { - const requestType = HCaptchaJobType.HCAPTCHA; - const tokenFundAmount = faker.number.int({ min: 1, max: 1000 }); - const tokenFundDecimals = faker.number.int({ min: 1, max: 18 }); - - beforeEach(() => { - const fileContent = JSON.stringify({ - [faker.internet.url()]: [true, true, true], - }); - (listObjectsInBucket as jest.Mock).mockResolvedValueOnce([ - `${faker.word.sample()}.jpg`, - `${faker.word.sample()}.jpg`, - `${faker.word.sample()}.jpg`, - ]); - mockStorageService.uploadJsonLikeData.mockResolvedValueOnce({ - url: faker.internet.url(), - hash: faker.string.uuid(), - }); - mockStorageService.downloadJsonLikeData.mockResolvedValueOnce( - fileContent, - ); - }); - - it('should create a valid HCaptcha manifest for COMPARISON job type', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: JobCaptchaShapeType.COMPARISON, - labelingPrompt: faker.lorem.sentence(), - groundTruths: faker.internet.url(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - exampleImages: [faker.internet.url(), faker.internet.url()], - }, - }); - - const result = await manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ); - - expect(result).toEqual({ - job_mode: JobCaptchaMode.BATCH, - requester_accuracy_target: jobDto.accuracyTarget, - request_config: {}, - restricted_audience: { - sitekey: [expect.any(Object)], - }, - requester_max_repeats: jobDto.maxRequests, - requester_min_repeats: jobDto.minRequests, - requester_question: { en: jobDto.annotations.labelingPrompt }, - job_total_tasks: 3, - task_bid_price: jobDto.annotations.taskBidPrice, - taskdata_uri: expect.any(String), - public_results: true, - oracle_stake: HCAPTCHA_ORACLE_STAKE, - repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, - ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, - request_type: JobCaptchaRequestType.IMAGE_LABEL_BINARY, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: {}, - requester_question_example: jobDto.annotations.exampleImages, - }); - }); - - it('should create a valid HCaptcha manifest for CATEGORIZATION job type', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: JobCaptchaShapeType.CATEGORIZATION, - labelingPrompt: faker.lorem.sentence(), - groundTruths: faker.internet.url(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - }, - }); - - const result = await manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ); - - expect(result).toEqual({ - job_mode: JobCaptchaMode.BATCH, - requester_accuracy_target: jobDto.accuracyTarget, - request_config: {}, - restricted_audience: { - sitekey: [expect.any(Object)], - }, - requester_max_repeats: jobDto.maxRequests, - requester_min_repeats: jobDto.minRequests, - requester_question: { en: jobDto.annotations.labelingPrompt }, - job_total_tasks: 3, - task_bid_price: jobDto.annotations.taskBidPrice, - taskdata_uri: expect.any(String), - public_results: true, - oracle_stake: HCAPTCHA_ORACLE_STAKE, - repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, - ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, - request_type: JobCaptchaRequestType.IMAGE_LABEL_MULTIPLE_CHOICE, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: expect.any(Object), - }); - }); - - it('should create a valid HCaptcha manifest for POLYGON job type', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: JobCaptchaShapeType.POLYGON, - labelingPrompt: faker.lorem.sentence(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - groundTruths: faker.internet.url(), - label: faker.lorem.word(), - }, - }); - - const result = await manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ); - - expect(result).toEqual({ - job_mode: JobCaptchaMode.BATCH, - requester_accuracy_target: jobDto.accuracyTarget, - request_config: { - shape_type: JobCaptchaShapeType.POLYGON, - min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, - max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, - min_points: HCAPTCHA_POLYGON_MIN_POINTS, - max_points: HCAPTCHA_POLYGON_MAX_POINTS, - minimum_selection_area_per_shape: - HCAPTCHA_MINIMUM_SELECTION_AREA_PER_SHAPE, - }, - restricted_audience: { - sitekey: [expect.any(Object)], - }, - requester_max_repeats: jobDto.maxRequests, - requester_min_repeats: jobDto.minRequests, - requester_question: { en: jobDto.annotations.labelingPrompt }, - requester_question_example: [], - job_total_tasks: 3, - task_bid_price: jobDto.annotations.taskBidPrice, - taskdata_uri: expect.any(String), - public_results: true, - oracle_stake: HCAPTCHA_ORACLE_STAKE, - repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, - ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, - request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - }); - }); - - it('should throw ValidationError for invalid POLYGON job type without label', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: JobCaptchaShapeType.POLYGON, - labelingPrompt: faker.lorem.sentence(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - groundTruths: faker.internet.url(), - }, - }); - - await expect( - manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ), - ).rejects.toThrow( - new ValidationError(ErrorJob.JobParamsValidationFailed), - ); - }); - - it('should create a valid HCaptcha manifest for POINT job type', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: JobCaptchaShapeType.POINT, - labelingPrompt: faker.lorem.sentence(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - groundTruths: faker.internet.url(), - label: faker.lorem.word(), - }, - }); - - const result = await manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ); - - expect(result).toEqual({ - job_mode: JobCaptchaMode.BATCH, - requester_accuracy_target: jobDto.accuracyTarget, - request_config: { - shape_type: JobCaptchaShapeType.POINT, - min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, - max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, - min_points: HCAPTCHA_LANDMARK_MIN_POINTS, - max_points: HCAPTCHA_LANDMARK_MAX_POINTS, - }, - restricted_audience: { - sitekey: [expect.any(Object)], - }, - requester_max_repeats: jobDto.maxRequests, - requester_min_repeats: jobDto.minRequests, - requester_question: { en: jobDto.annotations.labelingPrompt }, - requester_question_example: jobDto.annotations.exampleImages || [], - job_total_tasks: 3, - task_bid_price: jobDto.annotations.taskBidPrice, - taskdata_uri: expect.any(String), - public_results: true, - oracle_stake: HCAPTCHA_ORACLE_STAKE, - repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, - ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, - request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - }); - }); - - it('should throw ValidationError for invalid POINT job type without label', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: JobCaptchaShapeType.POINT, - labelingPrompt: faker.lorem.sentence(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - groundTruths: faker.internet.url(), - }, - }); - - await expect( - manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ), - ).rejects.toThrow( - new ValidationError(ErrorJob.JobParamsValidationFailed), - ); - }); - - it('should create a valid HCaptcha manifest for BOUNDING_BOX job type', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: JobCaptchaShapeType.BOUNDING_BOX, - labelingPrompt: faker.lorem.sentence(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - groundTruths: faker.internet.url(), - label: faker.lorem.word(), - }, - }); - - const result = await manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ); - - expect(result).toEqual({ - job_mode: JobCaptchaMode.BATCH, - requester_accuracy_target: jobDto.accuracyTarget, - request_config: { - shape_type: JobCaptchaShapeType.BOUNDING_BOX, - min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, - max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, - min_points: HCAPTCHA_BOUNDING_BOX_MIN_POINTS, - max_points: HCAPTCHA_BOUNDING_BOX_MAX_POINTS, - }, - restricted_audience: { - sitekey: [expect.any(Object)], - }, - requester_max_repeats: jobDto.maxRequests, - requester_min_repeats: jobDto.minRequests, - requester_question: { en: jobDto.annotations.labelingPrompt }, - requester_question_example: jobDto.annotations.exampleImages || [], - job_total_tasks: 3, - task_bid_price: jobDto.annotations.taskBidPrice, - taskdata_uri: expect.any(String), - public_results: true, - oracle_stake: HCAPTCHA_ORACLE_STAKE, - repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, - ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, - request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - }); - }); - - it('should throw ValidationError for invalid BOUNDING_BOX job type without label', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: JobCaptchaShapeType.BOUNDING_BOX, - labelingPrompt: faker.lorem.sentence(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - groundTruths: faker.internet.url(), - }, - }); - - await expect( - manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ), - ).rejects.toThrow( - new ValidationError(ErrorJob.JobParamsValidationFailed), - ); - }); - - it('should create a valid HCaptcha manifest for IMMO job type', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: JobCaptchaShapeType.IMMO, - labelingPrompt: faker.lorem.sentence(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - groundTruths: faker.internet.url(), - label: faker.lorem.word(), - }, - }); - - const result = await manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ); - - expect(result).toEqual({ - job_mode: JobCaptchaMode.BATCH, - requester_accuracy_target: jobDto.accuracyTarget, - request_config: { - multiple_choice_max_choices: 1, - multiple_choice_min_choices: 1, - overlap_threshold: null, - answer_type: 'str', - max_length: HCAPTCHA_IMMO_MAX_LENGTH, - min_length: HCAPTCHA_IMMO_MIN_LENGTH, - }, - restricted_audience: { - sitekey: [expect.any(Object)], - }, - requester_max_repeats: jobDto.maxRequests, - requester_min_repeats: jobDto.minRequests, - requester_question: { en: jobDto.annotations.labelingPrompt }, - job_total_tasks: 3, - task_bid_price: jobDto.annotations.taskBidPrice, - taskdata: [], - taskdata_uri: expect.any(String), - public_results: true, - oracle_stake: HCAPTCHA_ORACLE_STAKE, - repo_uri: mockWeb3ConfigService.hCaptchaReputationOracleURI, - ro_uri: mockWeb3ConfigService.hCaptchaRecordingOracleURI, - request_type: JobCaptchaRequestType.TEXT_FREEE_NTRY, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - }); - }); - - it('should throw ValidationError for invalid IMMO job type without label', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: JobCaptchaShapeType.IMMO, - labelingPrompt: faker.lorem.sentence(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - groundTruths: faker.internet.url(), - }, - }); - - await expect( - manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ), - ).rejects.toThrow( - new ValidationError(ErrorJob.JobParamsValidationFailed), - ); - }); - - it('should throw ValidationError for invalid job type', async () => { - const jobDto = createJobCaptchaDto({ - annotations: { - typeOfJob: 'INVALID_JOB_TYPE' as JobCaptchaShapeType, - labelingPrompt: faker.lorem.sentence(), - taskBidPrice: faker.number.float({ min: 0.1, max: 10 }), - groundTruths: faker.internet.url(), - }, - }); - - await expect( - manifestService.createManifest( - jobDto, - requestType, - tokenFundAmount, - tokenFundDecimals, - ), - ).rejects.toThrow(new ValidationError(ErrorJob.HCaptchaInvalidJobType)); - }); - }); }); describe('uploadManifest', () => { diff --git a/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.ts b/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.ts index 6276568d13..93a47b8481 100644 --- a/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.ts +++ b/packages/apps/job-launcher/server/src/modules/manifest/manifest.service.ts @@ -5,34 +5,13 @@ import { } from '@nestjs/common'; import { validate } from 'class-validator'; import { ethers } from 'ethers'; -import { v4 as uuidv4 } from 'uuid'; -import { AuthConfigService } from '../../common/config/auth-config.service'; import { CvatConfigService } from '../../common/config/cvat-config.service'; import { PGPConfigService } from '../../common/config/pgp-config.service'; -import { Web3ConfigService } from '../../common/config/web3-config.service'; -import { - HCAPTCHA_BOUNDING_BOX_MAX_POINTS, - HCAPTCHA_BOUNDING_BOX_MIN_POINTS, - HCAPTCHA_IMMO_MAX_LENGTH, - HCAPTCHA_IMMO_MIN_LENGTH, - HCAPTCHA_LANDMARK_MAX_POINTS, - HCAPTCHA_LANDMARK_MIN_POINTS, - HCAPTCHA_MAX_SHAPES_PER_IMAGE, - HCAPTCHA_MINIMUM_SELECTION_AREA_PER_SHAPE, - HCAPTCHA_MIN_SHAPES_PER_IMAGE, - HCAPTCHA_NOT_PRESENTED_LABEL, - HCAPTCHA_ORACLE_STAKE, - HCAPTCHA_POLYGON_MAX_POINTS, - HCAPTCHA_POLYGON_MIN_POINTS, -} from '../../common/constants'; import { ErrorJob } from '../../common/constants/errors'; import { CvatJobType, FortuneJobType, HCaptchaJobType, - JobCaptchaMode, - JobCaptchaRequestType, - JobCaptchaShapeType, JobRequestType, } from '../../common/enums/job'; import { ConflictError, ValidationError } from '../../common/errors'; @@ -40,12 +19,7 @@ import { generateBucketUrl, listObjectsInBucket, } from '../../common/utils/storage'; -import { - CreateJob, - JobCaptchaAdvancedDto, - JobCaptchaDto, - JobCvatDto, -} from '../job/job.dto'; +import { CreateJob, JobCvatDto } from '../job/job.dto'; import { CvatAnnotationData, CvatCalculateJobBounty, @@ -59,7 +33,6 @@ import { FortuneManifestDto, HCaptchaManifestDto, ManifestDto, - RestrictedAudience, } from './manifest.dto'; @Injectable() @@ -68,8 +41,6 @@ export class ManifestService { constructor( private readonly web3Service: Web3Service, - private readonly authConfigService: AuthConfigService, - private readonly web3ConfigService: Web3ConfigService, private readonly cvatConfigService: CvatConfigService, private readonly pgpConfigService: PGPConfigService, private readonly storageService: StorageService, @@ -83,9 +54,6 @@ export class ManifestService { decimals: number, ): Promise { switch (requestType) { - case HCaptchaJobType.HCAPTCHA: - return this.createHCaptchaManifest(dto as JobCaptchaDto); - case FortuneJobType.FORTUNE: return { ...dto, @@ -311,226 +279,6 @@ export class ManifestService { }; } - private async createHCaptchaManifest( - jobDto: JobCaptchaDto, - ): Promise { - const jobType = jobDto.annotations.typeOfJob; - const dataUrl = generateBucketUrl(jobDto.data, HCaptchaJobType.HCAPTCHA); - const objectsInBucket = await listObjectsInBucket(dataUrl); - - const commonManifestProperties = { - job_mode: JobCaptchaMode.BATCH, - requester_accuracy_target: jobDto.accuracyTarget, - request_config: {}, - restricted_audience: this.buildHCaptchaRestrictedAudience( - jobDto.advanced, - ), - requester_max_repeats: jobDto.maxRequests, - requester_min_repeats: jobDto.minRequests, - requester_question: { en: jobDto.annotations.labelingPrompt }, - job_total_tasks: objectsInBucket.length, - task_bid_price: jobDto.annotations.taskBidPrice, - taskdata_uri: await this.generateAndUploadTaskData( - dataUrl.href, - objectsInBucket, - ), - public_results: true, - oracle_stake: HCAPTCHA_ORACLE_STAKE, - repo_uri: this.web3ConfigService.hCaptchaReputationOracleURI, - ro_uri: this.web3ConfigService.hCaptchaRecordingOracleURI, - ...(jobDto.qualifications && { - qualifications: jobDto.qualifications, - }), - }; - - let groundTruthsData; - if (jobDto.annotations.groundTruths) { - groundTruthsData = await this.storageService.downloadJsonLikeData( - jobDto.annotations.groundTruths, - ); - } - - switch (jobType) { - case JobCaptchaShapeType.COMPARISON: - return { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.IMAGE_LABEL_BINARY, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: {}, - requester_question_example: jobDto.annotations.exampleImages || [], - }; - - case JobCaptchaShapeType.CATEGORIZATION: - return { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.IMAGE_LABEL_MULTIPLE_CHOICE, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: - this.buildHCaptchaRestrictedAnswerSet(groundTruthsData), - }; - - case JobCaptchaShapeType.POLYGON: - if (!jobDto.annotations.label) { - throw new ValidationError(ErrorJob.JobParamsValidationFailed); - } - - return { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, - request_config: { - shape_type: JobCaptchaShapeType.POLYGON, - min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, - max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, - min_points: HCAPTCHA_POLYGON_MIN_POINTS, - max_points: HCAPTCHA_POLYGON_MAX_POINTS, - minimum_selection_area_per_shape: - HCAPTCHA_MINIMUM_SELECTION_AREA_PER_SHAPE, - }, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - requester_question_example: jobDto.annotations.exampleImages || [], - }; - - case JobCaptchaShapeType.POINT: - if (!jobDto.annotations.label) { - throw new ValidationError(ErrorJob.JobParamsValidationFailed); - } - - return { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, - request_config: { - shape_type: jobType, - min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, - max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, - min_points: HCAPTCHA_LANDMARK_MIN_POINTS, - max_points: HCAPTCHA_LANDMARK_MAX_POINTS, - }, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - requester_question_example: jobDto.annotations.exampleImages || [], - }; - - case JobCaptchaShapeType.BOUNDING_BOX: - if (!jobDto.annotations.label) { - throw new ValidationError(ErrorJob.JobParamsValidationFailed); - } - - return { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.IMAGE_LABEL_AREA_SELECT, - request_config: { - shape_type: jobType, - min_shapes_per_image: HCAPTCHA_MIN_SHAPES_PER_IMAGE, - max_shapes_per_image: HCAPTCHA_MAX_SHAPES_PER_IMAGE, - min_points: HCAPTCHA_BOUNDING_BOX_MIN_POINTS, - max_points: HCAPTCHA_BOUNDING_BOX_MAX_POINTS, - }, - groundtruth_uri: jobDto.annotations.groundTruths, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - requester_question_example: jobDto.annotations.exampleImages || [], - }; - - case JobCaptchaShapeType.IMMO: - if (!jobDto.annotations.label) { - throw new ValidationError(ErrorJob.JobParamsValidationFailed); - } - - return { - ...commonManifestProperties, - request_type: JobCaptchaRequestType.TEXT_FREEE_NTRY, - request_config: { - multiple_choice_max_choices: 1, - multiple_choice_min_choices: 1, - overlap_threshold: null, - answer_type: 'str', - max_length: HCAPTCHA_IMMO_MAX_LENGTH, - min_length: HCAPTCHA_IMMO_MIN_LENGTH, - }, - requester_restricted_answer_set: { - [jobDto.annotations.label!]: { en: jobDto.annotations.label }, - }, - taskdata: [], - }; - - default: - throw new ValidationError(ErrorJob.HCaptchaInvalidJobType); - } - } - - private buildHCaptchaRestrictedAudience(advanced: JobCaptchaAdvancedDto) { - const restrictedAudience: RestrictedAudience = {}; - - restrictedAudience.sitekey = [ - { - [this.authConfigService.hCaptchaSiteKey]: { - score: 1, - }, - }, - ]; - - if (advanced.workerLanguage) { - restrictedAudience.lang = [{ [advanced.workerLanguage]: { score: 1 } }]; - } - - if (advanced.workerLocation) { - restrictedAudience.country = [ - { [advanced.workerLocation]: { score: 1 } }, - ]; - } - - if (advanced.targetBrowser) { - restrictedAudience.browser = [{ [advanced.targetBrowser]: { score: 1 } }]; - } - - return restrictedAudience; - } - - private buildHCaptchaRestrictedAnswerSet(groundTruthsData: any) { - const maxElements = 3; - const outputObject: any = {}; - - let elementCount = 0; - - for (const key of Object.keys(groundTruthsData)) { - if (elementCount >= maxElements) { - break; - } - - const value = groundTruthsData[key][0][0]; - outputObject[value] = { en: value, answer_example_uri: key }; - elementCount++; - } - - // Default case - outputObject['0'] = { en: HCAPTCHA_NOT_PRESENTED_LABEL }; - - return outputObject; - } - - private async generateAndUploadTaskData( - dataUrl: string, - objectNames: string[], - ) { - const data = objectNames.map((objectName) => { - return { - datapoint_uri: `${dataUrl}/${objectName}`, - datapoint_hash: 'undefined-hash', - task_key: uuidv4(), - }; - }); - - const { url } = await this.storageService.uploadJsonLikeData(data); - - return url; - } - async uploadManifest( chainId: ChainId, data: any,