First version of media manager
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -67,6 +67,7 @@ received_screenshots/
|
|||||||
mosquitto/
|
mosquitto/
|
||||||
alte/
|
alte/
|
||||||
screenshots/
|
screenshots/
|
||||||
|
media/
|
||||||
dashboard/manitine_test.py
|
dashboard/manitine_test.py
|
||||||
dashboard/pages/test.py
|
dashboard/pages/test.py
|
||||||
.gitignore
|
.gitignore
|
||||||
|
|||||||
67
dashboard/package-lock.json
generated
67
dashboard/package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"@syncfusion/ej2-react-buttons": "^30.1.37",
|
"@syncfusion/ej2-react-buttons": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-calendars": "^30.1.37",
|
"@syncfusion/ej2-react-calendars": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-dropdowns": "^30.1.37",
|
"@syncfusion/ej2-react-dropdowns": "^30.1.37",
|
||||||
|
"@syncfusion/ej2-react-filemanager": "^30.1.38",
|
||||||
"@syncfusion/ej2-react-grids": "^30.1.37",
|
"@syncfusion/ej2-react-grids": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-inputs": "^30.1.38",
|
"@syncfusion/ej2-react-inputs": "^30.1.38",
|
||||||
"@syncfusion/ej2-react-kanban": "^30.1.37",
|
"@syncfusion/ej2-react-kanban": "^30.1.37",
|
||||||
@@ -1025,12 +1026,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@syncfusion/ej2-data": {
|
"node_modules/@syncfusion/ej2-data": {
|
||||||
"version": "30.1.37",
|
"version": "30.1.38",
|
||||||
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-data/-/ej2-data-30.1.37.tgz",
|
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-data/-/ej2-data-30.1.38.tgz",
|
||||||
"integrity": "sha512-UGbF1a95BmdRcgkgekMOdJY0kcNSaOrKWLlnZxFkEl9SSvN+mxPa7ktgXHlEWRc+KY/WeNSV2kmyZ8h/htnAXQ==",
|
"integrity": "sha512-BdqvjLzzK4OuUR1YlzPSG3SmeGg1mrLz/6ih5oD9dSpRXDoMG24bpO1rwCK7mjy8Dp9IJ8mliyCbPfoDycxM9Q==",
|
||||||
"license": "SEE LICENSE IN license",
|
"license": "SEE LICENSE IN license",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@syncfusion/ej2-base": "~30.1.37"
|
"@syncfusion/ej2-base": "~30.1.38"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@syncfusion/ej2-dropdowns": {
|
"node_modules/@syncfusion/ej2-dropdowns": {
|
||||||
@@ -1064,6 +1065,47 @@
|
|||||||
"integrity": "sha512-1r0rWrXEsXsRJhG0yOs8Hp2nGARLsBCD4u602R4IxLDrockIPVJv4gz0rIw4dDtFW7bpAa8J+yWXtr6fW29Bhw==",
|
"integrity": "sha512-1r0rWrXEsXsRJhG0yOs8Hp2nGARLsBCD4u602R4IxLDrockIPVJv4gz0rIw4dDtFW7bpAa8J+yWXtr6fW29Bhw==",
|
||||||
"license": "SEE LICENSE IN license"
|
"license": "SEE LICENSE IN license"
|
||||||
},
|
},
|
||||||
|
"node_modules/@syncfusion/ej2-filemanager": {
|
||||||
|
"version": "30.1.38",
|
||||||
|
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-filemanager/-/ej2-filemanager-30.1.38.tgz",
|
||||||
|
"integrity": "sha512-QuXnn7V1/Ipit+zl1+3WJsnXytqX0VHVSJ5vzhdpE87nENdDktcOmifzZPpPd19ozh7rif2t2aP41KivUVF14A==",
|
||||||
|
"license": "SEE LICENSE IN license",
|
||||||
|
"dependencies": {
|
||||||
|
"@syncfusion/ej2-base": "~30.1.38",
|
||||||
|
"@syncfusion/ej2-buttons": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-data": "~30.1.38",
|
||||||
|
"@syncfusion/ej2-grids": "~30.1.38",
|
||||||
|
"@syncfusion/ej2-inputs": "~30.1.38",
|
||||||
|
"@syncfusion/ej2-layouts": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-lists": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-navigations": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-popups": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-splitbuttons": "~30.1.37"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@syncfusion/ej2-filemanager/node_modules/@syncfusion/ej2-grids": {
|
||||||
|
"version": "30.1.38",
|
||||||
|
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-grids/-/ej2-grids-30.1.38.tgz",
|
||||||
|
"integrity": "sha512-0ULWC/P8AsYco3fhUfrkppEdU+IMzrIWyoP057/yp0Mktq9UI5mgvQ12ruZbEMQXl0vK5S5DKaWMDJU2vBTDWQ==",
|
||||||
|
"license": "SEE LICENSE IN license",
|
||||||
|
"dependencies": {
|
||||||
|
"@syncfusion/ej2-base": "~30.1.38",
|
||||||
|
"@syncfusion/ej2-buttons": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-calendars": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-compression": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-data": "~30.1.38",
|
||||||
|
"@syncfusion/ej2-dropdowns": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-excel-export": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-file-utils": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-inputs": "~30.1.38",
|
||||||
|
"@syncfusion/ej2-lists": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-navigations": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-notifications": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-pdf-export": "~30.1.38",
|
||||||
|
"@syncfusion/ej2-popups": "~30.1.37",
|
||||||
|
"@syncfusion/ej2-splitbuttons": "~30.1.37"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@syncfusion/ej2-grids": {
|
"node_modules/@syncfusion/ej2-grids": {
|
||||||
"version": "30.1.37",
|
"version": "30.1.37",
|
||||||
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-grids/-/ej2-grids-30.1.37.tgz",
|
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-grids/-/ej2-grids-30.1.37.tgz",
|
||||||
@@ -1169,9 +1211,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@syncfusion/ej2-pdf-export": {
|
"node_modules/@syncfusion/ej2-pdf-export": {
|
||||||
"version": "30.1.37",
|
"version": "30.1.38",
|
||||||
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-pdf-export/-/ej2-pdf-export-30.1.37.tgz",
|
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-pdf-export/-/ej2-pdf-export-30.1.38.tgz",
|
||||||
"integrity": "sha512-4vtuyp+IhB8FUdxnizwytYFYP7Uxdr0thFCEuT3p7T7v4HzwEIk1uingaO32Z4AD72LUNuzznnRTZNyd59VU7w==",
|
"integrity": "sha512-iWgy/40rgKGweC3CJozn3MxcwKRg4OVoS7bnpkF+aDWFlJu4qV2bjZawacwASCitca+0jSs5aT7KOc24cmqJ4w==",
|
||||||
"license": "SEE LICENSE IN license",
|
"license": "SEE LICENSE IN license",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@syncfusion/ej2-compression": "~30.1.37"
|
"@syncfusion/ej2-compression": "~30.1.37"
|
||||||
@@ -1229,6 +1271,17 @@
|
|||||||
"@syncfusion/ej2-react-base": "~30.1.37"
|
"@syncfusion/ej2-react-base": "~30.1.37"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@syncfusion/ej2-react-filemanager": {
|
||||||
|
"version": "30.1.38",
|
||||||
|
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-filemanager/-/ej2-react-filemanager-30.1.38.tgz",
|
||||||
|
"integrity": "sha512-xwq7J24oC7zoiuSQpBe1NWvdK/4zIFSZOXGfTapUdjdczcolC6d+s3AgGmJZLvR1rqdNCJE99m5DO0LiRnJz3A==",
|
||||||
|
"license": "SEE LICENSE IN license",
|
||||||
|
"dependencies": {
|
||||||
|
"@syncfusion/ej2-base": "~30.1.38",
|
||||||
|
"@syncfusion/ej2-filemanager": "30.1.38",
|
||||||
|
"@syncfusion/ej2-react-base": "~30.1.37"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@syncfusion/ej2-react-grids": {
|
"node_modules/@syncfusion/ej2-react-grids": {
|
||||||
"version": "30.1.37",
|
"version": "30.1.37",
|
||||||
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-grids/-/ej2-react-grids-30.1.37.tgz",
|
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-grids/-/ej2-react-grids-30.1.37.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"@syncfusion/ej2-react-buttons": "^30.1.37",
|
"@syncfusion/ej2-react-buttons": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-calendars": "^30.1.37",
|
"@syncfusion/ej2-react-calendars": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-dropdowns": "^30.1.37",
|
"@syncfusion/ej2-react-dropdowns": "^30.1.37",
|
||||||
|
"@syncfusion/ej2-react-filemanager": "^30.1.38",
|
||||||
"@syncfusion/ej2-react-grids": "^30.1.37",
|
"@syncfusion/ej2-react-grids": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-inputs": "^30.1.38",
|
"@syncfusion/ej2-react-inputs": "^30.1.38",
|
||||||
"@syncfusion/ej2-react-kanban": "^30.1.37",
|
"@syncfusion/ej2-react-kanban": "^30.1.37",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@import "../node_modules/@syncfusion/ej2-react-filemanager/styles/material.css";
|
||||||
@import "../node_modules/@syncfusion/ej2-base/styles/material.css";
|
@import "../node_modules/@syncfusion/ej2-base/styles/material.css";
|
||||||
@import "../node_modules/@syncfusion/ej2-buttons/styles/material.css";
|
@import "../node_modules/@syncfusion/ej2-buttons/styles/material.css";
|
||||||
@import "../node_modules/@syncfusion/ej2-calendars/styles/material.css";
|
@import "../node_modules/@syncfusion/ej2-calendars/styles/material.css";
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ const App: React.FC = () => (
|
|||||||
<Route path="ressourcen" element={<Ressourcen />} />
|
<Route path="ressourcen" element={<Ressourcen />} />
|
||||||
<Route path="Infoscreens" element={<Infoscreens />} />
|
<Route path="Infoscreens" element={<Infoscreens />} />
|
||||||
<Route path="infoscr_groups" element={<Infoscreen_groups />} />
|
<Route path="infoscr_groups" element={<Infoscreen_groups />} />
|
||||||
<Route path="medien" element={<Medien />} />
|
<Route path="medien" element={<Media />} />
|
||||||
<Route path="benutzer" element={<Benutzer />} />
|
<Route path="benutzer" element={<Benutzer />} />
|
||||||
<Route path="einstellungen" element={<Einstellungen />} />
|
<Route path="einstellungen" element={<Einstellungen />} />
|
||||||
</Route>
|
</Route>
|
||||||
@@ -146,6 +146,6 @@ import Appointments from './appointments';
|
|||||||
import Ressourcen from './ressourcen';
|
import Ressourcen from './ressourcen';
|
||||||
import Infoscreens from './clients';
|
import Infoscreens from './clients';
|
||||||
import Infoscreen_groups from './infoscreen_groups';
|
import Infoscreen_groups from './infoscreen_groups';
|
||||||
import Medien from './medien';
|
import Media from './media';
|
||||||
import Benutzer from './benutzer';
|
import Benutzer from './benutzer';
|
||||||
import Einstellungen from './einstellungen';
|
import Einstellungen from './einstellungen';
|
||||||
|
|||||||
27
dashboard/src/components/CustomMediaInfoPanel.tsx
Normal file
27
dashboard/src/components/CustomMediaInfoPanel.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface CustomMediaInfoPanelProps {
|
||||||
|
mediaId: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
eventId?: string;
|
||||||
|
onSave: (data: { title: string; description: string; eventId?: string }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomMediaInfoPanel: React.FC<CustomMediaInfoPanelProps> = ({
|
||||||
|
mediaId,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
eventId,
|
||||||
|
onSave,
|
||||||
|
}) => {
|
||||||
|
// Hier kannst du Formularfelder und Logik für die Bearbeitung einbauen
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Medien-Informationen bearbeiten</h3>
|
||||||
|
{/* Formularfelder für Titel, Beschreibung, Event-Zuordnung */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomMediaInfoPanel;
|
||||||
100
dashboard/src/media.tsx
Normal file
100
dashboard/src/media.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import CustomMediaInfoPanel from './components/CustomMediaInfoPanel';
|
||||||
|
import {
|
||||||
|
FileManagerComponent,
|
||||||
|
Inject,
|
||||||
|
NavigationPane,
|
||||||
|
DetailsView,
|
||||||
|
Toolbar,
|
||||||
|
} from '@syncfusion/ej2-react-filemanager';
|
||||||
|
|
||||||
|
const hostUrl = '/api/eventmedia/filemanager/'; // Dein Backend-Endpunkt für FileManager
|
||||||
|
|
||||||
|
interface MediaItem {
|
||||||
|
id: string;
|
||||||
|
file_path: string;
|
||||||
|
url: string;
|
||||||
|
description: string;
|
||||||
|
eventId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Media: React.FC = () => {
|
||||||
|
const [mediaList, setMediaList] = useState<MediaItem[]>([]);
|
||||||
|
const [selectedMedia, setSelectedMedia] = useState<MediaItem | null>(null);
|
||||||
|
|
||||||
|
// Medien vom Server laden
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/eventmedia')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(setMediaList);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Upload-Handler (vereinfachtes Beispiel)
|
||||||
|
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (!e.target.files?.length) return;
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', e.target.files[0]);
|
||||||
|
await fetch('/api/eventmedia/upload', { method: 'POST', body: formData });
|
||||||
|
// Nach Upload neu laden
|
||||||
|
const res = await fetch('/api/eventmedia');
|
||||||
|
setMediaList(await res.json());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Speichern von Metadaten/Event-Zuordnung
|
||||||
|
const handleSave = async (data: { title: string; description: string; eventId?: string }) => {
|
||||||
|
if (!selectedMedia) return;
|
||||||
|
await fetch(`/api/eventmedia/${selectedMedia.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
// Nach dem Speichern neu laden
|
||||||
|
const res = await fetch('/api/eventmedia');
|
||||||
|
setMediaList(await res.json());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-bold mb-4">Medien</h2>
|
||||||
|
<FileManagerComponent
|
||||||
|
ajaxSettings={{
|
||||||
|
url: hostUrl + 'operations',
|
||||||
|
getImageUrl: hostUrl + 'get-image',
|
||||||
|
uploadUrl: hostUrl + 'upload',
|
||||||
|
downloadUrl: hostUrl + 'download',
|
||||||
|
}}
|
||||||
|
toolbarSettings={{
|
||||||
|
items: [
|
||||||
|
'NewFolder',
|
||||||
|
'Upload',
|
||||||
|
'Download',
|
||||||
|
'Rename',
|
||||||
|
'Delete',
|
||||||
|
'SortBy',
|
||||||
|
'Refresh',
|
||||||
|
'Details',
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
contextMenuSettings={{
|
||||||
|
file: ['Open', '|', 'Download', '|', 'Rename', 'Delete', '|', 'Details'],
|
||||||
|
folder: ['Open', '|', 'Rename', 'Delete', '|', 'Details'],
|
||||||
|
layout: ['SortBy', 'Refresh', '|', 'View', 'Details'],
|
||||||
|
}}
|
||||||
|
allowMultiSelection={false}
|
||||||
|
>
|
||||||
|
<Inject services={[NavigationPane, DetailsView, Toolbar]} />
|
||||||
|
</FileManagerComponent>
|
||||||
|
{selectedMedia && (
|
||||||
|
<CustomMediaInfoPanel
|
||||||
|
mediaId={selectedMedia.id}
|
||||||
|
title={selectedMedia.url}
|
||||||
|
description={selectedMedia.description}
|
||||||
|
eventId={selectedMedia.eventId}
|
||||||
|
onSave={handleSave}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Media;
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
const Medien: React.FC = () => (
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-bold mb-4">Medien</h2>
|
|
||||||
<p>Willkommen im Infoscreen-Management Medien.</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
export default Medien;
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"""Update media_type enum for event_media
|
||||||
|
|
||||||
|
Revision ID: a0f3f9325e05
|
||||||
|
Revises: bb29b5524f5c
|
||||||
|
Create Date: 2025-07-05 07:49:37.696162
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'a0f3f9325e05'
|
||||||
|
down_revision: Union[str, None] = 'bb29b5524f5c'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.execute("""
|
||||||
|
ALTER TABLE event_media MODIFY COLUMN media_type ENUM(
|
||||||
|
'pdf','ppt','pptx','odp',
|
||||||
|
'mp4','avi','mkv','mov','wmv','flv','webm','mpg','mpeg','ogv',
|
||||||
|
'jpg','jpeg','png','gif','bmp','tiff','svg',
|
||||||
|
'html'
|
||||||
|
) NOT NULL;
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
pass
|
||||||
@@ -116,7 +116,16 @@ class Event(Base):
|
|||||||
class EventMedia(Base):
|
class EventMedia(Base):
|
||||||
__tablename__ = 'event_media'
|
__tablename__ = 'event_media'
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
media_type = Column(Enum(MediaType), nullable=False) # Enum angepasst!
|
media_type = Column(Enum(MediaType), nullable=False)
|
||||||
url = Column(String(255), nullable=False)
|
url = Column(String(255), nullable=False)
|
||||||
file_path = Column(String(255), nullable=True)
|
file_path = Column(String(255), nullable=True)
|
||||||
message_content = Column(Text, nullable=True)
|
message_content = Column(Text, nullable=True)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"media_type": self.media_type.value if self.media_type else None,
|
||||||
|
"url": self.url,
|
||||||
|
"file_path": self.file_path,
|
||||||
|
"message_content": self.message_content,
|
||||||
|
}
|
||||||
|
|||||||
130
server/routes/eventmedia.py
Normal file
130
server/routes/eventmedia.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
from re import A
|
||||||
|
from flask import Blueprint, request, jsonify, send_from_directory
|
||||||
|
from database import Session
|
||||||
|
from models import EventMedia
|
||||||
|
import os
|
||||||
|
|
||||||
|
eventmedia_bp = Blueprint('eventmedia', __name__, url_prefix='/api/eventmedia')
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
|
|
||||||
|
|
||||||
|
def get_param(key, default=None):
|
||||||
|
# Reihenfolge: form > json > args
|
||||||
|
if request.form and key in request.form:
|
||||||
|
return request.form.get(key, default)
|
||||||
|
if request.is_json and request.json and key in request.json:
|
||||||
|
return request.json.get(key, default)
|
||||||
|
return request.args.get(key, default)
|
||||||
|
|
||||||
|
# --- FileManager: List, Create Folder, Rename, Delete, Move ---
|
||||||
|
|
||||||
|
|
||||||
|
@eventmedia_bp.route('/filemanager/operations', methods=['GET', 'POST'])
|
||||||
|
def filemanager_operations():
|
||||||
|
action = get_param('action')
|
||||||
|
path = get_param('path', '/')
|
||||||
|
name = get_param('name')
|
||||||
|
new_name = get_param('newName')
|
||||||
|
target_path = get_param('targetPath')
|
||||||
|
|
||||||
|
full_path = os.path.join(MEDIA_ROOT, path.lstrip('/'))
|
||||||
|
|
||||||
|
print(action, path, name, new_name, target_path, full_path) # Debug-Ausgabe
|
||||||
|
|
||||||
|
if action == 'read':
|
||||||
|
# List files and folders
|
||||||
|
items = []
|
||||||
|
for entry in os.scandir(full_path):
|
||||||
|
items.append({
|
||||||
|
'name': entry.name,
|
||||||
|
'isFile': entry.is_file(),
|
||||||
|
'size': entry.stat().st_size,
|
||||||
|
'dateModified': entry.stat().st_mtime,
|
||||||
|
'type': os.path.splitext(entry.name)[1][1:] if entry.is_file() else '',
|
||||||
|
'hasChild': entry.is_dir()
|
||||||
|
})
|
||||||
|
return jsonify({'files': items, 'cwd': {'name': os.path.basename(full_path), 'path': path}})
|
||||||
|
elif action == 'delete':
|
||||||
|
for item in request.form.getlist('names[]'):
|
||||||
|
item_path = os.path.join(full_path, item)
|
||||||
|
if os.path.isdir(item_path):
|
||||||
|
os.rmdir(item_path)
|
||||||
|
else:
|
||||||
|
os.remove(item_path)
|
||||||
|
return jsonify({'success': True})
|
||||||
|
elif action == 'rename':
|
||||||
|
src = os.path.join(full_path, name)
|
||||||
|
dst = os.path.join(full_path, new_name)
|
||||||
|
os.rename(src, dst)
|
||||||
|
return jsonify({'success': True})
|
||||||
|
elif action == 'move':
|
||||||
|
src = os.path.join(full_path, name)
|
||||||
|
dst = os.path.join(MEDIA_ROOT, target_path.lstrip('/'), name)
|
||||||
|
os.rename(src, dst)
|
||||||
|
return jsonify({'success': True})
|
||||||
|
elif action == 'create':
|
||||||
|
os.makedirs(os.path.join(full_path, name), exist_ok=True)
|
||||||
|
return jsonify({'success': True})
|
||||||
|
else:
|
||||||
|
return jsonify({'error': 'Unknown action'}), 400
|
||||||
|
|
||||||
|
# --- FileManager: Upload ---
|
||||||
|
|
||||||
|
|
||||||
|
@eventmedia_bp.route('/filemanager/upload', methods=['POST'])
|
||||||
|
def filemanager_upload():
|
||||||
|
path = request.args.get('path', '/')
|
||||||
|
upload_path = os.path.join(MEDIA_ROOT, path.lstrip('/'))
|
||||||
|
os.makedirs(upload_path, exist_ok=True)
|
||||||
|
for file in request.files.getlist('uploadFiles'):
|
||||||
|
file.save(os.path.join(upload_path, file.filename))
|
||||||
|
return jsonify({'success': True})
|
||||||
|
|
||||||
|
# --- FileManager: Download ---
|
||||||
|
|
||||||
|
|
||||||
|
@eventmedia_bp.route('/filemanager/download', methods=['GET'])
|
||||||
|
def filemanager_download():
|
||||||
|
path = request.args.get('path', '/')
|
||||||
|
names = request.args.getlist('names[]')
|
||||||
|
# Nur Einzel-Download für Beispiel
|
||||||
|
if names:
|
||||||
|
file_path = os.path.join(MEDIA_ROOT, path.lstrip('/'), names[0])
|
||||||
|
return send_from_directory(os.path.dirname(file_path), os.path.basename(file_path), as_attachment=True)
|
||||||
|
return jsonify({'error': 'No file specified'}), 400
|
||||||
|
|
||||||
|
# --- FileManager: Get Image (optional, für Thumbnails) ---
|
||||||
|
|
||||||
|
|
||||||
|
@eventmedia_bp.route('/filemanager/get-image', methods=['GET'])
|
||||||
|
def filemanager_get_image():
|
||||||
|
path = request.args.get('path', '/')
|
||||||
|
file_path = os.path.join(MEDIA_ROOT, path.lstrip('/'))
|
||||||
|
return send_from_directory(os.path.dirname(file_path), os.path.basename(file_path))
|
||||||
|
|
||||||
|
# --- EventMedia-API: Metadaten-Liste (wie gehabt) ---
|
||||||
|
|
||||||
|
|
||||||
|
@eventmedia_bp.route('', methods=['GET'])
|
||||||
|
def list_media():
|
||||||
|
session = Session()
|
||||||
|
media = session.query(EventMedia).all()
|
||||||
|
return jsonify([m.to_dict() for m in media])
|
||||||
|
|
||||||
|
# --- EventMedia-API: Metadaten-Update ---
|
||||||
|
|
||||||
|
|
||||||
|
@eventmedia_bp.route('/<int:media_id>', methods=['PUT'])
|
||||||
|
def update_media(media_id):
|
||||||
|
session = Session()
|
||||||
|
media = session.query(EventMedia).get(media_id)
|
||||||
|
if not media:
|
||||||
|
return jsonify({'error': 'Not found'}), 404
|
||||||
|
data = request.json
|
||||||
|
media.url = data.get('title', media.url)
|
||||||
|
media.message_content = data.get('description', media.message_content)
|
||||||
|
# Event-Zuordnung ggf. ergänzen
|
||||||
|
session.commit()
|
||||||
|
return jsonify(media.to_dict())
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
# server/wsgi.py
|
# server/wsgi.py
|
||||||
|
from routes.eventmedia import eventmedia_bp
|
||||||
|
from routes.events import events_bp
|
||||||
|
from routes.groups import groups_bp
|
||||||
|
from routes.clients import clients_bp
|
||||||
|
from database import Session, engine
|
||||||
|
from flask import Flask, jsonify, send_from_directory, request
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
sys.path.append('/workspace')
|
sys.path.append('/workspace')
|
||||||
from flask import Flask, jsonify, send_from_directory, request
|
|
||||||
from database import Session, engine
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
# Blueprints importieren und registrieren
|
# Blueprints importieren und registrieren
|
||||||
from routes.clients import clients_bp
|
|
||||||
from routes.groups import groups_bp
|
|
||||||
from routes.events import events_bp
|
|
||||||
|
|
||||||
app.register_blueprint(clients_bp)
|
app.register_blueprint(clients_bp)
|
||||||
app.register_blueprint(groups_bp)
|
app.register_blueprint(groups_bp)
|
||||||
app.register_blueprint(events_bp)
|
app.register_blueprint(events_bp)
|
||||||
|
app.register_blueprint(eventmedia_bp)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/health")
|
@app.route("/health")
|
||||||
|
|||||||
Reference in New Issue
Block a user