Skip to content

Commit 3abb85e

Browse files
committed
merge main
2 parents 58e0833 + e65e168 commit 3abb85e

120 files changed

Lines changed: 12640 additions & 3385 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

atproto-slurper/slurper/server.ts

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ app.get("/calendar-events", async (req, res) => {
528528
.from("atproto_records")
529529
.select(
530530
`
531-
id, rkey, created_by, record_passed_review, is_core_event,
531+
id, rkey, created_by, record_passed_review, is_core_event, admin_override, updated_at,
532532
atproto_dids!created_by(did, alias)
533533
`
534534
)
@@ -539,11 +539,66 @@ app.get("/calendar-events", async (req, res) => {
539539
if (error) {
540540
res.status(500).json({ error });
541541
} else {
542-
res.json(data);
542+
const formatted = data?.map((rawEvent) => {
543+
let recordPassedReview = rawEvent.record_passed_review;
544+
// const devconnectFormSubmissionsDid = 'did:plc:l26dgtpir4fydulvmuoee2sn'
545+
// // @ts-ignore
546+
// const currentDid = rawEvent.atproto_dids.did
547+
548+
// const isDevconnectFormSubmission = currentDid === devconnectFormSubmissionsDid
549+
550+
// // Helper function to convert Argentina time to UTC (subtract 3 hours)
551+
// const convertArgentinaToUtc = (timestamp: string) => {
552+
// const date = new Date(timestamp);
553+
// date.setHours(date.getHours() - 3);
554+
// return date.toISOString();
555+
// }
556+
557+
// const tmpFixTimezone = (event: any) => {
558+
// // If event is from Devconnect submission form, then convert from Argentina time back to UTC time (subtract 3 hours)
559+
// // This is because the form is UTC, but people have been entering the time in Argentina time, and so this is now the de facto standard
560+
// // Had to change the form to not say UTC as well, which is unfortunate - anyway, the fix for now is to convert from Argentina time back to UTC time for events through the form - this needs to be fixed for future editions if we still use atprotocol.
561+
// let fixedEvent = event
562+
563+
// if (isDevconnectFormSubmission) {
564+
// if (event.timeblocks) {
565+
// fixedEvent.timeblocks = event.timeblocks.map((timeblock: any) => {
566+
// timeblock.start_utc = convertArgentinaToUtc(timeblock.start_utc)
567+
// timeblock.end_utc = convertArgentinaToUtc(timeblock.end_utc)
568+
// return timeblock
569+
// })
570+
// }
571+
572+
// if (fixedEvent.start_utc) {
573+
// fixedEvent.start_utc = convertArgentinaToUtc(fixedEvent.start_utc)
574+
// }
575+
// if (fixedEvent.end_utc) {
576+
// fixedEvent.end_utc = convertArgentinaToUtc(fixedEvent.end_utc)
577+
// }
578+
579+
// return fixedEvent
580+
// }
581+
582+
// return event
583+
// }
584+
585+
// // Apply timezone fix
586+
// const fixedRecord = tmpFixTimezone(recordPassedReview);
587+
588+
return {
589+
...rawEvent,
590+
record_passed_review: {
591+
...recordPassedReview,
592+
...rawEvent.admin_override,
593+
},
594+
};
595+
});
596+
597+
res.json(formatted);
543598
}
544599
});
545600

546-
app.get("/validate-event", async (req, res) => {
601+
app.post("/validate-event", async (req, res) => {
547602
const record = req.body.record;
548603

549604
const { valid, error } = validateRecord(record);
@@ -565,9 +620,13 @@ app.post(
565620
verifySupabaseToken,
566621
async (req: express.Request, res: express.Response) => {
567622
try {
568-
const eventData = req.body;
623+
const { event: eventData, contact } = req.body;
624+
625+
console.log("Event data:", eventData, contact);
569626

570-
console.log("Event data:", eventData);
627+
if (!contact) {
628+
return res.status(400).json({ error: "No contact provided" });
629+
}
571630

572631
if (!eventData) {
573632
return res.status(400).json({ error: "No event data provided" });
@@ -595,6 +654,23 @@ app.post(
595654
rkey
596655
);
597656

657+
const { error: contactError } = await supabase
658+
.from("atproto_records_contacts")
659+
.upsert(
660+
{
661+
rkey,
662+
email: contact,
663+
},
664+
{ onConflict: "rkey" }
665+
);
666+
667+
if (contactError) {
668+
console.error(
669+
"Error saving contact, but continuing anyway:",
670+
contactError
671+
);
672+
}
673+
598674
if (!result.success) {
599675
console.error("Error creating event:", result.error);
600676
return res.status(400).json({ error: result.error });

devcon-api/data/sessions/devcon-7/whats-in-your-dose.json

Lines changed: 0 additions & 44 deletions
This file was deleted.

devcon-api/src/controllers/account.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { decryptFile } from '@/utils/encrypt'
1111
import { parseCSV } from '@/utils/files'
1212
import { ValidateTicketPod } from '@/utils/zupass'
1313
import { GenerateRandomUsername, GetEnsAddress, GetEnsAvatar, GetEnsName } from '@/utils/account'
14+
import { apikeyHandler } from '@/middleware/apikey'
1415
import dayjs from 'dayjs'
1516

1617
const client = new PrismaClient()
@@ -32,6 +33,7 @@ accountRouter.get(`/account/speakers`, FollowedSpeakers)
3233
accountRouter.get(`/account/speakers/recommended`, RecommendedSpeakers)
3334
accountRouter.get(`/account/sessions`, FollowedSessions)
3435
accountRouter.get(`/account/sessions/recommended`, RecommendedSessions)
36+
accountRouter.post(`/account/accreditation/email`, apikeyHandler, SendAccreditationEmail)
3537

3638
async function GetAccount(req: Request, res: Response) {
3739
// #swagger.tags = ['Account']
@@ -725,6 +727,47 @@ async function RecommendedSessions(req: Request, res: Response) {
725727
return res.status(200).send({ code: 200, message: '', data: sessions })
726728
}
727729

730+
async function SendAccreditationEmail(req: Request, res: Response) {
731+
// #swagger.ignore = true
732+
// Private route: requires apiKey
733+
734+
const { email, name, accreditationLink } = req.body
735+
736+
if (!email || !name || !accreditationLink) {
737+
return res.status(400).send({
738+
code: 400,
739+
message: 'Missing required fields: email, name, accreditationLink'
740+
})
741+
}
742+
743+
try {
744+
// Import the email service
745+
const { sendAccreditationConfirmationEmail } = await import('@/services/email')
746+
747+
// Send the accreditation confirmation email
748+
const success = await sendAccreditationConfirmationEmail(email, name, accreditationLink)
749+
750+
if (success) {
751+
return res.status(200).send({
752+
code: 200,
753+
message: 'Accreditation confirmation email sent successfully',
754+
data: { email, name, accreditationLink }
755+
})
756+
} else {
757+
return res.status(500).send({
758+
code: 500,
759+
message: 'Failed to send accreditation email'
760+
})
761+
}
762+
} catch (error) {
763+
console.error('Error in SendAccreditationEmail:', error)
764+
return res.status(500).send({
765+
code: 500,
766+
message: 'Internal server error'
767+
})
768+
}
769+
}
770+
728771
async function parseProfileData(attendeeEmail: string) {
729772
const normalizedEmail = attendeeEmail.toLowerCase()
730773

devcon-api/src/services/email-templates.json

Lines changed: 28 additions & 5 deletions
Large diffs are not rendered by default.

devcon-api/src/services/email.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import nodemailer from 'nodemailer'
22
import { SERVER_CONFIG } from '@/utils/config'
33
import emailTemplates from './email-templates.json'
44

5-
type EmailTemplates = 'default-email' | 'email-cta'
5+
type EmailTemplates = 'default-email' | 'email-cta' | 'accreditation-confirmation'
66

77
const transporter = nodemailer.createTransport({
88
host: SERVER_CONFIG.SMTP_SERVICE,
@@ -22,6 +22,9 @@ export async function sendMail(to: string, template: EmailTemplates, subject: st
2222
if (template === 'email-cta') {
2323
text = replace(emailTemplates.ctaEmail.text.join('\n'), properties)
2424
html = replace(emailTemplates.ctaEmail.html, properties).replace(/(?:\r\n|\r|\n)/g, '<br>')
25+
} else if (template === 'accreditation-confirmation') {
26+
text = replace(emailTemplates.accreditationConfirmation.text.join('\n'), properties)
27+
html = replace(emailTemplates.accreditationConfirmation.html, properties).replace(/(?:\r\n|\r|\n)/g, '<br>')
2528
}
2629

2730
const response = await transporter.sendMail({
@@ -35,6 +38,25 @@ export async function sendMail(to: string, template: EmailTemplates, subject: st
3538
return response.accepted.length > 0
3639
}
3740

41+
// Example usage for accreditation confirmation
42+
export async function sendAccreditationConfirmationEmail(
43+
to: string,
44+
name: string,
45+
accreditationLink: string
46+
) {
47+
const properties = {
48+
Name: name,
49+
AccreditationLink: accreditationLink
50+
}
51+
52+
return sendMail(
53+
to,
54+
'accreditation-confirmation',
55+
'🎉 Your Accreditation Has Been Confirmed!',
56+
properties
57+
)
58+
}
59+
3860
function replace(template: string, data: any) {
3961
const pattern = /{%\s*(\w+?)\s*%}/g // {%property%}
4062
return template.replace(pattern, (_, token) => data[token] || '')
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v2.33.9
1+
v2.39.2
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CREATE TABLE atproto_records_contacts (
2+
rkey TEXT PRIMARY KEY,
3+
email TEXT NOT NULL
4+
);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
-- Create devconnect_app_user table
2+
CREATE TABLE devconnect_app_user (
3+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4+
email TEXT NOT NULL,
5+
addresses TEXT[] DEFAULT '{}',
6+
additional_ticket_emails TEXT[] DEFAULT '{}',
7+
favorite_events TEXT[] DEFAULT '{}',
8+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
9+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
10+
);
11+
12+
-- Create index on email for faster lookups
13+
CREATE INDEX idx_devconnect_app_user_email ON devconnect_app_user(email);
14+
15+
-- Add RLS (Row Level Security) policies
16+
ALTER TABLE devconnect_app_user ENABLE ROW LEVEL SECURITY;
17+
18+
-- Policy: Users can perform all operations on their own rows (where email matches)
19+
CREATE POLICY "Users can manage their own data"
20+
ON devconnect_app_user
21+
FOR ALL
22+
USING (email = auth.email())
23+
WITH CHECK (email = auth.email());
24+
25+
-- Create updated_at trigger
26+
CREATE OR REPLACE FUNCTION update_updated_at_column()
27+
RETURNS TRIGGER AS $$
28+
BEGIN
29+
NEW.updated_at = NOW();
30+
RETURN NEW;
31+
END;
32+
$$ language 'plpgsql';
33+
34+
CREATE TRIGGER update_devconnect_app_user_updated_at
35+
BEFORE UPDATE ON devconnect_app_user
36+
FOR EACH ROW
37+
EXECUTE FUNCTION update_updated_at_column();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ALTER TABLE atproto_records
2+
ADD COLUMN admin_override JSONB;
3+
4+
-- Add comment for documentation
5+
COMMENT ON COLUMN atproto_records.admin_override IS 'JSON blob of admin override data';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ALTER TABLE atproto_records
2+
ADD COLUMN priority INTEGER DEFAULT 0;
3+
4+
-- Add comment for documentation
5+
COMMENT ON COLUMN atproto_records.priority IS 'Priority of the event on the calendar';

0 commit comments

Comments
 (0)