init
Some checks failed
CI/CD / Backend & Frontend Checks (push) Has been cancelled
CI/CD / Deploy to Production (push) Has been cancelled

This commit is contained in:
2026-05-18 11:34:07 +03:30
commit 7a8ddeabed
279 changed files with 37390 additions and 0 deletions

View File

@@ -0,0 +1,182 @@
// Push Notifications Client-side JavaScript
class PushNotificationManager {
constructor() {
this.vapidPublicKey = null
this.serviceWorkerRegistration = null
this.isSupported = "serviceWorker" in navigator && "PushManager" in window
}
async init(vapidPublicKey) {
if (!this.isSupported) {
console.warn("Push notifications are not supported in this browser")
return false
}
this.vapidPublicKey = vapidPublicKey
try {
// Register service worker
this.serviceWorkerRegistration = await navigator.serviceWorker.register("/static/js/sw.js")
console.log("Service Worker registered successfully")
return true
} catch (error) {
console.error("Service Worker registration failed:", error)
return false
}
}
async requestPermission() {
if (!this.isSupported) {
return "not-supported"
}
const permission = await Notification.requestPermission()
console.log("Notification permission:", permission)
return permission
}
async subscribe() {
if (!this.serviceWorkerRegistration) {
throw new Error("Service Worker not registered")
}
try {
const subscription = await this.serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: this.urlBase64ToUint8Array(this.vapidPublicKey),
})
console.log("Push subscription successful:", subscription)
return subscription
} catch (error) {
console.error("Push subscription failed:", error)
throw error
}
}
async unsubscribe() {
if (!this.serviceWorkerRegistration) {
return false
}
try {
const subscription = await this.serviceWorkerRegistration.pushManager.getSubscription()
if (subscription) {
await subscription.unsubscribe()
console.log("Push unsubscription successful")
return true
}
return false
} catch (error) {
console.error("Push unsubscription failed:", error)
return false
}
}
async getSubscription() {
if (!this.serviceWorkerRegistration) {
return null
}
try {
return await this.serviceWorkerRegistration.pushManager.getSubscription()
} catch (error) {
console.error("Failed to get subscription:", error)
return null
}
}
async sendSubscriptionToServer(subscription, deviceType = "web") {
try {
const response = await fetch("/api/communications/push-devices/", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
},
body: JSON.stringify({
device_token: JSON.stringify(subscription),
device_type: deviceType,
}),
})
if (response.ok) {
console.log("Subscription sent to server successfully")
return true
} else {
console.error("Failed to send subscription to server:", response.statusText)
return false
}
} catch (error) {
console.error("Error sending subscription to server:", error)
return false
}
}
async removeSubscriptionFromServer(subscription) {
try {
const response = await fetch("/api/communications/push-devices/", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
},
body: JSON.stringify({
device_token: JSON.stringify(subscription),
}),
})
if (response.ok) {
console.log("Subscription removed from server successfully")
return true
} else {
console.error("Failed to remove subscription from server:", response.statusText)
return false
}
} catch (error) {
console.error("Error removing subscription from server:", error)
return false
}
}
urlBase64ToUint8Array(base64String) {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/")
const rawData = window.atob(base64)
const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
// Helper method to setup push notifications for authenticated users
async setupPushNotifications(vapidPublicKey) {
const initialized = await this.init(vapidPublicKey)
if (!initialized) return false
const permission = await this.requestPermission()
if (permission !== "granted") {
console.log("Push notification permission denied")
return false
}
try {
const subscription = await this.subscribe()
const sent = await this.sendSubscriptionToServer(subscription)
return sent
} catch (error) {
console.error("Failed to setup push notifications:", error)
return false
}
}
}
// Global instance
window.pushNotificationManager = new PushNotificationManager()
// Auto-setup for authenticated users (call this after user login)
window.setupPushNotifications = async (vapidPublicKey) =>
await window.pushNotificationManager.setupPushNotifications(vapidPublicKey)

View File

@@ -0,0 +1,21 @@
// Custom JavaScript for Django Unfold admin
document.addEventListener('DOMContentLoaded', function() {
// Add confirmation for hard delete actions
const hardDeleteButtons = document.querySelectorAll('[name="hard_delete"]');
hardDeleteButtons.forEach(button => {
button.addEventListener('click', function(e) {
if (!confirm('Are you sure you want to permanently delete this item? This action cannot be undone.')) {
e.preventDefault();
}
});
});
// Auto-resize textareas
const textareas = document.querySelectorAll('textarea');
textareas.forEach(textarea => {
textarea.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = this.scrollHeight + 'px';
});
});
});

95
backend/static/js/sw.js Normal file
View File

@@ -0,0 +1,95 @@
// Service Worker for Push Notifications
const CACHE_NAME = "cs-association-v1"
const urlsToCache = [
"/",
"/static/css/styles.css",
"/static/js/scripts.js",
"/static/images/icon-192x192.png",
"/static/images/icon-512x512.png",
]
// Install event
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache)
}),
)
})
// Fetch event
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// Return cached version or fetch from network
return response || fetch(event.request)
}),
)
})
// Push event
self.addEventListener("push", (event) => {
if (event.data) {
const data = event.data.json()
const options = {
body: data.body,
icon: data.icon || "/static/images/icon-192x192.png",
badge: data.badge || "/static/images/badge-72x72.png",
data: data.data || {},
actions: data.actions || [],
dir: data.dir || "ltr",
lang: data.lang || "en",
requireInteraction: data.data && data.data.priority === "urgent",
silent: false,
tag: data.data ? `${data.data.type}-${data.data.announcement_id || data.data.event_id}` : "default",
renotify: true,
vibrate: data.data && data.data.priority === "urgent" ? [200, 100, 200] : [100, 50, 100],
}
event.waitUntil(self.registration.showNotification(data.title, options))
}
})
// Notification click event
self.addEventListener("notificationclick", (event) => {
event.notification.close()
if (event.action === "dismiss") {
return
}
const data = event.notification.data
let url = "/"
if (data && data.url) {
url = data.url
}
event.waitUntil(
clients.matchAll({ type: "window" }).then((clientList) => {
// Check if there's already a window/tab open with the target URL
for (const client of clientList) {
if (client.url === url && "focus" in client) {
return client.focus()
}
}
// If not, open a new window/tab
if (clients.openWindow) {
return clients.openWindow(url)
}
}),
)
})
// Background sync (for offline functionality)
self.addEventListener("sync", (event) => {
if (event.tag === "background-sync") {
event.waitUntil(doBackgroundSync())
}
})
function doBackgroundSync() {
// Implement background sync logic here
return Promise.resolve()
}