Skip to content

Commit 85923e9

Browse files
committed
first version
1 parent ef4ce89 commit 85923e9

6 files changed

Lines changed: 130 additions & 101 deletions

File tree

atproto-slurper/example/create-event.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import dotenv from "dotenv";
33

44
dotenv.config();
55

6-
const createEventBluesky = async (username: string, password: string) => {
6+
const createEventBluesky = async (
7+
record: any,
8+
username: string,
9+
password: string
10+
) => {
711
try {
812
// Initialize the agent with Bluesky PDS service
913
const agent = new BskyAgent({
@@ -16,20 +20,10 @@ const createEventBluesky = async (username: string, password: string) => {
1620
throw new Error("No session found");
1721
}
1822

19-
// This is your event
20-
const record = {
21-
title: "Example Devconnect Event",
22-
start: "2026-01-01T00:00:00Z",
23-
end: "2026-01-02T00:00:00Z",
24-
description: "This is an example Devcon/nect event",
25-
location: "Devcon/nect",
26-
url: "https://devconnect.org",
27-
};
28-
2923
const result = await agent.api.com.atproto.repo.putRecord({
3024
repo: agent.session.did,
3125
// Your record must adhere to this schema:
32-
collection: "org.devcon.event.test",
26+
collection: "org.devcon.event.vone",
3327
// Record key - this is effectively the id of your record - it can be whatever you want, as long as it's unique per event
3428
// Sidenote: to update the record, you can use the same rkey and it will update the existing record.
3529
rkey: record.title.toLowerCase().replace(/ /g, "-"),
@@ -45,6 +39,7 @@ const createEventBluesky = async (username: string, password: string) => {
4539
// If you have a custom PDS, you can use this function to create an event - make sure the pds is connected to the atproto network, or it won't be crawlable
4640
// It is easier to use the createEventBluesky function
4741
const createEvent = async (
42+
record: any,
4843
username: string,
4944
password: string,
5045
pdsService: string
@@ -74,7 +69,7 @@ const createEvent = async (
7469
const result = await agent.api.com.atproto.repo.putRecord({
7570
repo: agent.session.did,
7671
// Your record must adhere to this schema:
77-
collection: "org.devcon.event.test",
72+
collection: "org.devcon.event.vone",
7873
// Record key - this is effectively the id of your record - it can be whatever you want, as long as it's unique per event
7974
// Sidenote: to update the record, you can use the same rkey and it will update the existing record.
8075
rkey: record.title.toLowerCase().replace(/ /g, "-"),
@@ -90,13 +85,25 @@ const createEvent = async (
9085
(async () => {
9186
// const customPDS = "https://dcdev4.ticketh.xyz";
9287

88+
// This is your event
89+
const record = {
90+
title: "Example Devconnect Event",
91+
start: "2026-01-01T00:00:00Z",
92+
end: "2026-01-02T00:00:00Z",
93+
description: "This is an example Devcon/nect event",
94+
location: "Devcon/nect",
95+
url: "https://devconnect.org",
96+
};
97+
9398
// const customResult = await createEvent(
94-
// process.env.HANDLE || "lasse.dcdev4.ticketh.xyz", // Your custom pds handle
99+
// record,
100+
// process.env.HANDLE || "", // Your custom pds handle
95101
// process.env.PASSWORD || "", // Your custom pds password
96102
// customPDS // Your custom PDS
97103
// );
98104

99105
const blueskyResult = await createEventBluesky(
106+
record,
100107
process.env.BLUESKY_HANDLE!,
101108
process.env.BLUESKY_PASSWORD!
102109
);

atproto-slurper/slurper/atproto.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,30 @@ const api = (() => {
189189
}
190190
};
191191

192+
const removeSchema = async (
193+
serviceEndpoint: string,
194+
username: string,
195+
password: string,
196+
rkey: string
197+
) => {
198+
const { BskyAgent } = require("@atproto/api");
199+
200+
const agent = new BskyAgent({
201+
service: serviceEndpoint,
202+
});
203+
204+
// Log in with credentials
205+
await agent.login({ identifier: username, password });
206+
207+
const response = await agent.com.atproto.repo.deleteRecord({
208+
repo: agent.session.did,
209+
collection: "com.atproto.lexicon.schema",
210+
rkey: rkey,
211+
});
212+
213+
return response;
214+
};
215+
192216
const addSchema = async (
193217
serviceEndpoint: string,
194218
username: string,
@@ -208,7 +232,7 @@ const api = (() => {
208232
repo: agent.session.did,
209233
// $ nslookup -type=TXT _lexicon.lexicon.atproto.com
210234
collection: "com.atproto.lexicon.schema",
211-
rkey: "org.devcon.event.v1",
235+
rkey: "org.devcon.event.vone",
212236
record: schema,
213237
});
214238

@@ -232,7 +256,7 @@ const api = (() => {
232256

233257
const response = await agent.com.atproto.repo.putRecord({
234258
repo: agent.session.did,
235-
collection: "org.devcon.event.v1",
259+
collection: "org.devcon.event.vone",
236260
rkey: record.title.toLowerCase().replace(/ /g, "-"),
237261
record,
238262
});
@@ -247,10 +271,21 @@ const api = (() => {
247271
addSchema: async () => {
248272
const result = await addSchema(
249273
"https://bsky.social",
250-
process.env.AT_USERNAME!,
251-
process.env.AT_PASSWORD!,
274+
process.env.BLUESKY_HANDLE!,
275+
process.env.BLUESKY_PASSWORD!,
252276
schema
253277
);
278+
279+
return result;
280+
},
281+
removeSchema: async () => {
282+
const result = await removeSchema(
283+
"https://bsky.social",
284+
process.env.BLUESKY_HANDLE!,
285+
process.env.BLUESKY_PASSWORD!,
286+
"org.devcon.event.v1"
287+
);
288+
254289
return result;
255290
},
256291
test: async () => {

atproto-slurper/slurper/schema.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { LexiconDoc } from "@atproto/lexicon";
2-
31
export const schema = {
42
lexicon: 1,
53
$type: "com.atproto.lexicon.schema",
6-
id: "org.devcon.event.v1",
4+
id: "org.devcon.event.vone",
75
defs: {
86
main: {
97
type: "record",
@@ -197,7 +195,7 @@ export const schema = {
197195
};
198196

199197
export const dummyEvent = {
200-
$type: "org.devcon.event.v1",
198+
$type: "org.devcon.event.vone",
201199
start_utc: "2024-03-20T10:00:00Z",
202200
end_utc: "2024-03-20T12:00:00Z",
203201
title: "My Event",

atproto-slurper/slurper/server.ts

Lines changed: 17 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { createClient } from "@supabase/supabase-js";
44
import dotenv from "dotenv";
55
import WebSocket from "ws";
66
import validateRecord from "./validate";
7-
import { dummyEvent } from "./schema";
7+
import { dummyEvent, schema } from "./schema";
88
import { api } from "./atproto";
99

1010
dotenv.config();
@@ -18,16 +18,10 @@ const supabase = createClient(
1818
process.env.SUPABASE_KEY!
1919
);
2020

21-
// Initialize BskyAgent
22-
const agent = new BskyAgent({
23-
service: "https://bsky.social",
24-
});
25-
2621
// Target lexicon details
27-
const LEXICON_DID = "did:plc:dhnigydy24fp542wu5sxqy33"; // devcon did
28-
const COLLECTION_NAME = "org.devcon.event.test";
29-
const LEXICON_NSID = "com.atproto.lexicon.schema/org.devcon.event.test";
30-
const FULL_LEXICON_URI = `${LEXICON_DID}/${LEXICON_NSID}`;
22+
// const LEXICON_DID = "did:plc:dhnigydy24fp542wu5sxqy33"; // devcon did
23+
const COLLECTION_NAME = "org.devcon.event.vone";
24+
// const LEXICON_NSID = "com.atproto.lexicon.schema/org.devcon.event.vone";
3125

3226
// Store cursor in memory and sync with Supabase
3327
let currentCursor: string | undefined;
@@ -108,12 +102,12 @@ async function startFirehose() {
108102

109103
// Subtract a buffer (5 seconds worth of microseconds) to ensure gapless playback
110104
let cursorForConnection = cursor;
111-
// if (cursorForConnection) {
112-
// const cursorValue = BigInt(cursorForConnection);
113-
// const fiveSecondsInMicroseconds = BigInt(5 * 1000 * 1000);
114-
// cursorForConnection = String(cursorValue - fiveSecondsInMicroseconds);
115-
// console.log("Using cursor with 5 second buffer:", cursorForConnection);
116-
// }
105+
if (cursorForConnection) {
106+
const cursorValue = BigInt(cursorForConnection);
107+
const fiveSecondsInMicroseconds = BigInt(5 * 1000 * 1000);
108+
cursorForConnection = String(cursorValue - fiveSecondsInMicroseconds);
109+
console.log("Using cursor with 5 second buffer:", cursorForConnection);
110+
}
117111

118112
// Add cursor as a query parameter if it exists
119113
const wsUrl = cursorForConnection
@@ -267,37 +261,18 @@ app.get("/all-events", async (req, res) => {
267261
const { data, error } = await supabase
268262
.from("atproto-events")
269263
.select("did, record");
270-
res.json(data);
271-
});
272-
273-
app.get("/validate-event", async (req, res) => {
274-
// const record = req.body.record;
275-
276-
const { valid, error } = validateRecord(dummyEvent);
277264

278-
if (valid) {
279-
res.status(200).json({ valid: true });
280-
} else {
281-
res.status(400).json({ valid: false, error });
282-
}
283-
});
284-
285-
app.get("/submit-event", async (req, res) => {
286-
const { valid, error } = validateRecord(req.body);
287-
288-
res.json({ valid, error });
289-
290-
if (valid) {
291-
res.status(200).json({ valid: true });
265+
if (error) {
266+
res.status(500).json({ error });
292267
} else {
293-
res.status(400).json({ valid: false, error });
268+
res.json(data);
294269
}
295270
});
296271

297-
app.get("/submit-event-to-devcon-pds", async (req, res) => {
298-
const { valid, error } = validateRecord(req.body);
272+
app.get("/validate-event", async (req, res) => {
273+
const record = req.body.record;
299274

300-
res.json({ valid, error });
275+
const { valid, error } = validateRecord(record);
301276

302277
if (valid) {
303278
res.status(200).json({ valid: true });
@@ -306,42 +281,9 @@ app.get("/submit-event-to-devcon-pds", async (req, res) => {
306281
}
307282
});
308283

309-
// app.get("/add-record-to-devcon-pds", async (req, res) => {
310-
// const { valid, error } = validateRecord(req.body);
311-
312-
// if (valid) {
313-
// const result = await api.addRecordToDevconPds(
314-
// "https://bsky.social",
315-
// process.env.AT_USERNAME!,
316-
// process.env.AT_PASSWORD!,
317-
// req.body
318-
// );
319-
320-
// res.json({ valid, error });
321-
// }
322-
// });
323-
324-
// app.get("/add-schema", async (req, res) => {
325-
// if (valid) {
326-
// res.status(200).json({ valid: true });
327-
// } else {
328-
// res.status(400).json({ valid: false, error });
329-
// }
330-
// });
331-
332284
// Start the server
333285
app.listen(port, () => {
334286
console.log(`Server running on port ${port}`);
335-
// startFirehose();
336287

337-
const result = validateRecord(dummyEvent);
338-
console.log(result);
288+
startFirehose();
339289
});
340-
341-
/*
342-
TODO:
343-
- frontend to submit events
344-
- update schema to v1
345-
- update validate to v1
346-
-
347-
*/

atproto-slurper/slurper/validate.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ lexicons.add(schema as any);
1111
const validateRecord = (record: any) => {
1212
try {
1313
// This will use the same validation logic as the PDS
14-
lexicons.assertValidRecord("org.devcon.event.v1", record.record || record);
14+
lexicons.assertValidRecord(
15+
"org.devcon.event.vone",
16+
record.record || record
17+
);
1518
return { valid: true };
1619
} catch (error) {
1720
console.log(error);

devconnect/src/pages/dashboard.tsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,44 @@
11
import React, { useEffect, useState } from 'react'
22
import { SEO } from 'common/components/SEO'
3-
import { Header, Footer } from './index'
43
import Head from 'next/head'
54
import { supabase } from 'common/supabaseClient'
5+
import NewSchedule from 'lib/components/event-schedule-new'
6+
7+
const formatATProtoEvent = (atprotoEvent: any) => {
8+
// Map timeslots to timeblocks, or fallback to main event time
9+
let timeblocks: any[] = []
10+
if (Array.isArray(atprotoEvent.timeslots) && atprotoEvent.timeslots.length > 0) {
11+
timeblocks = atprotoEvent.timeslots.map((slot: any) => ({
12+
start: slot.start_utc,
13+
end: slot.end_utc,
14+
name: slot.title || undefined,
15+
}))
16+
} else if (atprotoEvent.start_utc && atprotoEvent.end_utc) {
17+
timeblocks = [
18+
{
19+
start: atprotoEvent.start_utc,
20+
end: atprotoEvent.end_utc,
21+
name: atprotoEvent.title,
22+
},
23+
]
24+
}
25+
26+
return {
27+
id: atprotoEvent.id,
28+
name: atprotoEvent.title,
29+
description: atprotoEvent.description,
30+
organizer: atprotoEvent.organizer?.name,
31+
difficulty: atprotoEvent.metadata?.expertise_level || 'All Welcome',
32+
location: {
33+
url: atprotoEvent.metadata?.website || '',
34+
text: atprotoEvent.location?.name || '',
35+
},
36+
timeblocks,
37+
priority: atprotoEvent.metadata?.priority || 1,
38+
categories: atprotoEvent.metadata?.categories || [],
39+
amountPeople: atprotoEvent.metadata?.capacity !== undefined ? String(atprotoEvent.metadata.capacity) : undefined,
40+
}
41+
}
642

743
const AdminPage = () => {
844
const [user, setUser] = useState<any>(null)
@@ -51,8 +87,6 @@ const AdminPage = () => {
5187
}
5288
}, [user])
5389

54-
console.log(events, 'events')
55-
5690
const handleMagicLink = async (e: React.FormEvent) => {
5791
e.preventDefault()
5892
setLoading(true)
@@ -94,6 +128,8 @@ const AdminPage = () => {
94128
setEventsLoading(false)
95129
}
96130

131+
const formattedEvents = events.map(formatATProtoEvent)
132+
97133
return (
98134
<>
99135
<SEO title="Admin" description="Admin dashboard for Devconnect" />
@@ -195,6 +231,14 @@ const AdminPage = () => {
195231
{message && <p style={{ marginTop: 12, color: '#0070f3' }}>{message}</p>}
196232
</>
197233
)}
234+
235+
<NewSchedule
236+
events={formattedEvents}
237+
selectedEvent={null}
238+
selectedDay={null}
239+
setSelectedEvent={() => {}}
240+
setSelectedDay={() => {}}
241+
/>
198242
</main>
199243
{/* <Footer /> */}
200244
</>

0 commit comments

Comments
 (0)