Skip to content
Open
1 change: 1 addition & 0 deletions constants/tables.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export const QRS_TABLE = "biztechQRs";
export const TEAMS_TABLE = "biztechTeams";
export const QR_SCANS_RECORD = "biztechQRScans";
export const PROFILES_TABLE = "biztechProfiles";
export const INVESTMENTS_TABLE = "biztechInvestments";

export const IMMUTABLE_USER_PROPS = ["admin"]; // make sure you check all calls to /user's patch in the frontend if you add to this list
28 changes: 14 additions & 14 deletions services/bots/helpersDiscord.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,20 @@ export function verifyRequestSignature(req) {
export function applicationCommandRouter(name, body) {
const { member } = body;
switch (name) {
case "verify":
return handleVerifyCommand(member);

default:
return {
statusCode: 200,
body: JSON.stringify({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: `Unknown command: /${body.name}`,
flags: 64
}
})
};
case "verify":
return handleVerifyCommand(member);

default:
return {
statusCode: 200,
body: JSON.stringify({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: `Unknown command: /${body.name}`,
flags: 64
}
})
};
}
}

Expand Down
216 changes: 216 additions & 0 deletions services/investments/handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import { USER_REGISTRATIONS_TABLE, INVESTMENTS_TABLE, TEAMS_TABLE } from "../../constants/tables";
import db from "../../lib/db";
import helpers from "../../lib/handlerHelpers";
import { v4 as uuidv4 } from "uuid";

// WIP
export const invest = async (event, ctx, callback) => {
/*
Responsible for:
- Decrementing the balance of the investor
- Incrementing the balance of the team
- Updating the DB with transaction + comments
*/

const data = JSON.parse(event.body);

helpers.checkPayloadProps(data, {
investorId: {
required: true,
type: "string"
},
teamId: {
required: true,
type: "string"
},
amount: {
required: true,
type: "number"
},
comment: {
required: true,
type: "string"
}
});

const investor = await db.getOne(data.investorId, USER_REGISTRATIONS_TABLE, {
"eventID;year": "kickstart;2025" // hardcoded
});

const team = await db.getOne(data.teamId, TEAMS_TABLE, {
"eventID;year": "kickstart;2025" // hardcoded
});

// only allow valid investors
if (!investor) {
return helpers.createResponse(400, {
message: "Investor not found or not registered for event"
});
}

// only allow valid teams
if (!team) {
return helpers.createResponse(400, {
message: "Team not found for event"
});
}

// investor cannot invest in their own team
if (investor.teamId === team.id) {
return helpers.createResponse(400, {
message: "Investor cannot invest in their own team"
});
}

// investor cannot invest more than their remaining balance
if (data.amount > investor.balance) {
return helpers.createResponse(400, {
message: "Investor does not have enough balance"
});
}

// 1. update investor balance
const updateInvestorPromise = db.updateDBCustom({
TableName: USER_REGISTRATIONS_TABLE + (process.env.ENVIRONMENT || ""),
Key: {
id: data.investorId,
"eventID;year": "kickstart;2025"
},
UpdateExpression: "SET balance = :newBalance",
ExpressionAttributeValues: {
":newBalance": investor.balance - data.amount,
},
ConditionExpression: "attribute_exists(id) and attribute_exists(eventID;year)",
ReturnValues: "UPDATED_NEW",
});

// 2. update team funding
const updateTeamPromise = db.updateDBCustom({
TableName: TEAMS_TABLE + (process.env.ENVIRONMENT || ""),
Key: {
id: data.teamId,
"eventID;year": "kickstart;2025"
},
UpdateExpression: "SET funding = :newFunding",
ExpressionAttributeValues: {
":newFunding": team.funding + data.amount,
},
ConditionExpression: "attribute_exists(id) and attribute_exists(eventID;year)",
ReturnValues: "UPDATED_NEW",
});

// 3. create investment
const createInvestmentPromise = db.create({
id: uuidv4(), // partition key
["eventID;year"]: "kickstart;2025", // sort key
investorId: data.investorId,
investorName: investor.fname,
teamId: data.teamId,
amount: data.amount,
comment: data.comment,
}, INVESTMENTS_TABLE);

await Promise.all([updateInvestorPromise, updateTeamPromise, createInvestmentPromise]);

return helpers.createResponse(200, {
message: "Investment successful"
});
};

// WIP
export const userStatus = async (event, ctx, callback) => {
/*
Responsible for:
- Fetching user current balance
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's confirm with gautham before we go ahead with this

- Fetching user's stake in other teams
*/

const data = JSON.parse(event.body);

helpers.checkPayloadProps(data, {
userId: {
required: true,
type: "string"
}
});

const user = await db.getOne(data.userId, USER_REGISTRATIONS_TABLE, {
"eventID;year": "kickstart;2025"
});

if (!user) {
return helpers.createResponse(400, {
message: "User not found or not registered for event"
});
}

const userInvestments = await db.scan(INVESTMENTS_TABLE, {
FilterExpression: "#investorId = :investorId",
ExpressionAttributeNames: {
"#investorId": "investorId"
},
ExpressionAttributeValues: {
":investorId": data.userId
}
});

// Aggregate user's stakes per team
const stakes = userInvestments.reduce((accumulatedStakes, investment) => {
if (!accumulatedStakes[investment.teamId]) {
accumulatedStakes[investment.teamId] = 0;
}

// Add the investment amount to the team's stake
accumulatedStakes[investment.teamId] += investment.amount;
return accumulatedStakes;
}, {});

return helpers.createResponse(200, {
balance: user.balance,
stakes
});
};

// WIP
export const teamStatus = async (event, ctx, callback) => {
/*
Responsible for:
- Fetching team's current funding
- Fetching all individual investments with comments
*/

const data = JSON.parse(event.body);

helpers.checkPayloadProps(data, {
teamId: {
required: true,
type: "string"
}
});

const team = await db.getOne(data.teamId, TEAMS_TABLE, {
"eventID;year": "kickstart;2025"
});

if (!team) {
return helpers.createResponse(400, {
message: "Team not found for event"
});
}

// Scan all investments made into this team
const teamInvestments = await db.scan(INVESTMENTS_TABLE, {
FilterExpression: "#teamId = :teamId",
ExpressionAttributeNames: {
"#teamId": "teamId"
},
ExpressionAttributeValues: {
":teamId": data.teamId
}
});

return helpers.createResponse(200, {
funding: team.funding,
investments: teamInvestments // each entry includes comment, investorId, investorName, amount
});
};
Loading
Loading