diff --git a/controllers/fileController.js b/controllers/fileController.js
new file mode 100644
index 0000000..aecd48d
--- /dev/null
+++ b/controllers/fileController.js
@@ -0,0 +1,144 @@
+const fs = require('fs');
+const path = require('path');
+const { File, Page } = require('../models');
+const { v4: uuidv4 } = require('uuid');
+
+const uploadDir = path.join(__dirname, '..', 'files', 'uploads');
+
+const uploadFile = (req, res, next) => {
+ if (!req.file) {
+ return res.status(400).send('No file uploaded.');
+ }
+ if (!fs.existsSync(uploadDir)) {
+ fs.mkdirSync(uploadDir, { recursive: true });
+ }
+ const file = req.file;
+ const hash = uuidv4();
+ const filePath = path.join(uploadDir, hash + path.extname(file.originalname));
+ try {
+ fs.writeFileSync(filePath, file.buffer);
+ req.fileData = {
+ hash: hash,
+ title: req.body.title || file.originalname,
+ originalName: file.originalname,
+ path: filePath
+ };
+ next();
+ } catch (error) {
+ return res.status(500).send('File upload failed.');
+ }
+};
+
+const saveFileDetails = async (req, res) => {
+ try {
+ const { hash, title, originalName, path } = req.fileData;
+ const newFile = await File.create({
+ hash,
+ title,
+ originalName,
+ path
+ });
+ res.status(201).json(newFile);
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({ error: 'Failed to save file details.' });
+ }
+};
+
+const getFiles = async (req, res) => {
+ try {
+ const files = await File.findAll();
+ res.status(200).json(files);
+ } catch (error) {
+ res.status(500).json({ error: 'Failed to fetch files.' });
+ }
+};
+
+const getFilesByPage = async (req, res) => {
+ try {
+ const { pageId } = req.params;
+ const page = await Page.findByPk(pageId, {
+ include: [File]
+ });
+ if (!page) {
+ return res.status(404).json({ error: 'Page not found.' });
+ }
+ res.status(200).json(page.Files);
+ } catch (error) {
+ res.status(500).json({ error: 'Failed to fetch files for the page.' });
+ }
+};
+
+const getFileById = async (req, res) => {
+ try {
+ const { id } = req.params;
+ const file = await File.findByPk(id);
+ if (!file) {
+ return res.status(404).json({ error: 'File not found.' });
+ }
+ res.status(200).json(file);
+ } catch (error) {
+ res.status(500).json({ error: 'Failed to fetch file.' });
+ }
+};
+
+const getFileByHash = async (req, res) => {
+ try {
+ const { hash } = req.params;
+ const file = await File.findOne({ where: { hash } });
+ if (!file) {
+ return res.status(404).json({ error: 'File not found.' });
+ }
+ res.status(200).json(file);
+ } catch (error) {
+ res.status(500).json({ error: 'Failed to fetch file.' });
+ }
+};
+
+const downloadFile = async (req, res) => {
+ try {
+ const { hash } = req.params;
+ const file = await File.findOne({ where: { hash } });
+ if (!file) {
+ return res.status(404).json({ error: 'File not found.' });
+ }
+ const filePath = path.join(__dirname, '..', 'files', 'uploads', hash + path.extname(file.originalName));
+ res.download(filePath, file.originalName, (err) => {
+ if (err) {
+ res.status(500).send({
+ message: "Could not download the file. " + err,
+ });
+ }
+ });
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({ error: 'Failed to download file.' });
+ }
+};
+
+const updateFile = async (req, res) => {
+ try {
+ const { id } = req.params;
+ const { title } = req.body;
+ const file = await File.findByPk(id);
+ if (!file) {
+ return res.status(404).json({ error: 'File not found.' });
+ }
+ file.title = title || file.title;
+ await file.save();
+ res.status(200).json(file);
+ } catch (error) {
+ res.status500.json({ error: 'Failed to update file.' });
+ }
+};
+
+module.exports = {
+ uploadFile,
+ saveFileDetails,
+ getFiles,
+ getFilesByPage,
+ getFileById,
+ getFileByHash,
+ downloadFile,
+ updateFile,
+};
diff --git a/files/uploads/1e221880-7626-406d-bed6-01692eb71e48.pdf b/files/uploads/1e221880-7626-406d-bed6-01692eb71e48.pdf
new file mode 100644
index 0000000..986e880
Binary files /dev/null and b/files/uploads/1e221880-7626-406d-bed6-01692eb71e48.pdf differ
diff --git a/files/uploads/30e723d1-bde0-4459-8d0e-06b820f1ccab.pdf b/files/uploads/30e723d1-bde0-4459-8d0e-06b820f1ccab.pdf
new file mode 100644
index 0000000..986e880
Binary files /dev/null and b/files/uploads/30e723d1-bde0-4459-8d0e-06b820f1ccab.pdf differ
diff --git a/files/uploads/c6f81d13-955b-4ab5-bee8-ca260dfad1c5.pdf b/files/uploads/c6f81d13-955b-4ab5-bee8-ca260dfad1c5.pdf
new file mode 100644
index 0000000..986e880
Binary files /dev/null and b/files/uploads/c6f81d13-955b-4ab5-bee8-ca260dfad1c5.pdf differ
diff --git a/migrations/20240621071724-create-files-table.js b/migrations/20240621071724-create-files-table.js
new file mode 100644
index 0000000..f90e375
--- /dev/null
+++ b/migrations/20240621071724-create-files-table.js
@@ -0,0 +1,39 @@
+'use strict';
+
+module.exports = {
+ up: async (queryInterface, Sequelize) => {
+ await queryInterface.createTable('files', {
+ id: {
+ allowNull: false,
+ autoIncrement: true,
+ primaryKey: true,
+ type: Sequelize.INTEGER
+ },
+ hash: {
+ type: Sequelize.STRING,
+ unique: true,
+ allowNull: false
+ },
+ title: {
+ type: Sequelize.STRING,
+ allowNull: false
+ },
+ originalName: {
+ type: Sequelize.STRING,
+ allowNull: false
+ },
+ createdAt: {
+ allowNull: false,
+ type: Sequelize.DATE
+ },
+ updatedAt: {
+ allowNull: false,
+ type: Sequelize.DATE
+ }
+ });
+ },
+
+ down: async (queryInterface, Sequelize) => {
+ await queryInterface.dropTable('files');
+ }
+};
diff --git a/migrations/20240621071827-create-page-files-table.js b/migrations/20240621071827-create-page-files-table.js
new file mode 100644
index 0000000..b7e062b
--- /dev/null
+++ b/migrations/20240621071827-create-page-files-table.js
@@ -0,0 +1,38 @@
+'use strict';
+
+module.exports = {
+ up: async (queryInterface, Sequelize) => {
+ await queryInterface.createTable('page_files', {
+ pageId: {
+ type: Sequelize.INTEGER,
+ references: {
+ model: 'pages',
+ key: 'id'
+ },
+ onUpdate: 'CASCADE',
+ onDelete: 'CASCADE'
+ },
+ fileId: {
+ type: Sequelize.INTEGER,
+ references: {
+ model: 'files',
+ key: 'id'
+ },
+ onUpdate: 'CASCADE',
+ onDelete: 'CASCADE'
+ },
+ createdAt: {
+ allowNull: false,
+ type: Sequelize.DATE
+ },
+ updatedAt: {
+ allowNull: false,
+ type: Sequelize.DATE
+ }
+ });
+ },
+
+ down: async (queryInterface, Sequelize) => {
+ await queryInterface.dropTable('page_files');
+ }
+};
diff --git a/migrations/20240621073053-alter-files-table.js b/migrations/20240621073053-alter-files-table.js
new file mode 100644
index 0000000..79c2714
--- /dev/null
+++ b/migrations/20240621073053-alter-files-table.js
@@ -0,0 +1,14 @@
+'use strict';
+
+module.exports = {
+ up: async (queryInterface, Sequelize) => {
+ await queryInterface.addColumn('Files', 'originalName', {
+ type: Sequelize.STRING,
+ allowNull: false,
+ after: 'title' // optional: to place the column after 'title'
+ });
+ },
+ down: async (queryInterface, Sequelize) => {
+ await queryInterface.removeColumn('Files', 'originalName');
+ }
+};
diff --git a/models/File.js b/models/File.js
new file mode 100644
index 0000000..cd42faa
--- /dev/null
+++ b/models/File.js
@@ -0,0 +1,28 @@
+const { DataTypes } = require('sequelize');
+
+module.exports = (sequelize) => {
+ const File = sequelize.define('File', {
+ hash: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ unique: true
+ },
+ title: {
+ type: DataTypes.STRING,
+ allowNull: false
+ },
+ originalName: {
+ type: DataTypes.STRING,
+ allowNull: false
+ }
+ }, {
+ tableName: 'files',
+ timestamps: true
+ });
+
+ File.associate = (models) => {
+ File.belongsToMany(models.Page, { through: 'PageFiles' });
+ };
+
+ return File;
+};
diff --git a/models/Page.js b/models/Page.js
index 918016d..dcad30e 100644
--- a/models/Page.js
+++ b/models/Page.js
@@ -21,5 +21,9 @@ module.exports = (sequelize) => {
timestamps: true
});
+ Page.associate = (models) => {
+ Page.belongsToMany(models.File, { through: 'page_files', foreignKey: 'pageId' });
+ };
+
return Page;
};
diff --git a/package-lock.json b/package-lock.json
index 445d541..a272345 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -34,6 +34,7 @@
"date-fns": "^3.6.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
+ "file-saver": "^2.0.5",
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1",
"multer": "^1.4.5-lts.1",
@@ -7379,6 +7380,11 @@
"node": "^10.12.0 || >=12.0.0"
}
},
+ "node_modules/file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
diff --git a/package.json b/package.json
index 1407370..7a40bb1 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"date-fns": "^3.6.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
+ "file-saver": "^2.0.5",
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1",
"multer": "^1.4.5-lts.1",
diff --git a/routes/files.js b/routes/files.js
new file mode 100644
index 0000000..702cefe
--- /dev/null
+++ b/routes/files.js
@@ -0,0 +1,17 @@
+const express = require('express');
+const router = express.Router();
+const multer = require('multer');
+const upload = multer({ storage: multer.memoryStorage() });
+
+const { uploadFile, saveFileDetails, getFiles, getFilesByPage, getFileById, getFileByHash, downloadFile, updateFile } = require('../controllers/fileController');
+const authMiddleware = require('../middleware/authMiddleware');
+
+router.post('/', authMiddleware, upload.single('file'), uploadFile, saveFileDetails);
+router.get('/', authMiddleware, getFiles);
+router.get('/page/:pageId', getFilesByPage);
+router.get('/:id', getFileById);
+router.get('/hash/:hash', getFileByHash);
+router.get('/download/:hash', downloadFile);
+router.put('/:id', authMiddleware, updateFile);
+
+module.exports = router;
diff --git a/server.js b/server.js
index 1fbfd5f..00c6ed4 100644
--- a/server.js
+++ b/server.js
@@ -2,18 +2,19 @@ const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const sequelize = require('./config/database');
-const authRoutes = require('./routes/auth');
+const authRouter = require('./routes/auth');
const eventTypesRouter = require('./routes/eventtypes');
const eventPlacesRouter = require('./routes/eventPlaces');
const contactPersonsRouter = require('./routes/contactPerson');
const positionsRouter = require('./routes/positions');
-const institutionRoutes = require('./routes/institutions');
+const institutionRouter = require('./routes/institutions');
const eventRouter = require('./routes/event');
const menuDataRouter = require('./routes/menuData');
const worshipRouter = require('./routes/worships');
-const pageRoutes = require('./routes/pages');
-const userRoutes = require('./routes/users');
-const imageRoutes = require('./routes/image');
+const pageRouter = require('./routes/pages');
+const userRouter = require('./routes/users');
+const imageRouter = require('./routes/image');
+const filesRouter = require('./routes/files');
const app = express();
const PORT = 3000;
@@ -21,18 +22,19 @@ const PORT = 3000;
app.use(cors());
app.use(bodyParser.json());
-app.use('/api/auth', authRoutes);
+app.use('/api/auth', authRouter);
app.use('/api/event-types', eventTypesRouter);
app.use('/api/event-places', eventPlacesRouter);
app.use('/api/contact-persons', contactPersonsRouter);
app.use('/api/positions', positionsRouter);
-app.use('/api/institutions', institutionRoutes);
+app.use('/api/institutions', institutionRouter);
app.use('/api/events', eventRouter);
app.use('/api/menu-data', menuDataRouter);
app.use('/api/worships', worshipRouter);
-app.use('/api/page-content', pageRoutes);
-app.use('/api/users', userRoutes);
-app.use('/api/image', imageRoutes);
+app.use('/api/page-content', pageRouter);
+app.use('/api/users', userRouter);
+app.use('/api/image', imageRouter);
+app.use('/api/files', filesRouter);
sequelize.sync().then(() => {
app.listen(PORT, () => {
console.log(`Server läuft auf Port ${PORT}`);
diff --git a/src/components/AddDownloadDialog.vue b/src/components/AddDownloadDialog.vue
new file mode 100644
index 0000000..f307206
--- /dev/null
+++ b/src/components/AddDownloadDialog.vue
@@ -0,0 +1,98 @@
+
+