A website for the ATmosphereConf
1import type { APIRoute } from 'astro'
2import { getOAuthClient } from '../../../lib/context'
3import { getSession } from '../../../lib/session'
4import { Agent, BlobRef } from '@atproto/api'
5import type { Main as ProfileRecord } from '../../../lexicon/types/org/atmosphereconf/profile'
6
7async function fileToBlob(agent: Agent, file: File): Promise<BlobRef> {
8 const arrayBuffer = await file.arrayBuffer()
9 const uint8Array = new Uint8Array(arrayBuffer)
10
11 const response = await agent.com.atproto.repo.uploadBlob(uint8Array, {
12 encoding: file.type,
13 })
14
15 return response.data.blob
16}
17
18export const POST: APIRoute = async ({ request, cookies, redirect }) => {
19 try {
20 const session = getSession(cookies)
21 const oauthClient = getOAuthClient(cookies)
22
23 if (!session.did) {
24 return new Response('Unauthorized', { status: 401 })
25 }
26
27 const oauthSession = await oauthClient.restore(session.did)
28 if (!oauthSession) {
29 return new Response('Session expired', { status: 401 })
30 }
31
32 const agent = new Agent(oauthSession)
33 const formData = await request.formData()
34
35 // Extract form data
36 const displayName = formData.get('displayName')
37 const description = formData.get('description')
38 const avatarFile = formData.get('avatar')
39 const bannerFile = formData.get('banner')
40
41 if (!displayName || typeof displayName !== 'string') {
42 return new Response('Display name is required', { status: 400 })
43 }
44
45 // Validate file sizes
46 if (avatarFile instanceof File && avatarFile.size > 0 && avatarFile.size > 1000000) {
47 return new Response('Avatar file size must be less than 1MB', { status: 400 })
48 }
49
50 if (bannerFile instanceof File && bannerFile.size > 0 && bannerFile.size > 1000000) {
51 return new Response('Banner file size must be less than 1MB', { status: 400 })
52 }
53
54 // Build the profile record
55 const record: Omit<ProfileRecord, '$type'> = {
56 displayName: displayName.slice(0, 64),
57 description: typeof description === 'string' ? description.slice(0, 256) : undefined,
58 createdAt: new Date().toISOString(),
59 }
60
61 // Upload avatar if provided
62 if (avatarFile instanceof File && avatarFile.size > 0) {
63 try {
64 record.avatar = await fileToBlob(agent, avatarFile)
65 } catch (err) {
66 console.error('Failed to upload avatar:', err)
67 return new Response('Failed to upload avatar', { status: 500 })
68 }
69 }
70
71 // Upload banner if provided
72 if (bannerFile instanceof File && bannerFile.size > 0) {
73 try {
74 record.banner = await fileToBlob(agent, bannerFile)
75 } catch (err) {
76 console.error('Failed to upload banner:', err)
77 return new Response('Failed to upload banner', { status: 500 })
78 }
79 }
80
81 // Create or update the profile record
82 try {
83 await agent.com.atproto.repo.putRecord({
84 repo: agent.assertDid,
85 collection: 'org.atmosphereconf.profile',
86 rkey: 'self',
87 record: {
88 $type: 'org.atmosphereconf.profile',
89 ...record,
90 },
91 })
92
93 return redirect('/')
94 } catch (err) {
95 console.error('Failed to create profile:', err)
96 const error = err instanceof Error ? err.message : 'unexpected error'
97 return new Response(`Failed to create profile: ${error}`, { status: 500 })
98 }
99 } catch (err) {
100 console.error('Profile creation failed:', err)
101 const error = err instanceof Error ? err.message : 'unexpected error'
102 return new Response(`Profile creation failed: ${error}`, { status: 500 })
103 }
104}