A decentralized event management and credentialing system built on atproto.
at main 241 lines 9.2 kB view raw
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 %}