feat: Mini-Cloud Plattform - komplette Implementierung Phase 0-8
Selbstgehostete Web-Cloud mit Dateiverwaltung, Kalender, Kontakte, Email-Webclient, Office-Viewer und Passwort-Manager. Backend (Flask/Python): - JWT-Auth mit Access/Refresh Tokens, Benutzerverwaltung - Dateien: Upload/Download, Ordner, Berechtigungen, Share-Links - Kalender: CRUD, Teilen, iCal-Export, CalDAV well-known URLs - Kontakte: Adressbuecher, vCard-Export, Teilen - Email: IMAP/SMTP-Proxy, Multi-Account - Office-Viewer: DOCX/XLSX/PPTX/PDF Vorschau - Passwort-Manager: AES-256-GCM clientseitig, KeePass-Import - Sync-API fuer Desktop/Mobile-Clients - SQLite mit WAL-Modus Frontend (Vue 3 + PrimeVue): - Datei-Explorer mit Breadcrumbs und Share-Dialogen - Monatskalender mit Event-Verwaltung - Kontaktliste mit Adressbuch-Sidebar - Email-Client mit 3-Spalten-Layout - Passwort-Manager mit TOTP und Passwort-Generator - Admin-Panel, Settings, oeffentliche Share-Seite Docker: Multi-Stage Build, Bind Mounts (keine Volumes) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import apiClient from '../api/client'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref(null)
|
||||
const accessToken = ref(null)
|
||||
const masterKeySalt = ref(null)
|
||||
|
||||
const isAuthenticated = computed(() => !!accessToken.value)
|
||||
const isAdmin = computed(() => user.value?.role === 'admin')
|
||||
const hasEmailAccounts = computed(() => (user.value?.email_account_count || 0) > 0)
|
||||
|
||||
async function login(username, password) {
|
||||
const response = await apiClient.post('/auth/login', { username, password })
|
||||
user.value = response.data.user
|
||||
accessToken.value = response.data.access_token
|
||||
masterKeySalt.value = response.data.master_key_salt
|
||||
return response.data
|
||||
}
|
||||
|
||||
async function register(username, password, email) {
|
||||
const payload = { username, password }
|
||||
if (email) payload.email = email
|
||||
const response = await apiClient.post('/auth/register', payload)
|
||||
user.value = response.data.user
|
||||
accessToken.value = response.data.access_token
|
||||
return response.data
|
||||
}
|
||||
|
||||
async function refreshToken() {
|
||||
const response = await apiClient.post('/auth/refresh')
|
||||
accessToken.value = response.data.access_token
|
||||
return response.data.access_token
|
||||
}
|
||||
|
||||
async function fetchMe() {
|
||||
const response = await apiClient.get('/auth/me')
|
||||
user.value = response.data
|
||||
masterKeySalt.value = response.data.master_key_salt
|
||||
return response.data
|
||||
}
|
||||
|
||||
async function changePassword(currentPassword, newPassword) {
|
||||
await apiClient.post('/auth/change-password', {
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword,
|
||||
})
|
||||
}
|
||||
|
||||
function logout() {
|
||||
apiClient.post('/auth/logout').catch(() => {})
|
||||
user.value = null
|
||||
accessToken.value = null
|
||||
masterKeySalt.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
accessToken,
|
||||
masterKeySalt,
|
||||
isAuthenticated,
|
||||
isAdmin,
|
||||
hasEmailAccounts,
|
||||
login,
|
||||
register,
|
||||
refreshToken,
|
||||
fetchMe,
|
||||
changePassword,
|
||||
logout,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,84 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import apiClient from '../api/client'
|
||||
|
||||
export const useFilesStore = defineStore('files', () => {
|
||||
const files = ref([])
|
||||
const breadcrumb = ref([])
|
||||
const currentParentId = ref(null)
|
||||
const loading = ref(false)
|
||||
|
||||
async function loadFiles(parentId = null) {
|
||||
loading.value = true
|
||||
try {
|
||||
currentParentId.value = parentId
|
||||
const params = parentId ? { parent_id: parentId } : {}
|
||||
const response = await apiClient.get('/files', { params })
|
||||
files.value = response.data.files
|
||||
breadcrumb.value = response.data.breadcrumb
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createFolder(name, parentId = null) {
|
||||
const response = await apiClient.post('/files/folder', {
|
||||
name,
|
||||
parent_id: parentId,
|
||||
})
|
||||
await loadFiles(parentId)
|
||||
return response.data
|
||||
}
|
||||
|
||||
async function uploadFile(file, parentId = null) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
if (parentId) formData.append('parent_id', parentId)
|
||||
|
||||
const response = await apiClient.post('/files/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
await loadFiles(parentId)
|
||||
return response.data
|
||||
}
|
||||
|
||||
async function deleteFile(fileId) {
|
||||
await apiClient.delete(`/files/${fileId}`)
|
||||
await loadFiles(currentParentId.value)
|
||||
}
|
||||
|
||||
async function renameFile(fileId, newName) {
|
||||
await apiClient.put(`/files/${fileId}`, { name: newName })
|
||||
await loadFiles(currentParentId.value)
|
||||
}
|
||||
|
||||
async function moveFile(fileId, newParentId) {
|
||||
await apiClient.put(`/files/${fileId}`, { parent_id: newParentId })
|
||||
await loadFiles(currentParentId.value)
|
||||
}
|
||||
|
||||
async function createShareLink(fileId, options = {}) {
|
||||
const response = await apiClient.post(`/files/${fileId}/share`, options)
|
||||
return response.data
|
||||
}
|
||||
|
||||
async function getShareLinks(fileId) {
|
||||
const response = await apiClient.get(`/files/${fileId}/shares`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
async function deleteShareLink(token) {
|
||||
await apiClient.delete(`/share/${token}`)
|
||||
}
|
||||
|
||||
function downloadUrl(fileId) {
|
||||
return `/api/files/${fileId}/download`
|
||||
}
|
||||
|
||||
return {
|
||||
files, breadcrumb, currentParentId, loading,
|
||||
loadFiles, createFolder, uploadFile, deleteFile,
|
||||
renameFile, moveFile, createShareLink, getShareLinks,
|
||||
deleteShareLink, downloadUrl,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user