Skip to content
Open
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
4 changes: 4 additions & 0 deletions api/[net]/[address].ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ interface IData {
points: string;
last24hPoints: string;
positions: number;
referralPoints?: string;
referrers?: number;
} | null;
leaderboard: {
rank: number;
address: string;
points: string;
last24hPoints: string;
positions: number;
referralPoints?: string;
referrers?: number;
}[];
totalItems: number;
}
Expand Down
2 changes: 1 addition & 1 deletion backend/mongo-container.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ wait_for_mongodb() {
manage_collections() {
echo "Creating/Clearing collections..."
docker exec mongodb mongosh --eval '
db = db.getSiblingDB("myDatabase");
db = db.getSiblingDB("points");
db.referrals.drop();
db.createCollection("referrals");
print("Collections have been reset successfully.");
Expand Down
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"license": "ISC",
"dependencies": {
"@coral-xyz/anchor": "^0.29.0",
"@fastify/cors": "^10.0.2",
"@fastify/redis": "^7.0.1",
"@fastify/schedule": "^5.0.2",
"@solana/web3.js": "^1.95.3",
Expand Down
22 changes: 22 additions & 0 deletions backend/requests.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
### Query Code
GET http://localhost:3000/api/leaderboard/get-code/AMw74tWw61gxbvVrYTrKrPD5ZKtF5hgRt4hovBFumEtt


### Get all codes
GET http://localhost:3000/api/leaderboard/get-referral-codes


### Insert mock codes
POST http://localhost:3000/api/leaderboard/insert-mock-codes



### Use code
POST http://localhost:3000/api/leaderboard/use-code
Content-Type: application/json

{
"address": "AMw74tWw61gxbvVrYTrKrPD5ZKtF5hgRt4hovBFumEtt",
"code": "rtowulqn",
"signature": []
}
6 changes: 5 additions & 1 deletion backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import leaderboardRoutes from "./routes/referral.routes";
import Fastify from "fastify";
import { Db, MongoClient } from "mongodb";
import fastifySchedule from "@fastify/schedule";
import cors from "@fastify/cors";
// import { fastifyRedis } from "@fastify/redis";

declare module "fastify" {
Expand All @@ -22,7 +23,10 @@ app.register(connectDb, {
});

// app.register(fastifyRedis, { url: "redis://127.0.0.1" });

app.register(cors, {
// TODO: change it to correct domain
origin: "*",
});
app.register(leaderboardRoutes, { prefix: "/api/leaderboard" });
app.register(fastifySchedule);

Expand Down
121 changes: 117 additions & 4 deletions backend/src/controllers/referral.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,27 @@ export const getReferralCodes = async (
res: FastifyReply
) => {
const collection = new Collection(Collections.Referrals);
const referrals = await collection.getAllElementsAsArray();
res.send(referrals);
const referrals = await collection.getReferrersAndReferred();
const filteredReferrals = referrals.reduce(
(
acc: Record<
string,
{
signature: string | null;
code: string;
codeUsed: string | null;
invited: string[];
}
>,
curr
) => {
const { address, _id, ...data } = curr;
acc[address] = data as Omit<IReferralCollectionItem, "address">;
return acc;
},
{}
);
res.send(filteredReferrals);
};

export const useCode = async (
Expand All @@ -51,7 +70,14 @@ export const useCode = async (
session.startTransaction();
const referrerEntry = await collection.findOne({ code });
const userEntry = await collection.findOne({ address });
if (!referrerEntry || (userEntry && !!userEntry.codeUsed)) {

if (!referrerEntry || (userEntry && userEntry.codeUsed)) {
await session.abortTransaction();
res.status(400).send({ ok: false });
return;
}

if (userEntry?.address === referrerEntry.address) {
await session.abortTransaction();
res.status(400).send({ ok: false });
return;
Expand Down Expand Up @@ -82,7 +108,7 @@ export const getCode = async (
const { address } = req.params;
const userEntry = await collection.findOne({ address });
if (userEntry) {
return res.status(200).send({ code: userEntry.codeOwned });
return res.status(200).send({ code: userEntry.code });
}
const codeOwned = getRandomCode();
const newUserEntry: IReferralCollectionItem = {
Expand All @@ -95,3 +121,90 @@ export const getCode = async (
await collection.insertOne(newUserEntry);
return res.status(200).send({ code: codeOwned });
};

export const insertMockCodes = async (
req: FastifyRequest,
res: FastifyReply
) => {
const collection = new Collection(Collections.Referrals);
const addresses = [
"BtGH2WkM1oNyPVgzYT51xV2gJqHhVQ4QiGwWirBUW5xN", //
"A3sjWSa4rNspqqZQFjMMbmmnbgvYDRRMiWjyaTbMyYG4", // uses first address code
"9BNu7C9f3cGS4fBS5jKvG2XASHGVVnBxR2XAW4q4ZYUE", // uses first address code
"63CQPyL9xzCSy4ejNuqxxqfjRpAGVKa1KbJUvXfRSKZK", // uses third address code
"6usfBWrWdKoA5BvyAqjjU4R8yKuWbP8JkofrtPk3Rupw", // uses third address code
];

const dbClient = app.mongoClient;

const firstAddress = addresses[0];
const firstAddressEntry: IReferralCollectionItem = {
address: firstAddress,
code: getRandomCode(),
codeUsed: null,
signature: null,
invited: [],
};
await collection.insertOne(firstAddressEntry);

for (const address of [addresses[1], addresses[2]]) {
const session = dbClient.startSession();
try {
session.startTransaction();

const referrerEntry = await collection.findOne({ address: firstAddress });
const userEntry = await collection.findOne({ address });

if (!referrerEntry || (userEntry && !!userEntry.codeUsed)) {
await session.abortTransaction();
continue;
}

await collection.addToInvitedList(referrerEntry.code, address);
await collection.insertOrUpdateOne(
address,
getRandomCode(),
referrerEntry.code,
"signature"
);

await session.commitTransaction();
} catch (err) {
await session.abortTransaction();
} finally {
await session.endSession();
}
}

const thirdAddress = addresses[2];
for (const address of [addresses[3], addresses[4]]) {
const session = dbClient.startSession();
try {
session.startTransaction();

const referrerEntry = await collection.findOne({ address: thirdAddress });
const userEntry = await collection.findOne({ address });

if (!referrerEntry || (userEntry && !!userEntry.codeUsed)) {
await session.abortTransaction();
continue;
}

await collection.addToInvitedList(referrerEntry.code, address);
await collection.insertOrUpdateOne(
address,
getRandomCode(),
referrerEntry.code,
"signature"
);

await session.commitTransaction();
} catch (err) {
await session.abortTransaction();
} finally {
await session.endSession();
}
}

res.status(200).send({ ok: true });
};
2 changes: 2 additions & 0 deletions backend/src/routes/referral.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
getCode,
useCode,
getReferralCodes,
insertMockCodes,
} from "../controllers/referral.controller";
import { FastifyInstance, FastifyPluginAsync } from "fastify";
import fp from "fastify-plugin";
Expand All @@ -12,6 +13,7 @@ const leaderboardRoutes: FastifyPluginAsync = async (
fastify.get("/api/leaderboard/get-code/:address", getCode);
fastify.post("/api/leaderboard/use-code", useCode);
fastify.get("/api/leaderboard/get-referral-codes", getReferralCodes);
fastify.post("/api/leaderboard/insert-mock-codes", insertMockCodes);
};

export default fp(leaderboardRoutes);
11 changes: 11 additions & 0 deletions backend/src/services/collection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ export class Collection {
);
}

async getReferrersAndReferred() {
return await this.db
.find({
$or: [
{ invited: { $exists: true, $not: { $size: 0 } } },
{ codeUsed: { $exists: true, $ne: null } },
],
})
.toArray();
}

async insertOne(element: any) {
return await this.db.insertOne(element);
}
Expand Down
17 changes: 17 additions & 0 deletions backend/src/test/use-code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ describe("Use code endpoint", () => {
expect(body.code).toBe(lastElement.code);
expect(allRecordsBefore.length).toBe(allRecordsAfter.length - 1);
expect(statusCode).toBe(200);

{
const payload = getMessagePayload(address.publicKey, code);
const signature = signMessage(address, decodeUTF8(payload));
const response = await fastify.inject({
method: "POST",
url: "/api/leaderboard/use-code",
payload: {
address: address.publicKey.toString(),
code,
signature: Buffer.from(signature).toString("base64"),
},
});

const statusCode = response.statusCode;
expect(statusCode).toBe(400);
}
});
test("Use code", async () => {
const address = Keypair.generate();
Expand Down
Loading