From 8bc5776b9a0902eda0973bf3d4d1587c2f704268 Mon Sep 17 00:00:00 2001 From: Ikem Peter Date: Sun, 24 Nov 2024 13:04:30 +0100 Subject: [PATCH 1/6] implemented basic handlers and indexer --- backend/bun.lockb | Bin 0 -> 116225 bytes backend/config/events.ts | 7 ++++ backend/indexer/handlers.ts | 28 +++++++++++++++ backend/indexer/index.ts | 69 ++++++++++++++++++++++++++++++++++++ backend/indexer/types.ts | 28 +++++++++++++++ backend/package.json | 2 ++ backend/server.js | 2 ++ 7 files changed, 136 insertions(+) create mode 100755 backend/bun.lockb create mode 100644 backend/config/events.ts create mode 100644 backend/indexer/handlers.ts create mode 100644 backend/indexer/index.ts create mode 100644 backend/indexer/types.ts diff --git a/backend/bun.lockb b/backend/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..cc5078d2c06b8a557c42c6b6159fee18e24f9409 GIT binary patch literal 116225 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 by=I>BIaVFK5ioSw;}1x>USIux_~-usyzSRa literal 0 HcmV?d00001 diff --git a/backend/config/events.ts b/backend/config/events.ts new file mode 100644 index 0000000..a3bf130 --- /dev/null +++ b/backend/config/events.ts @@ -0,0 +1,7 @@ +export const events = { + NewEventAdded: "NewEventAdded", //replace with event identify + RegisteredForEvent: "RegisteredForEvent", + EventAttendanceMark: "EventAttendanceMark", + EndEventRegistration: "EndEventRegistration", + RSVPForEvent: "RSVPForEvent" +}; \ No newline at end of file diff --git a/backend/indexer/handlers.ts b/backend/indexer/handlers.ts new file mode 100644 index 0000000..4c8bdc1 --- /dev/null +++ b/backend/indexer/handlers.ts @@ -0,0 +1,28 @@ +import { + NewEventAdded, + RegisteredForEvent, + EventAttendanceMark, + EndEventRegistration, + RSVPForEvent +} from "./types"; +import { v1alpha2 as starknet } from '@apibara/starknet'; + +export async function handleNewEventAdded(event: starknet.IEvent) { + console.log(event); +} + +export async function handleRegisteredForEvent(event: starknet.IEvent) { + console.log(event); +} + +export async function handleEventAttendanceMark(event: starknet.IEvent) { + console.log(event); +} + +export async function handleEndEventRegistration(event: starknet.IEvent) { + console.log(event); +} + +export async function handleRSVPForEvent(event: starknet.IEvent) { + console.log(event); +} \ No newline at end of file diff --git a/backend/indexer/index.ts b/backend/indexer/index.ts new file mode 100644 index 0000000..29f5aa9 --- /dev/null +++ b/backend/indexer/index.ts @@ -0,0 +1,69 @@ +import { StreamClient, v1alpha2 } from '@apibara/protocol'; +import { + FieldElement, + Filter, + v1alpha2 as starknet, + StarkNetCursor, +} from '@apibara/starknet'; +import { events } from '../config/events'; +import { + handleNewEventAdded, + handleRegisteredForEvent, + handleEventAttendanceMark, + handleEndEventRegistration, + handleRSVPForEvent, +} from './handlers'; + +const client = new StreamClient({ + url: process.env.DNA_CLIENT_URL!, + clientOptions: { + 'grpc.max_receive_message_length': 100 * 1024 * 1024, // 100MB + }, + token: process.env.DNA_TOKEN, +}); + +// Create filter combining all event handlers +const filter = Filter.create().withHeader({ weak: true }); + +// Map your events to handlers +const eventHandlers: Record Promise> = { + [events.NewEventAdded]: handleNewEventAdded, + [events.RegisteredForEvent]: handleRegisteredForEvent, + [events.EventAttendanceMark]: handleEventAttendanceMark, + [events.EndEventRegistration]: handleEndEventRegistration, + [events.RSVPForEvent]: handleRSVPForEvent, +}; + +// Add all events to filter +Object.keys(eventHandlers).forEach((eventKey) => { + filter.addEvent((event) => + event.withKeys([FieldElement.fromBigInt(BigInt(eventKey))]) + ); +}); + +// Start indexer function +export async function startIndexer() { + client.configure({ + filter: filter.encode(), + batchSize: 1, + finality: v1alpha2.DataFinality.DATA_STATUS_FINALIZED, + cursor: StarkNetCursor.createWithBlockNumber(0), + }); + + for await (const message of client) { + if (message.message === 'data') { + const { data } = message.data!; + for (const item of data) { + const block = starknet.Block.decode(item); + for (const event of block.events) { + if (!event.event) continue; + const eventKey = FieldElement.toHex(event.event.keys![0]); + const handler = eventHandlers[eventKey]; + if (handler) { + await handler(event.event); + } + } + } + } + } +} diff --git a/backend/indexer/types.ts b/backend/indexer/types.ts new file mode 100644 index 0000000..b1f252d --- /dev/null +++ b/backend/indexer/types.ts @@ -0,0 +1,28 @@ +export type NewEventAdded = { + name: string; + event_id: number; + location: string; + event_owner: string; +}; + +export type RegisteredForEvent = { + event_id: number; + event_name: string; + user_address: string; +}; + +export type EndEventRegistration = { + event_id: number; + event_name: string; + event_owner: string; +}; + +export type RSVPForEvent = { + event_id: number; + attendee_address: string; +}; + +export type EventAttendanceMark = { + event_id: number; + user_address: string; +}; diff --git a/backend/package.json b/backend/package.json index 594ac9d..df048a4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,6 +11,8 @@ "migrate": "knex migrate:latest" }, "dependencies": { + "@apibara/protocol": "^0.4.9", + "@apibara/starknet": "^0.5.0", "axios": "^1.7.7", "bcryptjs": "^2.4.3", "body-parser": "^1.20.3", diff --git a/backend/server.js b/backend/server.js index de07216..36b6927 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,7 +1,9 @@ import app from "./app.js"; import dotenv from "dotenv"; + dotenv.config(); const port = process.env.PORT || 3000; + app.listen(port, () => { console.log(`Server is running on port ${port}`); }); From 90acbdc7e0d730c9d24e84001251d41947571811 Mon Sep 17 00:00:00 2001 From: Ikem Peter Date: Sun, 24 Nov 2024 13:22:56 +0100 Subject: [PATCH 2/6] added migrations and updated models --- backend/indexer/handlers.ts | 1 + ...124121820_create_event_attendance_table.js | 23 +++++ ...121820_create_event_registrations_table.js | 24 +++++ ...20241124121820_create_event_rsvps_table.js | 23 +++++ backend/models/Event.js | 93 +++++++++++++++++++ backend/server.js | 4 +- 6 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 backend/migrations/20241124121820_create_event_attendance_table.js create mode 100644 backend/migrations/20241124121820_create_event_registrations_table.js create mode 100644 backend/migrations/20241124121820_create_event_rsvps_table.js diff --git a/backend/indexer/handlers.ts b/backend/indexer/handlers.ts index 4c8bdc1..1fd6d67 100644 --- a/backend/indexer/handlers.ts +++ b/backend/indexer/handlers.ts @@ -6,6 +6,7 @@ import { RSVPForEvent } from "./types"; import { v1alpha2 as starknet } from '@apibara/starknet'; +import Event from "../models/Event.js"; export async function handleNewEventAdded(event: starknet.IEvent) { console.log(event); diff --git a/backend/migrations/20241124121820_create_event_attendance_table.js b/backend/migrations/20241124121820_create_event_attendance_table.js new file mode 100644 index 0000000..1e11d8e --- /dev/null +++ b/backend/migrations/20241124121820_create_event_attendance_table.js @@ -0,0 +1,23 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema.createTable("event_attendance", function (table) { + table.increments("id").primary(); + table.string("event_id").notNullable(); + table.string("user_address").notNullable(); + table.timestamps(true, true); + + // Composite unique constraint + table.unique(['event_id', 'user_address']); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema.dropTableIfExists("event_attendance"); +}; diff --git a/backend/migrations/20241124121820_create_event_registrations_table.js b/backend/migrations/20241124121820_create_event_registrations_table.js new file mode 100644 index 0000000..33ff8b1 --- /dev/null +++ b/backend/migrations/20241124121820_create_event_registrations_table.js @@ -0,0 +1,24 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema.createTable("event_registrations", function (table) { + table.increments("id").primary(); + table.string("event_id").notNullable(); + table.string("user_address").notNullable(); + table.boolean("is_active").defaultTo(true); + table.timestamps(true, true); + + // Composite unique constraint + table.unique(['event_id', 'user_address']); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema.dropTableIfExists("event_registrations"); +}; diff --git a/backend/migrations/20241124121820_create_event_rsvps_table.js b/backend/migrations/20241124121820_create_event_rsvps_table.js new file mode 100644 index 0000000..1228388 --- /dev/null +++ b/backend/migrations/20241124121820_create_event_rsvps_table.js @@ -0,0 +1,23 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema.createTable("event_rsvps", function (table) { + table.increments("id").primary(); + table.string("event_id").notNullable(); + table.string("attendee_address").notNullable(); + table.timestamps(true, true); + + // Composite unique constraint + table.unique(['event_id', 'attendee_address']); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema.dropTableIfExists("event_rsvps"); +}; diff --git a/backend/models/Event.js b/backend/models/Event.js index efe42e1..9ab9692 100644 --- a/backend/models/Event.js +++ b/backend/models/Event.js @@ -62,6 +62,99 @@ class Event { const event = await db("events").where({ id }).del(); return event; } + + static async registerUser(event_id, user_address) { + const [registration] = await db("event_registrations").insert({ + event_id, + user_address, + }); + return registration; + } + + static async getRegisteredUsers(event_id) { + return await db("event_registrations") + .where({ event_id, is_active: true }) + .select("user_address"); + } + + static async isUserRegistered(event_id, user_address) { + const registration = await db("event_registrations") + .where({ event_id, user_address, is_active: true }) + .first(); + return !!registration; + } + + static async addRSVP(event_id, attendee_address) { + const [rsvp] = await db("event_rsvps").insert({ + event_id, + attendee_address, + }); + return rsvp; + } + + static async getRSVPs(event_id) { + return await db("event_rsvps") + .where({ event_id }) + .select("attendee_address"); + } + + static async hasUserRSVPed(event_id, attendee_address) { + const rsvp = await db("event_rsvps") + .where({ event_id, attendee_address }) + .first(); + return !!rsvp; + } + + static async markAttendance(event_id, user_address) { + const [attendance] = await db("event_attendance").insert({ + event_id, + user_address, + }); + return attendance; + } + + static async getAttendance(event_id) { + return await db("event_attendance") + .where({ event_id }) + .select("user_address"); + } + + static async hasUserAttended(event_id, user_address) { + const attendance = await db("event_attendance") + .where({ event_id, user_address }) + .first(); + return !!attendance; + } + + static async endRegistration(event_id) { + await db("event_registrations") + .where({ event_id }) + .update({ is_active: false }); + return true; + } + + static async getCounts(event_id) { + const registrations = await db("event_registrations") + .where({ event_id, is_active: true }) + .count("id as count") + .first(); + + const rsvps = await db("event_rsvps") + .where({ event_id }) + .count("id as count") + .first(); + + const attendance = await db("event_attendance") + .where({ event_id }) + .count("id as count") + .first(); + + return { + registrations: registrations.count, + rsvps: rsvps.count, + attendance: attendance.count + }; + } } export default Event; diff --git a/backend/server.js b/backend/server.js index 36b6927..2e972ab 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,9 +1,11 @@ import app from "./app.js"; import dotenv from "dotenv"; +import { startIndexer } from "./indexer/index.js"; dotenv.config(); const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`Server is running on port ${port}`); -}); + startIndexer(); +}); \ No newline at end of file From 4738805f5c38e4ee9266cf9788344cf3e1c142eb Mon Sep 17 00:00:00 2001 From: Ikem Peter Date: Sun, 24 Nov 2024 14:08:15 +0100 Subject: [PATCH 3/6] handled emmited events --- backend/bun.lockb | Bin 116225 -> 129941 bytes backend/indexer/handlers.ts | 114 ++++++++++++++++++++++++++++++++++-- backend/package.json | 1 + backend/utils/tohexAscii.js | 10 ++++ 4 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 backend/utils/tohexAscii.js diff --git a/backend/bun.lockb b/backend/bun.lockb index cc5078d2c06b8a557c42c6b6159fee18e24f9409..87c20ad1c29f6a446593a0617b906ee66d5c7ca6 100755 GIT binary patch delta 28603 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

H5ax1-LP&Yrao3J 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 diff --git a/backend/indexer/handlers.ts b/backend/indexer/handlers.ts index 1fd6d67..c3d0e19 100644 --- a/backend/indexer/handlers.ts +++ b/backend/indexer/handlers.ts @@ -5,25 +5,127 @@ import { EndEventRegistration, RSVPForEvent } from "./types"; -import { v1alpha2 as starknet } from '@apibara/starknet'; +import { FieldElement, v1alpha2 as starknet } from '@apibara/starknet'; import Event from "../models/Event.js"; +import { uint256 } from 'starknet'; +import { hexToAscii } from "../utils/tohexAscii.js"; export async function handleNewEventAdded(event: starknet.IEvent) { - console.log(event); + const data = event.data!; + + const eventDetails: NewEventAdded = { + name: hexToAscii(FieldElement.toHex(data[0]).toString()), + event_id: parseInt(uint256 + .uint256ToBN({ + low: FieldElement.toBigInt(data[1]), + high: FieldElement.toBigInt(data[2]), + }) + .toString()), + location: hexToAscii(FieldElement.toHex(data[3]).toString()), + event_owner: FieldElement.toHex(data[4]).toString() + }; + + //Debugging purposes + console.log(eventDetails); + + const eventExists = await Event.findByEventId(eventDetails.event_id); + if (eventExists) { + console.log("Event already exists"); + return; + } + await Event.create(eventDetails); } export async function handleRegisteredForEvent(event: starknet.IEvent) { - console.log(event); + const data = event.data!; + + const registeredForEvent: RegisteredForEvent = { + event_id: parseInt(uint256 + .uint256ToBN({ + low: FieldElement.toBigInt(data[0]), + high: FieldElement.toBigInt(data[1]), + }) + .toString()), + event_name: hexToAscii(FieldElement.toHex(data[2]).toString()), + user_address: FieldElement.toHex(data[3]).toString() + }; + + console.log(registeredForEvent); + + const hasRegistered = await Event.isUserRegistered(registeredForEvent.event_id, registeredForEvent.user_address); + if (hasRegistered) { + console.log("User has already registered"); + return; + } + await Event.registerUser(registeredForEvent.event_id, registeredForEvent.user_address); } export async function handleEventAttendanceMark(event: starknet.IEvent) { - console.log(event); + const data = event.data!; + + const eventAttendanceMark: EventAttendanceMark = { + event_id: parseInt(uint256 + .uint256ToBN({ + low: FieldElement.toBigInt(data[0]), + high: FieldElement.toBigInt(data[1]), + }) + .toString()), + user_address: FieldElement.toHex(data[2]).toString() + }; + + console.log(eventAttendanceMark); + + const hasMarkedAttendance = await Event.hasUserAttended(eventAttendanceMark.event_id, eventAttendanceMark.user_address); + if (hasMarkedAttendance) { + console.log("User has already marked attendance"); + return; + } + await Event.markAttendance(eventAttendanceMark.event_id, eventAttendanceMark.user_address); } export async function handleEndEventRegistration(event: starknet.IEvent) { - console.log(event); + const data = event.data!; + + const endEventRegistration: EndEventRegistration = { + event_id: parseInt(uint256 + .uint256ToBN({ + low: FieldElement.toBigInt(data[0]), + high: FieldElement.toBigInt(data[1]), + }) + .toString()), + event_name: hexToAscii(FieldElement.toHex(data[2]).toString()), + event_owner: FieldElement.toHex(data[3]).toString() + }; + + console.log(endEventRegistration); + + const eventExists = await Event.findByEventId(endEventRegistration.event_id); + if (!eventExists) { + console.log("Event does not exist"); + return; + } + await Event.endRegistration(endEventRegistration.event_id); } export async function handleRSVPForEvent(event: starknet.IEvent) { - console.log(event); + const data = event.data!; + + const rsvpForEvent: RSVPForEvent = { + event_id: parseInt(uint256 + .uint256ToBN({ + low: FieldElement.toBigInt(data[0]), + high: FieldElement.toBigInt(data[1]), + }) + .toString()), + attendee_address: FieldElement.toHex(data[2]).toString() + }; + + console.log(rsvpForEvent); + + const hasRSVPed = await Event.hasUserRSVPed(rsvpForEvent.event_id, rsvpForEvent.attendee_address); + if (hasRSVPed) { + console.log("User has already RSVPed"); + return; + } + await Event.addRSVP(rsvpForEvent.event_id, rsvpForEvent.attendee_address); } \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index df048a4..d56636b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -29,6 +29,7 @@ "nodemon": "^3.1.4", "pg": "^8.13.0", "sqlite3": "^5.1.7", + "starknet": "^6.11.0", "validator": "^13.12.0" } } diff --git a/backend/utils/tohexAscii.js b/backend/utils/tohexAscii.js new file mode 100644 index 0000000..0031f8c --- /dev/null +++ b/backend/utils/tohexAscii.js @@ -0,0 +1,10 @@ +export function hexToAscii(hex) { + hex = hex.startsWith('0x') ? hex.slice(2) : hex; + + let str = ''; + for (let i = 0; i < hex.length; i += 2) { + const code = parseInt(hex.substr(i, 2), 16); + if (code) str += String.fromCharCode(code); + } + return str; +} \ No newline at end of file From 49052ccbeab14f5c87bb1318a0ebfbfad155f14c Mon Sep 17 00:00:00 2001 From: Ikem Peter Date: Sun, 1 Dec 2024 15:08:24 +0100 Subject: [PATCH 4/6] transcribe ts files to js --- backend/.env.example | 2 + backend/config/{events.ts => events.js} | 0 backend/indexer/{handlers.ts => handlers.js} | 37 ++++++++----------- backend/indexer/{index.ts => index.js} | 12 +++--- backend/indexer/types.ts | 28 -------------- ...124121820_create_event_attendance_table.js | 4 +- ...121820_create_event_registrations_table.js | 4 +- ...20241124121820_create_event_rsvps_table.js | 4 +- 8 files changed, 29 insertions(+), 62 deletions(-) rename backend/config/{events.ts => events.js} (100%) rename backend/indexer/{handlers.ts => handlers.js} (78%) rename backend/indexer/{index.ts => index.js} (85%) delete mode 100644 backend/indexer/types.ts diff --git a/backend/.env.example b/backend/.env.example index fb6416f..0aba94b 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -9,3 +9,5 @@ DB_PASS= DB_URL= DB_SSL= JWT_SECRET="" +DNA_TOKEN="" +DNA_CLIENT_URL="" \ No newline at end of file diff --git a/backend/config/events.ts b/backend/config/events.js similarity index 100% rename from backend/config/events.ts rename to backend/config/events.js diff --git a/backend/indexer/handlers.ts b/backend/indexer/handlers.js similarity index 78% rename from backend/indexer/handlers.ts rename to backend/indexer/handlers.js index c3d0e19..c4b3edc 100644 --- a/backend/indexer/handlers.ts +++ b/backend/indexer/handlers.js @@ -1,19 +1,12 @@ -import { - NewEventAdded, - RegisteredForEvent, - EventAttendanceMark, - EndEventRegistration, - RSVPForEvent -} from "./types"; import { FieldElement, v1alpha2 as starknet } from '@apibara/starknet'; import Event from "../models/Event.js"; import { uint256 } from 'starknet'; import { hexToAscii } from "../utils/tohexAscii.js"; -export async function handleNewEventAdded(event: starknet.IEvent) { - const data = event.data!; +export async function handleNewEventAdded(event) { + const data = event.data; - const eventDetails: NewEventAdded = { + const eventDetails = { name: hexToAscii(FieldElement.toHex(data[0]).toString()), event_id: parseInt(uint256 .uint256ToBN({ @@ -36,10 +29,10 @@ export async function handleNewEventAdded(event: starknet.IEvent) { await Event.create(eventDetails); } -export async function handleRegisteredForEvent(event: starknet.IEvent) { - const data = event.data!; +export async function handleRegisteredForEvent(event) { + const data = event.data; - const registeredForEvent: RegisteredForEvent = { + const registeredForEvent = { event_id: parseInt(uint256 .uint256ToBN({ low: FieldElement.toBigInt(data[0]), @@ -60,10 +53,10 @@ export async function handleRegisteredForEvent(event: starknet.IEvent) { await Event.registerUser(registeredForEvent.event_id, registeredForEvent.user_address); } -export async function handleEventAttendanceMark(event: starknet.IEvent) { - const data = event.data!; +export async function handleEventAttendanceMark(event) { + const data = event.data; - const eventAttendanceMark: EventAttendanceMark = { + const eventAttendanceMark = { event_id: parseInt(uint256 .uint256ToBN({ low: FieldElement.toBigInt(data[0]), @@ -83,10 +76,10 @@ export async function handleEventAttendanceMark(event: starknet.IEvent) { await Event.markAttendance(eventAttendanceMark.event_id, eventAttendanceMark.user_address); } -export async function handleEndEventRegistration(event: starknet.IEvent) { - const data = event.data!; +export async function handleEndEventRegistration(event) { + const data = event.data; - const endEventRegistration: EndEventRegistration = { + const endEventRegistration = { event_id: parseInt(uint256 .uint256ToBN({ low: FieldElement.toBigInt(data[0]), @@ -107,10 +100,10 @@ export async function handleEndEventRegistration(event: starknet.IEvent) { await Event.endRegistration(endEventRegistration.event_id); } -export async function handleRSVPForEvent(event: starknet.IEvent) { - const data = event.data!; +export async function handleRSVPForEvent(event) { + const data = event.data; - const rsvpForEvent: RSVPForEvent = { + const rsvpForEvent = { event_id: parseInt(uint256 .uint256ToBN({ low: FieldElement.toBigInt(data[0]), diff --git a/backend/indexer/index.ts b/backend/indexer/index.js similarity index 85% rename from backend/indexer/index.ts rename to backend/indexer/index.js index 29f5aa9..5312a40 100644 --- a/backend/indexer/index.ts +++ b/backend/indexer/index.js @@ -5,17 +5,17 @@ import { v1alpha2 as starknet, StarkNetCursor, } from '@apibara/starknet'; -import { events } from '../config/events'; +import { events } from '../config/events.js'; import { handleNewEventAdded, handleRegisteredForEvent, handleEventAttendanceMark, handleEndEventRegistration, handleRSVPForEvent, -} from './handlers'; +} from './handlers.js'; const client = new StreamClient({ - url: process.env.DNA_CLIENT_URL!, + url: process.env.DNA_CLIENT_URL, clientOptions: { 'grpc.max_receive_message_length': 100 * 1024 * 1024, // 100MB }, @@ -26,7 +26,7 @@ const client = new StreamClient({ const filter = Filter.create().withHeader({ weak: true }); // Map your events to handlers -const eventHandlers: Record Promise> = { +const eventHandlers = { [events.NewEventAdded]: handleNewEventAdded, [events.RegisteredForEvent]: handleRegisteredForEvent, [events.EventAttendanceMark]: handleEventAttendanceMark, @@ -52,12 +52,12 @@ export async function startIndexer() { for await (const message of client) { if (message.message === 'data') { - const { data } = message.data!; + const { data } = message.data; for (const item of data) { const block = starknet.Block.decode(item); for (const event of block.events) { if (!event.event) continue; - const eventKey = FieldElement.toHex(event.event.keys![0]); + const eventKey = FieldElement.toHex(event.event.keys[0]); const handler = eventHandlers[eventKey]; if (handler) { await handler(event.event); diff --git a/backend/indexer/types.ts b/backend/indexer/types.ts deleted file mode 100644 index b1f252d..0000000 --- a/backend/indexer/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -export type NewEventAdded = { - name: string; - event_id: number; - location: string; - event_owner: string; -}; - -export type RegisteredForEvent = { - event_id: number; - event_name: string; - user_address: string; -}; - -export type EndEventRegistration = { - event_id: number; - event_name: string; - event_owner: string; -}; - -export type RSVPForEvent = { - event_id: number; - attendee_address: string; -}; - -export type EventAttendanceMark = { - event_id: number; - user_address: string; -}; diff --git a/backend/migrations/20241124121820_create_event_attendance_table.js b/backend/migrations/20241124121820_create_event_attendance_table.js index 1e11d8e..f228db4 100644 --- a/backend/migrations/20241124121820_create_event_attendance_table.js +++ b/backend/migrations/20241124121820_create_event_attendance_table.js @@ -2,7 +2,7 @@ * @param { import("knex").Knex } knex * @returns { Promise } */ -exports.up = function(knex) { +export const up = function(knex) { return knex.schema.createTable("event_attendance", function (table) { table.increments("id").primary(); table.string("event_id").notNullable(); @@ -18,6 +18,6 @@ exports.up = function(knex) { * @param { import("knex").Knex } knex * @returns { Promise } */ -exports.down = function(knex) { +export const down = function(knex) { return knex.schema.dropTableIfExists("event_attendance"); }; diff --git a/backend/migrations/20241124121820_create_event_registrations_table.js b/backend/migrations/20241124121820_create_event_registrations_table.js index 33ff8b1..b2d927b 100644 --- a/backend/migrations/20241124121820_create_event_registrations_table.js +++ b/backend/migrations/20241124121820_create_event_registrations_table.js @@ -2,7 +2,7 @@ * @param { import("knex").Knex } knex * @returns { Promise } */ -exports.up = function(knex) { +export const up = function(knex) { return knex.schema.createTable("event_registrations", function (table) { table.increments("id").primary(); table.string("event_id").notNullable(); @@ -19,6 +19,6 @@ exports.up = function(knex) { * @param { import("knex").Knex } knex * @returns { Promise } */ -exports.down = function(knex) { +export const down = function(knex) { return knex.schema.dropTableIfExists("event_registrations"); }; diff --git a/backend/migrations/20241124121820_create_event_rsvps_table.js b/backend/migrations/20241124121820_create_event_rsvps_table.js index 1228388..33fd435 100644 --- a/backend/migrations/20241124121820_create_event_rsvps_table.js +++ b/backend/migrations/20241124121820_create_event_rsvps_table.js @@ -2,7 +2,7 @@ * @param { import("knex").Knex } knex * @returns { Promise } */ -exports.up = function(knex) { +export const up = function(knex) { return knex.schema.createTable("event_rsvps", function (table) { table.increments("id").primary(); table.string("event_id").notNullable(); @@ -18,6 +18,6 @@ exports.up = function(knex) { * @param { import("knex").Knex } knex * @returns { Promise } */ -exports.down = function(knex) { +export const down = function(knex) { return knex.schema.dropTableIfExists("event_rsvps"); }; From 3cabc00ff9b044b34c6b4db01d210d707547c8fd Mon Sep 17 00:00:00 2001 From: Ikem Peter Date: Sun, 1 Dec 2024 15:14:51 +0100 Subject: [PATCH 5/6] updated readme and env.example --- backend/.env.example | 4 +++- backend/README.md | 7 +++++++ backend/config/events.js | 10 +++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 0aba94b..6ddaea8 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -9,5 +9,7 @@ DB_PASS= DB_URL= DB_SSL= JWT_SECRET="" + +# gotten from https://www.apibara.com/ DNA_TOKEN="" -DNA_CLIENT_URL="" \ No newline at end of file +DNA_CLIENT_URL="dns:///sepolia.starknet.a5a.ch" \ No newline at end of file diff --git a/backend/README.md b/backend/README.md index 6517275..e392b1f 100644 --- a/backend/README.md +++ b/backend/README.md @@ -10,4 +10,11 @@ > Copy `.env.example` to `.env` file and update the postgresql credentials for production environment also set NODE_ENV to production if you want to run production, you can either use DB URL or set DB host,username,password and port with SSL for your DB connection. ### STEP 4 +> Get your Apibara DNA Token: +> 1. Create an account at [Apibara](https://www.apibara.com/) +> 2. Click on "New Indexer" to create an indexer +> 3. Copy the DNA token provided +> 4. Paste the token in your `.env` file as `DNA_TOKEN` + +### STEP 5 > Start project with `npm start` on production environment and `npm run dev` on development environment. diff --git a/backend/config/events.js b/backend/config/events.js index a3bf130..2f38561 100644 --- a/backend/config/events.js +++ b/backend/config/events.js @@ -1,7 +1,7 @@ export const events = { - NewEventAdded: "NewEventAdded", //replace with event identify - RegisteredForEvent: "RegisteredForEvent", - EventAttendanceMark: "EventAttendanceMark", - EndEventRegistration: "EndEventRegistration", - RSVPForEvent: "RSVPForEvent" + NewEventAdded: "0x1", //replace with event identify + RegisteredForEvent: "0x2", + EventAttendanceMark: "0x3", + EndEventRegistration: "0x4", + RSVPForEvent: "0x5" }; \ No newline at end of file From 5170aed9ecee840f02e90d536921d79582a6425a Mon Sep 17 00:00:00 2001 From: Ikem Peter Date: Sun, 1 Dec 2024 15:20:20 +0100 Subject: [PATCH 6/6] rm --- backend/config/events.ts | 7 -- backend/indexer/handlers.ts | 131 ------------------------------------ backend/indexer/index.ts | 69 ------------------- backend/indexer/types.ts | 28 -------- 4 files changed, 235 deletions(-) delete mode 100644 backend/config/events.ts delete mode 100644 backend/indexer/handlers.ts delete mode 100644 backend/indexer/index.ts delete mode 100644 backend/indexer/types.ts diff --git a/backend/config/events.ts b/backend/config/events.ts deleted file mode 100644 index a3bf130..0000000 --- a/backend/config/events.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const events = { - NewEventAdded: "NewEventAdded", //replace with event identify - RegisteredForEvent: "RegisteredForEvent", - EventAttendanceMark: "EventAttendanceMark", - EndEventRegistration: "EndEventRegistration", - RSVPForEvent: "RSVPForEvent" -}; \ No newline at end of file diff --git a/backend/indexer/handlers.ts b/backend/indexer/handlers.ts deleted file mode 100644 index c3d0e19..0000000 --- a/backend/indexer/handlers.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { - NewEventAdded, - RegisteredForEvent, - EventAttendanceMark, - EndEventRegistration, - RSVPForEvent -} from "./types"; -import { FieldElement, v1alpha2 as starknet } from '@apibara/starknet'; -import Event from "../models/Event.js"; -import { uint256 } from 'starknet'; -import { hexToAscii } from "../utils/tohexAscii.js"; - -export async function handleNewEventAdded(event: starknet.IEvent) { - const data = event.data!; - - const eventDetails: NewEventAdded = { - name: hexToAscii(FieldElement.toHex(data[0]).toString()), - event_id: parseInt(uint256 - .uint256ToBN({ - low: FieldElement.toBigInt(data[1]), - high: FieldElement.toBigInt(data[2]), - }) - .toString()), - location: hexToAscii(FieldElement.toHex(data[3]).toString()), - event_owner: FieldElement.toHex(data[4]).toString() - }; - - //Debugging purposes - console.log(eventDetails); - - const eventExists = await Event.findByEventId(eventDetails.event_id); - if (eventExists) { - console.log("Event already exists"); - return; - } - await Event.create(eventDetails); -} - -export async function handleRegisteredForEvent(event: starknet.IEvent) { - const data = event.data!; - - const registeredForEvent: RegisteredForEvent = { - event_id: parseInt(uint256 - .uint256ToBN({ - low: FieldElement.toBigInt(data[0]), - high: FieldElement.toBigInt(data[1]), - }) - .toString()), - event_name: hexToAscii(FieldElement.toHex(data[2]).toString()), - user_address: FieldElement.toHex(data[3]).toString() - }; - - console.log(registeredForEvent); - - const hasRegistered = await Event.isUserRegistered(registeredForEvent.event_id, registeredForEvent.user_address); - if (hasRegistered) { - console.log("User has already registered"); - return; - } - await Event.registerUser(registeredForEvent.event_id, registeredForEvent.user_address); -} - -export async function handleEventAttendanceMark(event: starknet.IEvent) { - const data = event.data!; - - const eventAttendanceMark: EventAttendanceMark = { - event_id: parseInt(uint256 - .uint256ToBN({ - low: FieldElement.toBigInt(data[0]), - high: FieldElement.toBigInt(data[1]), - }) - .toString()), - user_address: FieldElement.toHex(data[2]).toString() - }; - - console.log(eventAttendanceMark); - - const hasMarkedAttendance = await Event.hasUserAttended(eventAttendanceMark.event_id, eventAttendanceMark.user_address); - if (hasMarkedAttendance) { - console.log("User has already marked attendance"); - return; - } - await Event.markAttendance(eventAttendanceMark.event_id, eventAttendanceMark.user_address); -} - -export async function handleEndEventRegistration(event: starknet.IEvent) { - const data = event.data!; - - const endEventRegistration: EndEventRegistration = { - event_id: parseInt(uint256 - .uint256ToBN({ - low: FieldElement.toBigInt(data[0]), - high: FieldElement.toBigInt(data[1]), - }) - .toString()), - event_name: hexToAscii(FieldElement.toHex(data[2]).toString()), - event_owner: FieldElement.toHex(data[3]).toString() - }; - - console.log(endEventRegistration); - - const eventExists = await Event.findByEventId(endEventRegistration.event_id); - if (!eventExists) { - console.log("Event does not exist"); - return; - } - await Event.endRegistration(endEventRegistration.event_id); -} - -export async function handleRSVPForEvent(event: starknet.IEvent) { - const data = event.data!; - - const rsvpForEvent: RSVPForEvent = { - event_id: parseInt(uint256 - .uint256ToBN({ - low: FieldElement.toBigInt(data[0]), - high: FieldElement.toBigInt(data[1]), - }) - .toString()), - attendee_address: FieldElement.toHex(data[2]).toString() - }; - - console.log(rsvpForEvent); - - const hasRSVPed = await Event.hasUserRSVPed(rsvpForEvent.event_id, rsvpForEvent.attendee_address); - if (hasRSVPed) { - console.log("User has already RSVPed"); - return; - } - await Event.addRSVP(rsvpForEvent.event_id, rsvpForEvent.attendee_address); -} \ No newline at end of file diff --git a/backend/indexer/index.ts b/backend/indexer/index.ts deleted file mode 100644 index 29f5aa9..0000000 --- a/backend/indexer/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { StreamClient, v1alpha2 } from '@apibara/protocol'; -import { - FieldElement, - Filter, - v1alpha2 as starknet, - StarkNetCursor, -} from '@apibara/starknet'; -import { events } from '../config/events'; -import { - handleNewEventAdded, - handleRegisteredForEvent, - handleEventAttendanceMark, - handleEndEventRegistration, - handleRSVPForEvent, -} from './handlers'; - -const client = new StreamClient({ - url: process.env.DNA_CLIENT_URL!, - clientOptions: { - 'grpc.max_receive_message_length': 100 * 1024 * 1024, // 100MB - }, - token: process.env.DNA_TOKEN, -}); - -// Create filter combining all event handlers -const filter = Filter.create().withHeader({ weak: true }); - -// Map your events to handlers -const eventHandlers: Record Promise> = { - [events.NewEventAdded]: handleNewEventAdded, - [events.RegisteredForEvent]: handleRegisteredForEvent, - [events.EventAttendanceMark]: handleEventAttendanceMark, - [events.EndEventRegistration]: handleEndEventRegistration, - [events.RSVPForEvent]: handleRSVPForEvent, -}; - -// Add all events to filter -Object.keys(eventHandlers).forEach((eventKey) => { - filter.addEvent((event) => - event.withKeys([FieldElement.fromBigInt(BigInt(eventKey))]) - ); -}); - -// Start indexer function -export async function startIndexer() { - client.configure({ - filter: filter.encode(), - batchSize: 1, - finality: v1alpha2.DataFinality.DATA_STATUS_FINALIZED, - cursor: StarkNetCursor.createWithBlockNumber(0), - }); - - for await (const message of client) { - if (message.message === 'data') { - const { data } = message.data!; - for (const item of data) { - const block = starknet.Block.decode(item); - for (const event of block.events) { - if (!event.event) continue; - const eventKey = FieldElement.toHex(event.event.keys![0]); - const handler = eventHandlers[eventKey]; - if (handler) { - await handler(event.event); - } - } - } - } - } -} diff --git a/backend/indexer/types.ts b/backend/indexer/types.ts deleted file mode 100644 index b1f252d..0000000 --- a/backend/indexer/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -export type NewEventAdded = { - name: string; - event_id: number; - location: string; - event_owner: string; -}; - -export type RegisteredForEvent = { - event_id: number; - event_name: string; - user_address: string; -}; - -export type EndEventRegistration = { - event_id: number; - event_name: string; - event_owner: string; -}; - -export type RSVPForEvent = { - event_id: number; - attendee_address: string; -}; - -export type EventAttendanceMark = { - event_id: number; - user_address: string; -};