Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/controllers/organization.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,21 @@ export class OrganizationController {
}
}


public removeAgreement = async (req: any, res: Response) => {
try {
const agreementId: string = req.params.agreementId;
const createdResponseModel = await this.organizationService.removeAgreement(
req.user.walletAddress,
agreementId);
res.status(createdResponseModel.statusCode).json(handleResponse(createdResponseModel));

} catch (error) {
console.error('Error editing an org:', error);
res.status(500).send('Internal Server Error');
}
}

public getContribAgreement = async (req: any, res: Response) => {
try {
const contributorId = req.params.contributorId;
Expand Down
3 changes: 3 additions & 0 deletions src/entities/org/organization.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export class Organization {
@Column({ type: 'varchar', length: 255 })
teamPointsContractAddress!: string;

@Column({ type: 'int', nullable: false, default: 42161 })
chainId!: number;

@Column({ type: 'varchar', nullable: true })
logo?: string;

Expand Down
2 changes: 1 addition & 1 deletion src/entities/users/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class User {

@OneToOne(() => Agreement, { nullable: true, cascade: true })
@JoinColumn({ name: 'agreement_id' })
agreement?: Relation<Agreement>;
agreement?: Relation<Agreement> | null;

@ManyToOne(() => Organization, { nullable: true, onDelete: 'CASCADE' })
@JoinColumn({ name: 'organization_id' })
Expand Down
4 changes: 3 additions & 1 deletion src/models/org/createOrg.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ export interface CreateOrgModel {
name: string;
logo: string;
teamPointsContractAddress: string;
chainId: number;
}

export const organizationScheme = Joi.object({
name: Joi.string().required(),
logo: Joi.string().optional().uri(),
teamPointsContractAddress: Joi.string().required()
teamPointsContractAddress: Joi.string().required(),
chainId: Joi.number().valid(42161, 421614, 42220).optional()
});
1 change: 1 addition & 0 deletions src/models/org/editOrg.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface OrgModel {
par: number;
totalFunds: number | null;
teamPointsContractAddress: string;
chainId: number;
compensationPeriod: CompensationPeriod | null;
compensationStartDay: Date | null;
assessmentDurationInDays: number | null;
Expand Down
2 changes: 1 addition & 1 deletion src/models/user/userList.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export interface UserListModel {
walletAddress?: string;
username: string;
profilePicture?: string;
agreement?: AgreementModel;
agreement?: AgreementModel | null;
}

1 change: 1 addition & 0 deletions src/routers/org.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class OrgRouter {
this._router.put('/', jwtMiddleware, upload.single('logo'), adminMiddleware, this.orgController.editOrg);
this._router.post('/agreement', jwtMiddleware, adminMiddleware, this.orgController.addAgreement);
this._router.put('/agreement/:agreementId', jwtMiddleware, adminMiddleware, this.orgController.editAgreement);
this._router.delete('/agreement/:agreementId', jwtMiddleware, adminMiddleware, this.orgController.removeAgreement);
}

public get router(): Router {
Expand Down
33 changes: 33 additions & 0 deletions src/services/organization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class OrganizationService {
organization.name = orgModel.name;
organization.logo = orgModel.logo;
organization.teamPointsContractAddress = orgModel.teamPointsContractAddress;
organization.chainId = orgModel.chainId;
organization.contributors = [creator];

await this.organizationRepository.save(organization);
Expand Down Expand Up @@ -168,6 +169,7 @@ export class OrganizationService {
assessmentDurationInDays: org.assessmentDurationInDays,
assessmentStartDelayInDays: org.assessmentStartDelayInDays,
teamPointsContractAddress: org.teamPointsContractAddress,
chainId: org.chainId,
totalFunds: org.totalFunds,
totalDistributedFiat: allCompensation.reduce((acc, c) => acc + +c.fiat, 0),
contributors: org.contributors?.map((u) => ({
Expand Down Expand Up @@ -256,6 +258,37 @@ export class OrganizationService {
return ResponseModel.createSuccess({ id: agreement.id });
}


/**
* Removes an agreement for a user in the organization
*/
public async removeAgreement(walletAddress: string, agreeementId: string)
: Promise<ResponseModel<CreatedResponseModel | null>> {

const agreement = await this.agreementRepository.findOne({
where: { id: agreeementId },
relations: ['user', 'user.organization']
});
if (!agreement) {
return ResponseModel.createError(new Error('Agreement not found!'), 404);
}
const user = await this.userRepository.findOne({
where: { id: agreement.user.id },
relations: ['organization']
});
if (!user || user.organization.id !== agreement.user.organization.id) {
return ResponseModel.createError(new Error('User not found or not part of the organization!'), 404);
}
user.agreement = null;
await this.userRepository.save(user); // 👈 This flushes the FK to NULL

await this.agreementRepository.remove(agreement); // 👈 Now safe to delete

// await this.agreementRepository.delete(agreement);

return ResponseModel.createSuccess({ id: agreement.id });
}

/**
* Get a user's agreement
*/
Expand Down
2 changes: 1 addition & 1 deletion src/services/round.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class RoundService {
} else {
orgs = await this.organizationRepository
.createQueryBuilder('organization')
.leftJoin('organization.rounds', 'round')
.leftJoinAndSelect('organization.rounds', 'round')
.leftJoinAndSelect('organization.contributors', 'contributors')
.where(qb => {
const subQuery = qb.subQuery()
Expand Down
19 changes: 14 additions & 5 deletions src/services/teamPoints.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,35 @@ import { User, Organization } from '../entities/index.js';
// ABI interface for just the isAdmin function
const ADMIN_ROLE_ABI = [
{
inputs: [{internalType: 'address', name: 'account', type: 'address'}],
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
name: 'isAdmin',
outputs: [{internalType: 'bool', name: '', type: 'bool'}],
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function'
}
];
// Mapping of chain IDs to RPC URLs
const chainIDToRPCUrl: Record<string, string> = {
42161: process.env.ARBITRUM_RPC_URL || '',
421614: process.env.ARBITRUM_SEPOLIA_RPC_URL || '',
42220: process.env.CELO_RPC_URL || ''
};

@injectable()
export class TeamPointsService {
private provider: ethers.JsonRpcProvider;
private userRepository;
private organizationRepository;

constructor() {
// Initialize provider from environment variable
this.provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
this.userRepository = AppDataSource.getRepository(User);
this.organizationRepository = AppDataSource.getRepository(Organization);
}

/**
* Check if a wallet address is an admin of the organization's team points contract
*/
// TODO: edit to use the chainID to set correctly provider
public async isAdmin(walletAddress: string): Promise<boolean> {
try {
// Get the user from the wallet address
Expand All @@ -51,11 +56,15 @@ export class TeamPointsService {
return false;
}

const provider = new ethers.JsonRpcProvider(
chainIDToRPCUrl[organization.chainId] || process.env.ARBITRUM_RPC_URL);


// Create a contract instance
const contract = new ethers.Contract(
organization.teamPointsContractAddress,
ADMIN_ROLE_ABI,
this.provider
provider
);

// Call the isAdmin function on the contract
Expand Down