Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
68970de
feat(safe): install safe-core-sdk and safe-ethers-lib
Majormaxx Sep 27, 2025
8db5d4c
feat(org): add safe configuration fields to organization entity
Majormaxx Sep 27, 2025
cce211e
refactor(controllers): removed any casts and fixed type augmentation
Majormaxx Sep 27, 2025
1f7d519
feat(org): updated RecognitionTokenMode enum to support MINT and TRAN…
Majormaxx Sep 27, 2025
0a04315
feat(org): updated updateSafeConfiguration to use new enum
Majormaxx Sep 27, 2025
f6dd48f
feat(payout): added Payout, TxProposal, and PayoutRecipient entities
Majormaxx Sep 27, 2025
82c075f
feat(payout): created PayoutService with initial structure
Majormaxx Sep 27, 2025
563ccfc
feat(org): add MINTER_ROLE validation for recognition token
Majormaxx Sep 27, 2025
969afd8
feat(org): add controller and router for configuration
Majormaxx Sep 27, 2025
dc9d5fc
fix(payout): removed previewPayout and fixed error handling in contro…
Majormaxx Sep 27, 2025
ad7b448
Added MINTER_ROLE enforcement for TeamPoints minting scenarios
Majormaxx Sep 29, 2025
2f55055
Removed sensitive configuration
Majormaxx Sep 29, 2025
8805767
refactor: fetch token decimals from contract instead of accepting as …
Majormaxx Oct 5, 2025
936fb81
refactor: standardized router and controller patterns with dependency…
Majormaxx Oct 5, 2025
fba064b
chore: removed unnecessary gitignore entry
Majormaxx Oct 5, 2025
caa1bab
docs: clarified payout methods are deferred to Module-II
Majormaxx Oct 5, 2025
285dbaa
refactor: converted payout router to class-based pattern for consistency
Majormaxx Oct 5, 2025
2793f3d
fix: resolved TypeScript compilation errors
Majormaxx Oct 6, 2025
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@ package-lock.json
/lib/
.env
dist
mysql-data
mysql-data
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"start": "yarn build && node dist/src/index.js",
"start-background": "yarn build && node dist/src/roundsStartJob.index.js",
"export": "yarn build && node dist/src/export.js",
"dev": "npx ts-node-dev src/index.ts",
"dev": "npx ts-node-dev --src/index.ts",
"lint": "tslint --fix -c tslint.json 'src/**/*.ts'",
"test": "mocha --config test/.mocharc.yml"
},
Expand All @@ -19,6 +19,11 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.669.0",
"@aws-sdk/lib-storage": "^3.669.0",
"@safe-global/protocol-kit": "^5.2.17",
"@safe-global/safe-core-sdk": "^3.3.5",
"@safe-global/safe-ethers-lib": "^1.9.4",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
Comment thread
Majormaxx marked this conversation as resolved.
"cors": "2.8.5",
"crypto": "1.0.1",
"desm": "1.3.1",
Expand All @@ -27,10 +32,11 @@
"express": "^4.20.0",
"express-validator": "^7.0.1",
"inversify": "6.0.2",
"inversify-express-utils": "^6.4.6",
"joi": "^17.4.0",
"jsonwebtoken": "9.0.2",
"multer": "^1.4.4",
"mysql2":"^3.11.3",
"mysql2": "^3.11.3",
"node-cron": "^3.0.0",
"nodemailer": "^6.6.3",
"passport": "^0.7.0",
Expand All @@ -55,8 +61,9 @@
"@types/uuid": "^10.0.0",
"chai": "^5.1.1",
"chai-http": "^4.4.0",
"csv-writer":"^1.6.0",
"csv-writer": "^1.6.0",
"mocha": "^10.4.0",
"ts-mockito": "^2.6.1",
"ts-node": "^10.9.2",
"ts-node-dev": "2.0.0",
"tslint": "^6.1.3",
Expand Down
141 changes: 141 additions & 0 deletions scripts/check-test-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Script to check existing test data and create mock data if needed
*/
import { config as dotenv_config } from 'dotenv';
import mysql from 'mysql2/promise';

dotenv_config();

interface TestDataResult {
hasData: boolean;
testOrgId?: string;
testRoundId?: string;
orgName?: string;
roundNumber?: number;
error?: string;
}

async function createConnection() {
return await mysql.createConnection({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT) || 3306,
user: process.env.DB_UNAME,
password: process.env.DB_PASS,
database: process.env.DB_NAME
});
}

async function checkTestData(): Promise<TestDataResult> {
let connection;
try {
connection = await createConnection();
console.log('πŸ” Checking existing test data...\n');

// Check organizations with Safe configuration
const [orgsWithSafe] = await connection.execute(`
SELECT id, name, safeAddress, safeChainId, stablecoinAddress, recognitionTokenAddress
FROM organizations
WHERE safeAddress IS NOT NULL
LIMIT 5
`) as any[];

console.log(`πŸ“Š Organizations with Safe config: ${orgsWithSafe.length}`);
if (orgsWithSafe.length > 0) {
console.log('Sample organizations:');
orgsWithSafe.forEach((org: any) => {
console.log(` - ${org.name} (${org.id})`);
console.log(` Safe: ${org.safeAddress} (Chain: ${org.safeChainId})`);
console.log(` Stablecoin: ${org.stablecoinAddress || 'Not configured'}`);
});
}

// Check completed rounds without payouts
const [incompleteRounds] = await connection.execute(`
SELECT r.id, r.roundNumber, r.isCompleted, r.txHash, o.name as orgName
FROM rounds r
JOIN organizations o ON r.organization_id = o.id
WHERE r.isCompleted = true AND r.txHash IS NULL
LIMIT 5
`) as any[];

console.log(`\nπŸ”„ Completed rounds without payouts: ${incompleteRounds.length}`);
if (incompleteRounds.length > 0) {
console.log('Sample rounds:');
incompleteRounds.forEach((round: any) => {
console.log(` - Round ${round.roundNumber} (${round.id}) - ${round.orgName}`);
});
}

// Check compensation data
const [compensationData] = await connection.execute(`
SELECT COUNT(*) as count, r.id as roundId, r.roundNumber
FROM contributor_round_compensations crc
JOIN rounds r ON crc.round_id = r.id
WHERE r.isCompleted = true AND r.txHash IS NULL
GROUP BY r.id, r.roundNumber
LIMIT 5
`) as any[];

console.log(`\nπŸ’° Rounds with compensation data: ${compensationData.length}`);
if (compensationData.length > 0) {
console.log('Sample compensation data:');
compensationData.forEach((comp: any) => {
console.log(` - Round ${comp.roundNumber}: ${comp.count} contributors`);
});
}

// Check users with wallet addresses
const [usersWithWallets] = await connection.execute(`
SELECT COUNT(*) as count FROM users WHERE walletAddress IS NOT NULL
`) as any[];

console.log(`\nπŸ‘₯ Users with wallet addresses: ${usersWithWallets[0].count}`);

// Summary
console.log('\nπŸ“‹ Test Data Summary:');
console.log(`βœ… Organizations with Safe: ${orgsWithSafe.length > 0 ? 'YES' : 'NO'}`);
console.log(`βœ… Rounds ready for payout: ${incompleteRounds.length > 0 ? 'YES' : 'NO'}`);
console.log(`βœ… Compensation data: ${compensationData.length > 0 ? 'YES' : 'NO'}`);
console.log(`βœ… Users with wallets: ${usersWithWallets[0].count > 0 ? 'YES' : 'NO'}`);

const hasTestData = orgsWithSafe.length > 0 && incompleteRounds.length > 0 &&
compensationData.length > 0 && usersWithWallets[0].count > 0;

if (hasTestData) {
console.log('\nπŸŽ‰ Sufficient test data exists! Ready to proceed with implementation.');

return {
hasData: true,
testOrgId: orgsWithSafe[0].id,
testRoundId: incompleteRounds[0].id,
orgName: orgsWithSafe[0].name,
roundNumber: incompleteRounds[0].roundNumber
};
} else {
console.log('\n⚠️ Insufficient test data. Mock data creation needed.');
return { hasData: false };
}

} catch (error: any) {
console.error('❌ Error checking test data:', error.message);
return { hasData: false, error: error.message };
} finally {
if (connection) {
await connection.end();
}
}
}

// Run the check
checkTestData().then(result => {
if (result.hasData) {
console.log('\nπŸš€ Test Data Available:');
console.log(` Organization ID: ${result.testOrgId}`);
console.log(` Round ID: ${result.testRoundId}`);
console.log(` Test URLs:`);
console.log(` GET /api/v1/payouts/rounds?orgId=${result.testOrgId}`);
console.log(` GET /api/v1/payouts/preview?roundId=${result.testRoundId}`);
} else {
console.log('\nπŸ“ Next Step: Create mock data');
}
}).catch(console.error);
146 changes: 146 additions & 0 deletions scripts/create-mock-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Create mock test data for Module II implementation
* This will be removed once real data is available
*/

interface MockOrganization {
id: string;
name: string;
safeAddress: string;
safeChainId: number;
stablecoinAddress: string;
stablecoinDecimals: number;
recognitionTokenAddress: string;
recognitionTokenDecimals: number;
recognitionTokenMode: string;
}

interface MockRound {
id: string;
roundNumber: number;
isCompleted: boolean;
txHash: string | null;
organizationId: string;
startDate: string;
endDate: string;
compensationCycleStartDate: string;
compensationCycleEndDate: string;
}

interface MockContributor {
id: string;
walletAddress: string;
name: string;
fiat: number;
tp: number;
culturalScore: number;
workScore: number;
}

interface MockData {
organization: MockOrganization;
round: MockRound;
contributors: MockContributor[];
}

const mockData: MockData = {
// Mock organization with Safe configuration (from Module A)
organization: {
id: 'mock-org-123',
name: 'Test Organization',
safeAddress: '0x1234567890123456789012345678901234567890',
safeChainId: 421614, // Arbitrum Sepolia
stablecoinAddress: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd',
stablecoinDecimals: 6,
recognitionTokenAddress: '0xfedcbafedcbafedcbafedcbafedcbafedcbafedcba',
recognitionTokenDecimals: 18,
recognitionTokenMode: 'MINT'
},

// Mock completed round without payout
round: {
id: 'mock-round-456',
roundNumber: 5,
isCompleted: true,
txHash: null, // No payout yet
organizationId: 'mock-org-123',
startDate: '2024-01-01',
endDate: '2024-01-31',
compensationCycleStartDate: '2024-01-01',
compensationCycleEndDate: '2024-01-31'
},

// Mock contributors with compensation data
contributors: [
{
id: 'user-1',
walletAddress: '0x1111111111111111111111111111111111111111',
name: 'Alice Developer',
fiat: 1500.00,
tp: 500,
culturalScore: 4.2,
workScore: 4.5
},
{
id: 'user-2',
walletAddress: '0x2222222222222222222222222222222222222222',
name: 'Bob Designer',
fiat: 1200.00,
tp: 800,
culturalScore: 4.0,
workScore: 4.1
},
{
id: 'user-3',
walletAddress: '0x3333333333333333333333333333333333333333',
name: 'Carol Manager',
fiat: 2000.00,
tp: 300,
culturalScore: 4.8,
workScore: 4.3
}
]
};

console.log('πŸ“ Mock Test Data Created for Module II Implementation\n');

console.log('🏒 Mock Organization:');
console.log(` ID: ${mockData.organization.id}`);
console.log(` Name: ${mockData.organization.name}`);
console.log(` Safe: ${mockData.organization.safeAddress} (Chain: ${mockData.organization.safeChainId})`);
console.log(` Stablecoin: ${mockData.organization.stablecoinAddress} (${mockData.organization.stablecoinDecimals} decimals)`);
console.log(` Recognition Token: ${mockData.organization.recognitionTokenAddress} (${mockData.organization.recognitionTokenDecimals} decimals)`);

console.log('\nπŸ”„ Mock Round:');
console.log(` ID: ${mockData.round.id}`);
console.log(` Round Number: ${mockData.round.roundNumber}`);
console.log(` Status: Completed, No Payout Yet`);

console.log('\nπŸ‘₯ Mock Contributors:');
mockData.contributors.forEach(contributor => {
console.log(` - ${contributor.name} (${contributor.id})`);
console.log(` Wallet: ${contributor.walletAddress}`);
console.log(` Fiat: $${contributor.fiat}, TP: ${contributor.tp}`);
console.log(` Scores: Cultural ${contributor.culturalScore}, Work ${contributor.workScore}`);
});

const totalFiat = mockData.contributors.reduce((sum, c) => sum + c.fiat, 0);
const totalTP = mockData.contributors.reduce((sum, c) => sum + c.tp, 0);

console.log('\nπŸ’° Totals:');
console.log(` Total Fiat Payout: $${totalFiat}`);
console.log(` Total TP Payout: ${totalTP}`);
console.log(` Recipients: ${mockData.contributors.length}`);

console.log('\nπŸ§ͺ Test URLs (once API is implemented):');
console.log(` GET /api/v1/payouts/rounds?orgId=${mockData.organization.id}`);
console.log(` GET /api/v1/payouts/preview?roundId=${mockData.round.id}`);
console.log(` POST /api/v1/payouts/propose { "roundId": "${mockData.round.id}", "tokenType": "STABLECOIN" }`);
console.log(` POST /api/v1/payouts/propose { "roundId": "${mockData.round.id}", "tokenType": "RECOGNITION" }`);
console.log(` GET /api/v1/payouts/status?roundId=${mockData.round.id}`);

console.log('\n⚠️ Note: This mock data will be replaced with real database data once available.');
console.log('πŸ—‘οΈ Mock data removal: After successful testing with real data');

// Export for use in tests
export default mockData;
Loading