Skip to content

Commit 5ee81bd

Browse files
authored
Merge pull request #75 from TalentLayer-Labs/BP-118_JobPostingConditions
2Bp 118 job posting conditions
2 parents 08a0f61 + 01feb0a commit 5ee81bd

25 files changed

Lines changed: 577 additions & 392 deletions
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { IRemoveBuilderPlaceCollaborator } from '../../../../pages/[domain]/admin/collaborator-card';
2+
import { User } from '.prisma/client';
3+
import { checkOwnerSignature, isCollaboratorExists } from '../../../utils/domain';
4+
import prisma from '../../../../postgre/postgreClient';
5+
import { ERROR_REMOVING_BUILDERPLACE_OWNER } from '../../../../modules/BuilderPlace/apiResponses';
6+
import { handleApiError } from '../../../utils/handleApiErrors';
7+
8+
export async function GET(req: Request) {}
9+
10+
export async function PUT(req: Request, { params }: { params: { id: string } }) {
11+
console.log('PUT');
12+
const body: IRemoveBuilderPlaceCollaborator = await req.json();
13+
console.log('params', params);
14+
console.log('json', body);
15+
16+
try {
17+
const response = await checkOwnerSignature(
18+
body.data.builderPlaceId,
19+
body.data.ownerTalentLayerId,
20+
body.signature,
21+
body.address,
22+
);
23+
24+
console.log('response', response instanceof Response);
25+
26+
if (response instanceof Response) {
27+
return response;
28+
}
29+
30+
/**
31+
* @dev: Check whether the collaborator is not the BuilderPlace owner
32+
*/
33+
if (response?.builderPlace?.owner?.address === body.data.collaboratorAddress) {
34+
console.log('Cant remove the owner as a collaborator');
35+
return Response.json({ error: 'Restricted access' }, { status: 400 });
36+
}
37+
38+
/**
39+
* @dev: Check whether the collaborator exists
40+
*/
41+
const existingCollaborators: User[] = response?.builderPlace?.collaborators || [];
42+
43+
if (!isCollaboratorExists(existingCollaborators, body.data.collaboratorAddress)) {
44+
console.log('Collaborator does not exist');
45+
return Response.json({ error: 'Not a collaborator' }, { status: 400 });
46+
}
47+
48+
console.log('Removing collaborator', body.data.collaboratorAddress);
49+
50+
let status = 500;
51+
let collaborator: User | null = null;
52+
53+
try {
54+
collaborator = await prisma.user.findUnique({
55+
where: {
56+
address: body.data.collaboratorAddress,
57+
},
58+
});
59+
60+
if (!collaborator) {
61+
status = 400;
62+
throw new Error('Collaborator not found');
63+
}
64+
65+
await prisma.builderPlace.update({
66+
where: {
67+
id: Number(body.data.builderPlaceId),
68+
},
69+
data: {
70+
collaborators: {
71+
disconnect: [{ id: collaborator.id }],
72+
},
73+
},
74+
});
75+
76+
console.log('Collaborator removed successfully', body.data.collaboratorAddress);
77+
} catch (error: any) {
78+
handleApiError(error, ERROR_REMOVING_BUILDERPLACE_OWNER, status);
79+
}
80+
81+
return Response.json(
82+
{
83+
message: 'Collaborator removed successfully',
84+
address: collaborator?.address,
85+
id: collaborator?.id,
86+
},
87+
{ status: 200 },
88+
);
89+
} catch (error: any) {
90+
return Response.json({ error: error.message }, { status: 400 });
91+
}
92+
}

src/app/api/collaborators/route.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { IRemoveBuilderPlaceCollaborator } from '../../../pages/[domain]/admin/collaborator-card';
2+
import { checkOwnerSignature, isCollaboratorExists } from '../../utils/domain';
3+
import { EntityStatus, User } from '.prisma/client';
4+
import prisma from '../../../postgre/postgreClient';
5+
import { handleApiError } from '../../utils/handleApiErrors';
6+
import {
7+
COLLABORATOR_NOT_FOUND,
8+
ERROR_ADDING_COLLABORATOR,
9+
ERROR_REMOVING_BUILDERPLACE_OWNER,
10+
USER_NOT_FOUND,
11+
USER_PROFILE_NOT_VERIFIED,
12+
} from '../../../modules/BuilderPlace/apiResponses';
13+
14+
export async function POST(req: Request) {
15+
console.log('POST');
16+
const body: IRemoveBuilderPlaceCollaborator = await req.json();
17+
console.log('json', body);
18+
19+
try {
20+
const response = await checkOwnerSignature(
21+
body.data.builderPlaceId,
22+
body.data.ownerTalentLayerId,
23+
body.signature,
24+
body.address,
25+
);
26+
27+
console.log('response', response instanceof Response);
28+
29+
if (response instanceof Response) {
30+
return response;
31+
}
32+
33+
/**
34+
* @dev: Check whether the collaborator exists
35+
*/
36+
const existingCollaborators: User[] = response?.builderPlace?.collaborators || [];
37+
38+
if (isCollaboratorExists(existingCollaborators, body.data.collaboratorAddress)) {
39+
console.log('Collaborator already exists');
40+
return Response.json({ error: 'Already a collaborator' }, { status: 400 });
41+
}
42+
43+
console.log('Adding collaborator', body.data.collaboratorAddress);
44+
45+
let errorMessage = ERROR_ADDING_COLLABORATOR;
46+
let status = 500;
47+
let collaborator: User | null = null;
48+
49+
try {
50+
const newCollaborator = await prisma.user.findUnique({
51+
where: {
52+
address: body.data.collaboratorAddress,
53+
},
54+
});
55+
56+
if (!newCollaborator) {
57+
errorMessage = USER_NOT_FOUND;
58+
throw new Error(USER_NOT_FOUND);
59+
}
60+
61+
//TODO still useful?
62+
if (newCollaborator?.status === EntityStatus.PENDING) {
63+
errorMessage = USER_PROFILE_NOT_VERIFIED;
64+
throw new Error(USER_PROFILE_NOT_VERIFIED);
65+
}
66+
67+
await prisma.builderPlace.update({
68+
where: {
69+
id: Number(body.data.builderPlaceId),
70+
},
71+
data: {
72+
collaborators: {
73+
connect: [{ id: newCollaborator.id }],
74+
},
75+
},
76+
});
77+
78+
console.log('Collaborator added successfully', body.data.collaboratorAddress);
79+
} catch (error: any) {
80+
handleApiError(error, errorMessage, status);
81+
}
82+
83+
return Response.json(
84+
{
85+
message: 'Collaborator added successfully',
86+
// address: newCollaborator?.address,
87+
// id: newCollaborator?.id,
88+
},
89+
{ status: 200 },
90+
);
91+
} catch (error: any) {
92+
return Response.json({ error: error.message }, { status: 400 });
93+
}
94+
}

src/app/api/platforms/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ export async function POST(req: Request) {
6060

6161
return Response.json({ id: builderPlace.id }, { status: 201 });
6262
} catch (error: any) {
63+
console.log('errrrrrrrrrrrrrrrrrror', error);
6364
// @TODO: move error handle to a middleware ? or factorize it ?
64-
let message = 'Failed to create plaform';
65+
let message = 'Failed to create platform';
6566
if (error instanceof PrismaClientKnownRequestError) {
6667
if (error.code === 'P2002') {
6768
const target = (error.meta?.target as string)[0] || 'data';

src/app/utils/delegate.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { NetworkEnum } from '../../types';
88
export async function isPlatformAllowedToDelegate(
99
chainId: number,
1010
userAddress: string,
11-
): Promise<Response | void> {
11+
): Promise<Response | boolean> {
1212
const user = await getUserByAddress(chainId, userAddress);
1313
const delegateAddresses: string[] = user.data?.data?.users[0]?.delegates.map((delegate: any) =>
1414
delegate.toLowerCase(),
@@ -23,6 +23,9 @@ export async function isPlatformAllowedToDelegate(
2323
{ error: 'Delegation is Not activated for this address' },
2424
{ status: 401 },
2525
);
26+
else {
27+
return true;
28+
}
2629
}
2730

2831
export async function getDelegationSigner(): Promise<WalletClient | null> {

src/app/utils/domain.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { recoverMessageAddress } from 'viem';
2+
import { User } from '.prisma/client';
3+
import {
4+
getBuilderPlaceByCollaboratorAddressAndId,
5+
getBuilderPlaceByOwnerTlIdAndId,
6+
} from '../../modules/BuilderPlace/actions/builderPlace';
7+
8+
/**
9+
* Checks if the signature is from a BuilderPlace collaborator by getting the BuilderPlace
10+
* by the collaborator address & BuilderPlace Database id
11+
* @param id: Database BuilderPlace id
12+
* @param signature: Signature to verify
13+
*/
14+
export const checkSignature = async (id: string, signature: `0x${string}` | Uint8Array) => {
15+
try {
16+
const address = await recoverMessageAddress({
17+
message: id,
18+
signature: signature,
19+
});
20+
21+
const builderPlace = await getBuilderPlaceByCollaboratorAddressAndId(
22+
address.toLocaleLowerCase(),
23+
id,
24+
);
25+
26+
if (!builderPlace) {
27+
return Response.json({ error: 'No BuilderPlace found' }, { status: 400 });
28+
}
29+
30+
return { builderPlace, address };
31+
} catch (error) {
32+
console.error('Error in checkSignature:', error);
33+
34+
return Response.json(
35+
{ error: 'An error occurred while verifying the signature' },
36+
{ status: 500 },
37+
);
38+
}
39+
};
40+
41+
/**
42+
* Checks if the signature is from the BuilderPlace owner by getting the BuilderPlace
43+
* by the owner DataBase id & BuilderPlace Database id
44+
* @param builderPlaceId: Database BuilderPlace id
45+
* @param ownerId: Database BuilderPlace owner id
46+
* @param signature: Signature to verify
47+
* @param address: Address to verify
48+
* @throws 401 if the signature is not from the BuilderPlace owner
49+
*/
50+
export const checkOwnerSignature = async (
51+
builderPlaceId: string,
52+
ownerId: string,
53+
signature: `0x${string}` | Uint8Array,
54+
address: `0x${string}`,
55+
) => {
56+
try {
57+
const signatureAddress = await recoverMessageAddress({
58+
message: `connect with ${address}`,
59+
signature: signature,
60+
});
61+
62+
const builderPlace = await getBuilderPlaceByOwnerTlIdAndId(ownerId, builderPlaceId);
63+
64+
/**
65+
* Check whether the signature is from the BuilderPlace owner
66+
*/
67+
if (
68+
builderPlace &&
69+
signatureAddress.toLocaleLowerCase() !== builderPlace?.owner?.address?.toLocaleLowerCase()
70+
) {
71+
return Response.json({ error: 'Not BuilderPlace owner' }, { status: 401 });
72+
}
73+
74+
return { builderPlace, address: signatureAddress };
75+
} catch (error) {
76+
console.error('Error in checkSignature:', error);
77+
78+
return Response.json(
79+
{ error: 'An error occurred while verifying the signature' },
80+
{ status: 500 },
81+
);
82+
}
83+
};
84+
85+
export const isCollaboratorExists = (
86+
collaborators: User[] = [],
87+
newCollaboratorAddress: string,
88+
): boolean => {
89+
console.log(
90+
'collaborators',
91+
collaborators.map(collaborator => {
92+
console.log(collaborator.address);
93+
}),
94+
);
95+
console.log('newCollaboratorAddress', newCollaboratorAddress);
96+
return collaborators.some(
97+
collaborator =>
98+
collaborator?.address?.toLocaleLowerCase() === newCollaboratorAddress.toLocaleLowerCase(),
99+
);
100+
};

src/components/AsyncButton.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,28 @@ function AsyncButton({
22
isSubmitting,
33
label = 'Create',
44
onClick,
5-
buttonCss,
5+
validateButtonCss,
6+
loadingButtonCss,
7+
disabled = false,
68
}: {
79
isSubmitting: boolean;
810
label?: string;
911
onClick: () => Promise<void>;
10-
buttonCss?: string;
12+
validateButtonCss?: string;
13+
loadingButtonCss?: string;
14+
disabled?: boolean;
1115
}) {
1216
return (
1317
<div className='flex flex-row justify-between items-center'>
1418
{isSubmitting ? (
1519
<button
1620
type='button'
1721
disabled
18-
className={`py-2 px-5 bg-primary text-primary opacity-50 rounded-xl border inline-flex items-center ${buttonCss}`}>
22+
className={`${
23+
loadingButtonCss
24+
? loadingButtonCss
25+
: `py-2 px-5 bg-primary text-primary opacity-50 rounded-xl border inline-flex items-center`
26+
}`}>
1927
<svg
2028
role='status'
2129
className='inline mr-2 w-4 h-4 text-base-content animate-spin '
@@ -37,8 +45,11 @@ function AsyncButton({
3745
<button
3846
type='button'
3947
onClick={onClick}
48+
disabled={disabled}
4049
className={`${
41-
buttonCss ? buttonCss : `grow px-5 py-2 rounded-xl bg-primary text-primary`
50+
validateButtonCss
51+
? validateButtonCss
52+
: `grow px-5 py-2 rounded-xl bg-primary text-primary`
4253
}`}>
4354
{label}
4455
</button>

src/components/ConfigurePlatform/ConfigurePlatformForm.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const ethAddressRegex = /^0x[a-fA-F0-9]{40}$/;
3939

4040
const validationSchema = Yup.object({
4141
subdomain: Yup.string().required('Subdomain is required'),
42+
//TODO check Yup, try to make the JobConditions work ? Problème, quand on push on ne submit pas,
43+
// donc en théorie ça ne passera pas. Faut juste essayer de faire en sorte que
44+
// les err messages ne reset pas à chaque change. Un genre de prevent default ?
4245

4346
// tempFormValues: Yup.object({
4447
// tempNftAddress: Yup.string().matches(ethAddressRegex, 'Invalid Ethereum address').notRequired(),

src/components/ConfigurePlatform/JobPostingConditionsFieldArray.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ function JobPostingConditionsFieldArray({
179179
<AsyncButton
180180
label={'Add NFT Condition'}
181181
isSubmitting={nftSubmitting}
182-
buttonCss={'w-52'}
182+
validateButtonCss={'w-52 grow px-5 py-2 rounded-xl bg-primary text-primary'}
183183
onClick={() =>
184184
addJobPostingConditions(push, setFieldValue, setFieldError, {
185185
type: 'NFT',
@@ -230,7 +230,7 @@ function JobPostingConditionsFieldArray({
230230
<AsyncButton
231231
label={'Add Token Condition'}
232232
isSubmitting={tokenSubmitting}
233-
buttonCss={'w-52'}
233+
validateButtonCss={'w-52 grow px-5 py-2 rounded-xl bg-primary text-primary'}
234234
onClick={() =>
235235
addJobPostingConditions(push, setFieldValue, setFieldError, {
236236
type: 'Token',

0 commit comments

Comments
 (0)