A decentralized event management and credentialing system built on atproto.
1{% extends "admin_base.html" %}
2
3{% block title %}Import Events - Admin Panel{% endblock %}
4
5{% block content %}
6<!-- Page Header -->
7<div class="level">
8 <div class="level-left">
9 <div class="level-item">
10 <div>
11 <h1 class="title is-4">
12 <i class="fas fa-upload"></i> Import Events
13 </h1>
14 <p class="subtitle is-6">
15 Bulk import events from JSON data
16 </p>
17 </div>
18 </div>
19 </div>
20 <div class="level-right">
21 <div class="level-item">
22 <a href="/admin/events" class="button is-light">
23 <span class="icon"><i class="fas fa-arrow-left"></i></span>
24 <span>Back to Events</span>
25 </a>
26 </div>
27 </div>
28</div>
29
30<div class="columns">
31 <div class="column is-two-thirds">
32 <!-- Import Form -->
33 <div class="box">
34 <h2 class="title is-5">
35 <i class="fas fa-file-import"></i> Event Import Form
36 </h2>
37
38 <form method="post" action="/admin/events/import">
39 <div class="field">
40 <label class="label">Events JSON Data</label>
41 <div class="control">
42 <textarea class="textarea" name="events_json" rows="20"
43 placeholder="Paste your JSON array of events here..."
44 required></textarea>
45 </div>
46 <p class="help">
47 Provide a JSON array containing event objects. Each event must have an <code>aturi</code> or <code>uri</code> field.
48 </p>
49 </div>
50
51 <div class="field is-grouped">
52 <div class="control">
53 <button type="submit" class="button is-primary is-medium">
54 <span class="icon"><i class="fas fa-upload"></i></span>
55 <span>Import Events</span>
56 </button>
57 </div>
58 <div class="control">
59 <button type="button" class="button is-light is-medium" id="validateBtn">
60 <span class="icon"><i class="fas fa-check-circle"></i></span>
61 <span>Validate JSON</span>
62 </button>
63 </div>
64 <div class="control">
65 <button type="button" class="button is-info is-light is-medium" id="exampleBtn">
66 <span class="icon"><i class="fas fa-lightbulb"></i></span>
67 <span>Load Example</span>
68 </button>
69 </div>
70 </div>
71
72 <div id="validationResult" class="mt-4" style="display: none;"></div>
73 </form>
74 </div>
75 </div>
76
77 <div class="column is-one-third">
78 <!-- Format Guide -->
79 <div class="box">
80 <h2 class="title is-5">
81 <i class="fas fa-info-circle"></i> Import Format
82 </h2>
83
84 <div class="content is-size-7">
85 <p>Events should be provided as a JSON array with the following structure:</p>
86
87 <h6 class="title is-6">Required Fields</h6>
88 <ul>
89 <li><code>aturi</code> or <code>uri</code> - AT Protocol URI identifier</li>
90 </ul>
91
92 <h6 class="title is-6">Common Fields</h6>
93 <ul>
94 <li><code>title</code> - Event title/name</li>
95 <li><code>description</code> - Event description</li>
96 <li><code>startDate</code> - ISO 8601 start date/time</li>
97 <li><code>endDate</code> - ISO 8601 end date/time</li>
98 <li><code>location</code> - Location object with name/address</li>
99 <li><code>organizer</code> - Organizer object with name/DID</li>
100 </ul>
101 </div>
102
103 <div class="notification is-info is-light">
104 <p class="is-size-7">
105 <strong>AT-URI Format:</strong><br>
106 <code>at://did:plc:abc123/community.lexicon.calendar.event/eventkey</code>
107 </p>
108 </div>
109 </div>
110
111 <!-- Example Data -->
112 <div class="box">
113 <h2 class="title is-5">
114 <i class="fas fa-code"></i> Example Format
115 </h2>
116
117 <pre class="is-size-7" style="max-height: 300px; overflow-y: auto;" id="exampleJson">[
118 {
119 "aturi": "at://did:plc:example123/community.lexicon.calendar.event/event1",
120 "title": "Example Conference 2025",
121 "description": "An amazing tech conference",
122 "startDate": "2025-08-15T09:00:00.000Z",
123 "endDate": "2025-08-17T18:00:00.000Z",
124 "location": {
125 "name": "Convention Center",
126 "address": "123 Main St, City, State"
127 },
128 "organizer": {
129 "name": "Event Organizers Inc",
130 "did": "did:plc:organizer123"
131 }
132 },
133 {
134 "aturi": "at://did:plc:example456/community.lexicon.calendar.event/event2",
135 "title": "Workshop Series",
136 "description": "Hands-on learning workshops",
137 "startDate": "2025-09-01T10:00:00.000Z",
138 "endDate": "2025-09-01T16:00:00.000Z"
139 }
140]</pre>
141 </div>
142
143 <!-- Important Notes -->
144 <div class="box">
145 <h2 class="title is-5">
146 <i class="fas fa-exclamation-triangle"></i> Important Notes
147 </h2>
148
149 <div class="content is-size-7">
150 <div class="notification is-warning is-light">
151 <ul>
152 <li><strong>Duplicates:</strong> Events with existing AT-URIs will be updated with new data</li>
153 <li><strong>Validation:</strong> Invalid AT-URIs will cause import to fail</li>
154 <li><strong>Transaction:</strong> Import is all-or-nothing - either all events import or none do</li>
155 </ul>
156 </div>
157
158 <div class="notification is-success is-light">
159 <p>
160 <strong>Tip:</strong> Use the "Validate JSON" button to check your data format before importing.
161 </p>
162 </div>
163 </div>
164 </div>
165 </div>
166</div>
167
168<script>
169document.addEventListener('DOMContentLoaded', function() {
170 const textarea = document.querySelector('textarea[name="events_json"]');
171 const validateBtn = document.getElementById('validateBtn');
172 const exampleBtn = document.getElementById('exampleBtn');
173 const validationResult = document.getElementById('validationResult');
174 const exampleJson = document.getElementById('exampleJson');
175
176 // Validate JSON button
177 validateBtn.addEventListener('click', function() {
178 const jsonText = textarea.value.trim();
179
180 if (!jsonText) {
181 showValidationResult('Please enter some JSON data to validate.', 'warning');
182 return;
183 }
184
185 try {
186 const parsed = JSON.parse(jsonText);
187
188 if (!Array.isArray(parsed)) {
189 showValidationResult('JSON must be an array of events.', 'danger');
190 return;
191 }
192
193 let validCount = 0;
194 let errors = [];
195
196 parsed.forEach((event, index) => {
197 if (!event.aturi && !event.uri) {
198 errors.push(`Event ${index + 1}: Missing 'aturi' or 'uri' field`);
199 } else {
200 const uri = event.aturi || event.uri;
201 if (!uri.startsWith('at://')) {
202 errors.push(`Event ${index + 1}: Invalid AT-URI format: ${uri}`);
203 } else {
204 validCount++;
205 }
206 }
207 });
208
209 if (errors.length > 0) {
210 showValidationResult(
211 `Found ${errors.length} validation error(s):\n• ${errors.join('\n• ')}`,
212 'danger'
213 );
214 } else {
215 showValidationResult(
216 `✅ JSON is valid! Found ${validCount} events ready for import.`,
217 'success'
218 );
219 }
220 } catch (e) {
221 showValidationResult(`Invalid JSON format: ${e.message}`, 'danger');
222 }
223 });
224
225 // Load example button
226 exampleBtn.addEventListener('click', function() {
227 textarea.value = exampleJson.textContent.trim();
228 showValidationResult('Example data loaded! You can now validate or modify as needed.', 'info');
229 });
230
231 function showValidationResult(message, type) {
232 validationResult.innerHTML = `
233 <div class="notification is-${type}">
234 <pre style="white-space: pre-wrap; font-family: inherit;">${message}</pre>
235 </div>
236 `;
237 validationResult.style.display = 'block';
238 }
239});
240</script>
241{% endblock %}