Erster Aufbau Forum

This commit is contained in:
Torsten Schulz
2024-10-15 16:28:42 +02:00
parent c31be3f879
commit 663564aa96
163 changed files with 9449 additions and 116 deletions

View File

@@ -9,6 +9,7 @@ import adminRouter from './routers/adminRouter.js';
import contactRouter from './routers/contactRouter.js';
import cors from 'cors';
import socialnetworkRouter from './routers/socialnetworkRouter.js';
import forumRouter from './routers/forumRouter.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -33,6 +34,7 @@ app.use('/api/admin', adminRouter);
app.use('/images', express.static(path.join(__dirname, '../frontend/public/images')));
app.use('/api/contact', contactRouter);
app.use('/api/socialnetwork', socialnetworkRouter);
app.use('/api/forum', forumRouter);
app.use((req, res) => {
res.status(404).send('404 Not Found');

View File

@@ -0,0 +1,64 @@
import forumService from '../services/forumService.js';
const forumController = {
async createForum(req, res) {
try {
const { userid: userId } = req.headers;
const { name, permissions } = req.body;
const forum = await forumService.createForum(userId, name, permissions);
res.status(201).json(forum);
} catch (error) {
console.error('Error in createForum:', error);
res.status(400).json({ error: error.message });
}
},
async deleteForum(req, res) {
try {
const { userid: userId } = req.headers;
const { forumId } = req.params;
await forumService.deleteForum(userId, forumId);
res.status(200).json({ message: 'Forum deleted successfully' });
} catch (error) {
console.error('Error in deleteForum:', error);
res.status(400).json({ error: error.message });
}
},
async getAllForums(req, res) {
try {
const { userid: userId } = req.headers;
const forums = await forumService.getAllForums(userId);
res.status(200).json(forums);
} catch (error) {
console.error('Error in getAllForums:', error);
res.status(400).json({ error: error.message });
}
},
async getForum(req, res) {
try {
const { userid: userId } = req.headers;
const { forumId, page } = req.params;
const forum = await forumService.getForum(userId, forumId, page);
res.status(200).json(forum);
} catch (error) {
console.error('Error in getForum:', error);
res.status(400).json({ error: error.message });
}
},
async createTopic(req, res) {
try {
const { userid: userId } = req.headers;
const { forumId, title, content } = req.body;
const result = await forumService.createTopic(userId, forumId, title, content);
res.status(201).json(result);
} catch (error) {
console.error('Error in createTopic:', error);
res.status(400).json({ error: error.message });
}
}
};
export default forumController;

View File

@@ -254,9 +254,9 @@ class SocialNetworkController {
async updateDiaryEntry(req, res) {
try {
const { diaryId } = req.params;
const { diaryEntryId } = req.params;
const { userId, text } = req.body;
const updatedEntry = await this.socialNetworkService.updateDiaryEntry(diaryId, userId, text);
const updatedEntry = await this.socialNetworkService.updateDiaryEntry(diaryEntryId, userId, text);
res.status(200).json(updatedEntry);
} catch (error) {
console.error('Error in updateDiaryEntry:', error);
@@ -266,9 +266,9 @@ class SocialNetworkController {
async deleteDiaryEntry(req, res) {
try {
const { diaryId } = req.params;
const { entryId } = req.params;
const { userId } = req.body;
const result = await this.socialNetworkService.deleteDiaryEntry(diaryId, userId);
const result = await this.socialNetworkService.deleteDiaryEntry(entryId, userId);
res.status(200).json({ message: 'Entry deleted successfully', result });
} catch (error) {
console.error('Error in deleteDiaryEntry:', error);
@@ -278,8 +278,9 @@ class SocialNetworkController {
async getDiaryEntries(req, res) {
try {
const { userId } = req.params;
const entries = await this.socialNetworkService.getDiaryEntries(userId);
const { userid: userId} = req.headers;
const { page } = req.params;
const entries = await this.socialNetworkService.getDiaryEntries(userId, page);
res.status(200).json(entries);
} catch (error) {
console.error('Error in getDiaryEntries:', error);

View File

@@ -18,8 +18,18 @@ import FolderImageVisibility from './community/folder_image_visibility.js';
import ImageImageVisibility from './community/image_image_visibility.js';
import FolderVisibilityUser from './community/folder_visibility_user.js';
import GuestbookEntry from './community/guestbook.js';
import Forum from './forum/forum.js';
import Title from './forum/title.js';
import Message from './forum/message.js';
import MessageImage from './forum/message_image.js';
import MessageHistory from './forum/message_history.js';
import TitleHistory from './forum/title_history.js';
import ForumPermission from './forum/forum_permission.js';
import ForumUserPermission from './forum/forum_user_permission.js';
import ForumForumPermission from './forum/forum_forum_permission.js';
export default function setupAssociations() {
// UserParam related associations
SettingsType.hasMany(UserParamType, { foreignKey: 'settingsId', as: 'user_param_types' });
UserParamType.belongsTo(SettingsType, { foreignKey: 'settingsId', as: 'settings_type' });
@@ -29,13 +39,19 @@ export default function setupAssociations() {
User.hasMany(UserParam, { foreignKey: 'userId', as: 'user_params' });
UserParam.belongsTo(User, { foreignKey: 'userId', as: 'user' });
// UserRight related associations
UserRight.belongsTo(User, { foreignKey: 'userId', as: 'user_with_rights' });
UserRight.belongsTo(UserRightType, { foreignKey: 'rightTypeId', as: 'rightType' });
UserRightType.hasMany(UserRight, { foreignKey: 'rightTypeId', as: 'user_rights' });
UserParamType.hasMany(UserParamValue, { foreignKey: 'userParamTypeId', as: 'user_param_values' });
UserParamValue.belongsTo(UserParamType, { foreignKey: 'userParamTypeId', as: 'user_param_type' });
// UserParamVisibility related associations
UserParam.hasMany(UserParamVisibility, { foreignKey: 'param_id', as: 'param_visibilities' });
UserParamVisibility.belongsTo(UserParam, { foreignKey: 'param_id', as: 'param' });
UserParamVisibility.belongsTo(UserParamVisibilityType, { foreignKey: 'visibility', as: 'visibility_type' });
UserParamVisibilityType.hasMany(UserParamVisibility, { foreignKey: 'visibility', as: 'user_param_visibilities' });
// Interest related associations
InterestType.hasMany(InterestTranslationType, { foreignKey: 'interestsId', as: 'interest_translations' });
InterestTranslationType.belongsTo(InterestType, { foreignKey: 'interestsId', as: 'interest_translations' });
@@ -44,14 +60,7 @@ export default function setupAssociations() {
Interest.belongsTo(InterestType, { foreignKey: 'userinterestId', as: 'interest_type' });
Interest.belongsTo(User, { foreignKey: 'userId', as: 'interest_owner' });
InterestTranslationType.belongsTo(UserParamValue, { foreignKey: 'language', targetKey: 'id', as: 'user_param_value' });
UserParam.hasMany(UserParamVisibility, { foreignKey: 'param_id', as: 'param_visibilities' });
UserParamVisibility.belongsTo(UserParam, { foreignKey: 'param_id', as: 'param' });
UserParamVisibility.belongsTo(UserParamVisibilityType, { foreignKey: 'visibility', as: 'visibility_type' });
UserParamVisibilityType.hasMany(UserParamVisibility, { foreignKey: 'visibility', as: 'user_param_visibilities' });
// Folder and Image related associations
Folder.belongsTo(User, { foreignKey: 'userId' });
User.hasMany(Folder, { foreignKey: 'userId' });
@@ -62,8 +71,9 @@ export default function setupAssociations() {
Folder.hasMany(Image, { foreignKey: 'folderId' });
Image.belongsTo(User, { foreignKey: 'userId' });
User.hasMany(Image, { foreignKey: 'userId' });
User.hasMany(Image, { foreignKey: 'userId' });
// Image visibility associations
Folder.belongsToMany(ImageVisibilityType, {
through: FolderImageVisibility,
foreignKey: 'folderId',
@@ -97,24 +107,53 @@ export default function setupAssociations() {
otherKey: 'folderId'
});
User.hasMany(GuestbookEntry, {
foreignKey: 'recipientId',
as: 'receivedEntries'
// Guestbook related associations
User.hasMany(GuestbookEntry, { foreignKey: 'recipientId', as: 'receivedEntries' });
User.hasMany(GuestbookEntry, { foreignKey: 'senderId', as: 'sentEntries' });
GuestbookEntry.belongsTo(User, { foreignKey: 'recipientId', as: 'recipient' });
GuestbookEntry.belongsTo(User, { foreignKey: 'senderId', as: 'sender' });
// Forum related associations
Forum.hasMany(Title, { foreignKey: 'forumId' });
Title.belongsTo(Forum, { foreignKey: 'forumId' });
Title.belongsTo(User, { foreignKey: 'createdBy', as: 'createdByUser' });
User.hasMany(Title, { foreignKey: 'createdBy', as: 'titles' });
Title.hasMany(Message, { foreignKey: 'titleId', as: 'messages' });
Message.belongsTo(Title, { foreignKey: 'titleId', as: 'title' });
Message.belongsTo(User, { foreignKey: 'createdBy', as: 'lastMessageUser' });
User.hasMany(Message, { foreignKey: 'createdBy', as: 'userMessages' });
Message.hasMany(MessageImage, { foreignKey: 'messageId' });
MessageImage.belongsTo(Message, { foreignKey: 'messageId' });
Message.hasMany(MessageHistory, { foreignKey: 'messageId' });
MessageHistory.belongsTo(Message, { foreignKey: 'messageId' });
Title.hasMany(TitleHistory, { foreignKey: 'titleId' });
TitleHistory.belongsTo(Title, { foreignKey: 'titleId' });
// Forum permissions associations
Forum.hasMany(ForumUserPermission, { foreignKey: 'forumId', as: 'userPermissions' });
ForumUserPermission.belongsTo(Forum, { foreignKey: 'forumId' });
User.hasMany(ForumUserPermission, { foreignKey: 'userId', as: 'userPermissions' });
ForumUserPermission.belongsTo(User, { foreignKey: 'userId' });
Forum.belongsToMany(ForumPermission, {
through: ForumForumPermission,
foreignKey: 'forumId',
as: 'associatedPermissions'
});
User.hasMany(GuestbookEntry, {
foreignKey: 'senderId',
as: 'sentEntries'
});
GuestbookEntry.belongsTo(User, {
foreignKey: 'recipientId',
as: 'recipient'
});
GuestbookEntry.belongsTo(User, {
foreignKey: 'senderId',
as: 'sender'
ForumPermission.belongsToMany(Forum, {
through: ForumForumPermission,
foreignKey: 'permissionId',
as: 'forums'
});
ForumPermission.hasMany(ForumUserPermission, { foreignKey: 'permissionId' });
ForumUserPermission.belongsTo(ForumPermission, { foreignKey: 'permissionId' });
}

View File

@@ -26,6 +26,7 @@ Diary.init({
tableName: 'diary',
schema: 'community',
timestamps: true,
underscored: true,
});
export default Diary;

View File

@@ -15,7 +15,7 @@ DiaryHistory.init({
oldText: {
type: DataTypes.TEXT,
allowNull: false,
},
},
oldCreatedAt: {
type: DataTypes.DATE,
allowNull: false,
@@ -30,6 +30,7 @@ DiaryHistory.init({
tableName: 'diary_history',
schema: 'community',
timestamps: false,
underscored: true,
});
export default DiaryHistory;

View File

@@ -0,0 +1,15 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const Forum = sequelize.define('forum', {
name: {
type: DataTypes.STRING,
allowNull: false
}
}, {
tableName: 'forum',
schema: 'forum',
underscored: true
});
export default Forum;

View File

@@ -0,0 +1,27 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const ForumForumPermission = sequelize.define('forum_forum_permission', {
forumId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'forum',
key: 'id'
}
},
permissionId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'forum_permission',
key: 'id'
}
}
}, {
tableName: 'forum_forum_permission',
schema: 'forum',
underscored: true
});
export default ForumForumPermission;

View File

@@ -0,0 +1,22 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const ForumPermission = sequelize.define('forum_permission', {
name: {
type: DataTypes.STRING,
allowNull: false,
},
value: {
type: DataTypes.STRING,
allowNull: true,
},
}, {
sequelize,
modelName: 'ForumPermission',
tableName: 'forum_permission',
schema: 'forum',
timestamps: false,
underscored: true
});
export default ForumPermission;

View File

@@ -0,0 +1,23 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const ForumUserPermission = sequelize.define('forum_user_permission', {
userId: {
type: DataTypes.INTEGER,
allowNull: true
},
permissionId: {
type: DataTypes.INTEGER,
allowNull: false
},
forumId: {
type: DataTypes.INTEGER,
allowNull: false
},
}, {
tableName: 'forum_user_permission',
schema: 'forum',
underscored: true
});
export default ForumUserPermission;

View File

@@ -0,0 +1,24 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const Message = sequelize.define('message', {
text: {
type: DataTypes.TEXT,
allowNull: false
},
createdBy: {
type: DataTypes.INTEGER,
allowNull: false
},
titleId: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
tableName: 'message',
schema: 'forum',
underscored: true,
timestamps: true
});
export default Message;

View File

@@ -0,0 +1,28 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const MessageHistory = sequelize.define('message_history', {
messageId: {
type: DataTypes.INTEGER,
allowNull: false
},
oldText: {
type: DataTypes.TEXT,
allowNull: false
},
changedBy: {
type: DataTypes.INTEGER,
allowNull: false
},
oldUpdatedAt: {
type: DataTypes.DATE,
allowNull: false
}
}, {
tableName: 'message_history',
schema: 'forum',
underscored: true,
timestamps: false
});
export default MessageHistory;

View File

@@ -0,0 +1,20 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const MessageImage = sequelize.define('message_image', {
messageId: {
type: DataTypes.INTEGER,
allowNull: false
},
fileName: {
type: DataTypes.STRING,
allowNull: false
}
}, {
tableName: 'message_image',
schema: 'forum',
underscored: true,
timestamps: false
});
export default MessageImage;

View File

@@ -0,0 +1,24 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const Title = sequelize.define('title', {
title: {
type: DataTypes.STRING,
allowNull: false
},
createdBy: {
type: DataTypes.INTEGER,
allowNull: false
},
forumId: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
tableName: 'title',
schema: 'forum',
underscored: true,
timestamps: true,
});
export default Title;

View File

@@ -0,0 +1,28 @@
import { sequelize } from '../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const TitleHistory = sequelize.define('title_history', {
titleId: {
type: DataTypes.INTEGER,
allowNull: false
},
oldTitle: {
type: DataTypes.STRING,
allowNull: false
},
changedBy: {
type: DataTypes.INTEGER,
allowNull: false
},
oldUpdatedAt: {
type: DataTypes.DATE,
allowNull: false
}
}, {
tableName: 'title_history',
schema: 'forum',
underscored: true,
timestamps: false
});
export default TitleHistory;

View File

@@ -22,6 +22,15 @@ import FolderVisibilityUser from './community/folder_visibility_user.js';
import GuestbookEntry from './community/guestbook.js';
import DiaryHistory from './community/diary_history.js';
import Diary from './community/diary.js';
import Forum from './forum/forum.js';
import ForumPermission from './forum/forum_permission.js';
import ForumUserPermission from './forum/forum_user_permission.js';
import Title from './forum/title.js';
import TitleHistory from './forum/title_history.js';
import Message from './forum/message.js';
import MessageHistory from './forum/message_history.js';
import MessageImage from './forum/message_image.js';
import ForumForumPermission from './forum/forum_forum_permission.js';
const models = {
SettingsType,
@@ -48,6 +57,15 @@ const models = {
GuestbookEntry,
DiaryHistory,
Diary,
Forum,
ForumPermission,
ForumForumPermission,
ForumUserPermission,
Title,
TitleHistory,
Message,
MessageHistory,
MessageImage,
};
export default models;

View File

@@ -16,6 +16,12 @@ export async function createTriggers() {
SELECT id FROM type.user_param_visibility WHERE description = 'Invisible'
));
END IF;
-- If NEW is null, then we have nothing to do
IF NEW IS NULL THEN
RETURN NULL;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
@@ -25,6 +31,7 @@ export async function createTriggers() {
CREATE OR REPLACE TRIGGER trigger_create_user_param_visibility
AFTER INSERT ON community.user_param
FOR EACH ROW
WHEN (NEW.id IS NOT NULL)
EXECUTE FUNCTION create_user_param_visibility_trigger();
`;
@@ -32,6 +39,7 @@ export async function createTriggers() {
CREATE OR REPLACE TRIGGER trigger_update_user_param_visibility
AFTER UPDATE ON community.user_param
FOR EACH ROW
WHEN (NEW.id IS NOT NULL)
EXECUTE FUNCTION create_user_param_visibility_trigger();
`;
@@ -39,8 +47,14 @@ export async function createTriggers() {
CREATE OR REPLACE FUNCTION insert_diary_history()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO community.diary_history (diaryId, userId, oldText, oldCreatedAt, oldUpdatedAt)
VALUES (OLD.id, OLD.userId, OLD.text, OLD.createdAt, OLD.updatedAt);
INSERT INTO community.diary_history (diary_id, user_id, old_text, old_created_at, old_updated_at)
VALUES (OLD.id, OLD.user_id, OLD.text, OLD.created_at, OLD.updated_at);
-- If NEW is null, then we have nothing to do
IF NEW IS NULL THEN
RETURN NULL;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
@@ -50,18 +64,41 @@ export async function createTriggers() {
CREATE OR REPLACE TRIGGER diary_update_trigger
BEFORE UPDATE ON community.diary
FOR EACH ROW
WHEN (OLD.id IS NOT NULL)
EXECUTE FUNCTION insert_diary_history();
`;
const createTitleHistoryTriggerFunction = `
CREATE OR REPLACE FUNCTION insert_title_history()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO forum.title_history (title_id, old_title, changed_by, old_updated_at)
VALUES (OLD.id, OLD.title, OLD.created_by, OLD.updated_at);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`;
const createTitleHistoryTrigger = `
CREATE OR REPLACE TRIGGER title_update_trigger
BEFORE UPDATE ON forum.title
FOR EACH ROW
WHEN (OLD.id IS NOT NULL)
EXECUTE FUNCTION insert_title_history();
`;
try {
await sequelize.query(createTriggerFunction);
await sequelize.query(createInsertTrigger);
await sequelize.query(createUpdateTrigger);
await sequelize.query(createDiaryHistoryTriggerFunction);
await sequelize.query(createDiaryHistoryTrigger);
await sequelize.query(createTitleHistoryTriggerFunction);
await sequelize.query(createTitleHistoryTrigger);
console.log('Triggers created successfully');
} catch (error) {
console.error('Error creating triggers:', error);
}
}

View File

@@ -0,0 +1,15 @@
import { Router } from 'express';
import forumController from '../controllers/forumController.js';
import { authenticate } from '../middleware/authMiddleware.js';
const forumRouter = Router();
forumRouter.use(authenticate);
forumRouter.post('/topic', forumController.createTopic);
forumRouter.post('/', forumController.createForum);
forumRouter.delete('/:forumId', forumController.deleteForum);
forumRouter.get('/:forumId/:page', forumController.getForum);
forumRouter.get('/', forumController.getAllForums);
export default forumRouter;

View File

@@ -24,8 +24,8 @@ router.get('/guestbook/entries/:username/:page', authenticate, socialNetworkCont
router.delete('/guestbook/entries/:entryId', authenticate, socialNetworkController.deleteGuestbookEntry);
router.get('/guestbook/image/:guestbookUserName/:entryId', authenticate, socialNetworkController.getGuestbookImage);
router.post('/diary', authenticate, socialNetworkController.createDiaryEntry);
router.put('/diary/:diaryId', authenticate, socialNetworkController.updateDiaryEntry);
router.delete('/diary/:diaryId', authenticate, socialNetworkController.deleteDiaryEntry);
router.get('/diary/:userId', authenticate, socialNetworkController.getDiaryEntries);
router.put('/diary/:diaryEntryId', authenticate, socialNetworkController.updateDiaryEntry);
router.delete('/diary/:entryId', authenticate, socialNetworkController.deleteDiaryEntry);
router.get('/diary/:page', authenticate, socialNetworkController.getDiaryEntries);
export default router;

View File

@@ -4,6 +4,8 @@ import UserParamType from '../models/type/user_param.js';
import UserParamVisibility from '../models/community/user_param_visibility.js';
import UserParamVisibilityType from '../models/type/user_param_visibility.js';
import { Op } from 'sequelize';
import UserRight from '../models/community/user_right.js';
import UserRightType from '../models/type/user_right.js';
class BaseService {
async getUserByHashedId(hashedId) {
@@ -63,6 +65,33 @@ class BaseService {
return age >= 18;
}
async checkUserAccess(hashedId) {
const user = await this.getUserByHashedId(hashedId);
if (!user || !user.active) throw new Error('Access denied: User not found or inactive');
return user.id;
}
async getUserRights(userId) {
const userRights = await UserRight.findAll({
where: { userId },
include: [
{
model: UserRightType,
as: 'rightType',
attributes: ['title']
}
]
});
return userRights.map((right) => right.rightType.title);
}
async hasUserRight(userId, requiredRights) {
console.log(requiredRights);
const userRights = await this.getUserRights(userId);
return userRights.some((right) => requiredRights.includes(right));
}
}
export default BaseService;

View File

@@ -0,0 +1,280 @@
import BaseService from './BaseService.js';
import Forum from '../models/forum/forum.js';
import ForumPermission from '../models/forum/forum_permission.js';
import ForumUserPermission from '../models/forum/forum_user_permission.js';
import UserRight from '../models/community/user_right.js';
import UserRightType from '../models/type/user_right.js';
import { Op } from 'sequelize';
import { sequelize } from '../utils/sequelize.js';
import User from '../models/community/user.js';
import ForumForumPermission from '../models/forum/forum_forum_permission.js';
import Title from '../models/forum/title.js';
import Message from '../models/forum/message.js';
class ForumService extends BaseService {
async createForum(hashedUserId, name, permissions) {
const user = await this.getUserByHashedId(hashedUserId);
const isAdmin = await this.hasUserRight(user.id, ['mainadmin', 'forum']);
if (!isAdmin) {
throw new Error('Access denied: Only admins can create forums.');
}
const newForum = await Forum.create({ name });
if (permissions && permissions.length > 0) {
for (const permission of permissions) {
const forumPermission = await ForumPermission.findOne({
where: { name: permission.value }
});
if (forumPermission) {
await newForum.addAssociatedPermissions(forumPermission);
}
}
}
return newForum;
}
async deleteForum(hashedUserId, forumId) {
const user = await this.getUserByHashedId(hashedUserId);
const isAdmin = await this.hasUserRight(user.id, ['mainadmin', 'forum']);
if (!isAdmin) {
throw new Error('Access denied: Only admins can delete forums.');
}
const forum = await Forum.findByPk(forumId);
if (!forum) {
throw new Error('Forum not found.');
}
const transaction = await sequelize.transaction();
try {
const forumFormRights = await ForumForumPermission.findAll({
where: { forumId: forumId }
});
for (const forumFormRight of forumFormRights) {
await forumFormRight.destroy({ transaction });
}
const forumUserRights = await ForumUserPermission.findAll({
where: { forumId: forumId }
});
for (const forumUserRight of forumUserRights) {
await forumUserRight.destroy({ transaction });
}
await forum.destroy({ transaction });
await transaction.commit();
return forum;
} catch (error) {
await transaction.rollback();
throw new Error(`Error deleting forum: ${error.message}`);
}
}
async getAllForums(hashedUserId) {
const user = await this.getUserByHashedId(hashedUserId);
if (!user) throw new Error('User not found.');
const forums = await Forum.findAll({
include: [
{
model: ForumPermission,
as: 'associatedPermissions',
},
{
model: ForumUserPermission,
as: 'userPermissions',
}
]
});
const accessibleForums = [];
for (const forum of forums) {
const hasAccess = await this.checkForumAccess(forum, user);
if (hasAccess) {
accessibleForums.push(forum);
}
}
return accessibleForums;
}
async getForum(hashedUserId, forumId, page) {
const user = await this.getUserByHashedId(hashedUserId);
if (!user) throw new Error('User not found.');
const forum = await Forum.findByPk(forumId, {
include: [
{
model: ForumPermission,
as: 'associatedPermissions',
},
{
model: ForumUserPermission,
as: 'userPermissions',
}
]
});
const hasAccess = await this.checkForumAccess(forum, user);
if (!hasAccess) {
throw new Error('Access denied.');
}
const titlesRaw = await Title.findAll({
where: {
forumId
},
attributes: [
'id',
'title',
'createdBy',
'forumId',
'createdAt',
'updatedAt',
[sequelize.literal(`(SELECT COUNT(*) FROM forum.message WHERE message.title_id = title.id)`), 'messageCount'],
[sequelize.literal(`(SELECT MAX("created_at") FROM forum.message WHERE message.title_id = title.id)`), 'lastMessageDate']
],
include: [
{
model: User,
as: 'createdByUser',
attributes: ['id', 'username', 'hashedId']
},
{
model: Message,
as: 'messages',
attributes: [],
include: [
{
model: User,
as: 'lastMessageUser',
attributes: ['id', 'username']
}
]
}
],
offset: (page - 1) * 25,
limit: 25,
order: [
['createdAt', 'DESC']
],
group: [
'title.id',
'title.created_at',
]
});
const totalTopics = await Title.count({ where: { forumId } });
const titles = titlesRaw.map(title => ({
id: title.dataValues.id,
title: title.dataValues.title,
createdBy: title.createdByUser.username,
createdByHash: title.createdByUser.hashedId,
createdAt: title.dataValues.createdAt,
numberOfItems: title.dataValues.messageCount,
lastMessageDate: title.dataValues.lastMessageDate
}));
return {
name: forum.name,
titles,
page,
totalTopics,
};
}
async createTopic(hashedUserId, forumId, title, content) {
const user = await this.getUserByHashedId(hashedUserId);
if (!user) throw new Error('User not found.');
const forum = await Forum.findByPk(forumId, {
include: [
{
model: ForumPermission,
as: 'associatedPermissions',
},
{
model: ForumUserPermission,
as: 'userPermissions',
}
]
});
const hasAccess = await this.checkForumAccess(forum, user);
if (!hasAccess) {
throw new Error('Access denied.');
}
const newTopic = await Title.create({ title, forumId, createdBy: user.id });
await Message.create({ titleId: newTopic.id, text: content, createdBy: user.id})
return this.getForum(hashedUserId, forumId, 1);
}
async checkForumAccess(forum, user) {
console.log('[ForumService.checkForumAccess] - start');
const { age } = user;
console.log('[ForumService.checkForumAccess] - read user permissions');
const userPermission = await ForumUserPermission.findOne({
where: {
userId: user.id,
forumId: forum.id
}
});
if (userPermission) {
return true;
}
console.log('[ForumService.checkForumAccess] - read forum permissions');
const forumPermissions = await ForumForumPermission.findAll({
where: {
forumId: forum.id
}
});
console.log('[ForumService.checkForumAccess] - filter persmissions');
for (const permission of forumPermissions) {
if (permission.permission === 'age' && age >= parseInt(permission.value, 10)) {
return true;
}
if (permission.permission === 'all' || permission.permission === 'user') {
return true;
}
const userRight = await UserRight.findOne({
where: {
userId: user.id,
},
include: {
model: UserRightType,
as: 'rightType',
where: {
[Op.or]: [
{ title: 'mainadmin' },
{ title: 'forum' }
]
}
}
});
if (userRight) {
return true;
}
}
console.log('[ForumService.checkForumAccess] - not successful');
return false;
}
async setPermissions(forumId, permissions) {
if (permissions && Array.isArray(permissions)) {
for (const permission of permissions) {
await ForumPermission.create({
forumId,
permission: permission.type,
value: permission.value
});
}
}
}
async checkAdminRights(userId) {
const userRight = await UserRight.findOne({
where: { userId },
include: {
model: UserRightType,
as: 'rightType',
where: { title: ['mainadmin', 'forum'] }
}
});
if (!userRight) {
throw new Error('Unauthorized: Only admins can perform this action.');
}
}
}
export default new ForumService();

View File

@@ -22,6 +22,7 @@ import GuestbookEntry from '../models/community/guestbook.js';
import { JSDOM } from 'jsdom';
import DOMPurify from 'dompurify';
import sharp from 'sharp';
import Diary from '../models/community/diary.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -42,9 +43,7 @@ class SocialNetworkService extends BaseService {
async createFolder(hashedUserId, data, folderId) {
await this.checkUserAccess(hashedUserId);
const user = await User.findOne({
where: { hashedId: hashedUserId }
});
const user = await this.loadUserByHash(hashedUserId);
if (!user) {
throw new Error('User not found');
}
@@ -266,10 +265,12 @@ class SocialNetworkService extends BaseService {
return image;
}
async checkUserAccess(hashedId) {
const user = await User.findOne({ hashedId: hashedId });
if (!user || !user.active) throw new Error('Access denied: User not found or inactive');
return user.id;
async loadUserByHash(hashedId) {
return await User.findOne({ hashedId });
}
async loadUserByName(userName) {
return await User.findOne({ username: userName});
}
validateFolderData(data) {
@@ -354,9 +355,7 @@ class SocialNetworkService extends BaseService {
async constructUserProfile(user, hashedUserId) {
const userParams = {};
const requestingUser = await User.findOne({
where: { hashedId: hashedUserId },
});
const requestingUser = await this.loadUserByHash(hashedUserId);
if (!requestingUser) {
throw new Error('User not found');
}
@@ -461,7 +460,7 @@ class SocialNetworkService extends BaseService {
}
async getFoldersByUsername(username, hashedUserId) {
const user = await User.findOne({ where: { username } });
const user = await this.loadUserByName(username);
if (!user) {
throw new Error('User not found');
}
@@ -528,11 +527,11 @@ class SocialNetworkService extends BaseService {
}
async createGuestbookEntry(hashedSenderId, recipientName, htmlContent, image) {
const sender = await User.findOne({ where: { hashedId: hashedSenderId } });
const sender = await this.loadUserByHash(hashedSenderId);
if (!sender) {
throw new Error('Sender not found');
}
const recipient = await User.findOne({ where: { username: recipientName } });
const recipient = await this.loadUserByName(recipientName);
if (!recipient) {
throw new Error('Recipient not found');
}
@@ -594,7 +593,7 @@ class SocialNetworkService extends BaseService {
const pageSize = 20;
const offset = (page - 1) * pageSize;
this.checkUserAccess(hashedUserId);
const user = await User.findOne({ where: { username: username } });
const user = await this.loadUserByName(username);
if (!user) {
throw new Error('User not found');
}
@@ -624,9 +623,7 @@ class SocialNetworkService extends BaseService {
async getGuestbookImageFilePath(hashedUserId, guestbookOwnerName, entryId) {
await this.checkUserAccess(hashedUserId);
const guestbookOwner = await User.findOne({
where: { username: guestbookOwnerName },
});
const guestbookOwner = await this.loadUserByName(guestbookOwnerName);
if (!guestbookOwner) {
throw new Error('usernotfound');
}
@@ -649,7 +646,7 @@ class SocialNetworkService extends BaseService {
}
async deleteGuestbookEntry(hashedUserId, entryId) {
const user = await User.findOne({ where: { hashedId: hashedUserId } });
const user = await this.loadUserByHash(hashedUserId);
if (!user) {
throw new Error('User not found');
}
@@ -663,7 +660,8 @@ class SocialNetworkService extends BaseService {
await entry.destroy();
}
async createDiaryEntry(userId, text) {
async createDiaryEntry(hashedUserId, text) {
const userId = await this.checkUserAccess(hashedUserId);
const newEntry = await Diary.create({
userId: userId,
text: text,
@@ -673,29 +671,24 @@ class SocialNetworkService extends BaseService {
return newEntry;
}
async updateDiaryEntry(diaryId, userId, newText) {
async updateDiaryEntry(diaryEntryId, hashedUserId, newText) {
const userId = await this.checkUserAccess(hashedUserId);
const existingEntry = await Diary.findOne({
where: { id: diaryId, userId: userId }
where: { id: diaryEntryId, userId: userId }
});
if (!existingEntry) {
throw new Error('Diary entry not found or unauthorized access');
}
await DiaryHistory.create({
diaryId: existingEntry.id,
userId: existingEntry.userId,
oldText: existingEntry.text,
oldCreatedAt: existingEntry.createdAt,
oldUpdatedAt: existingEntry.updatedAt,
});
existingEntry.text = newText;
existingEntry.updatedAt = new Date();
await existingEntry.save();
return existingEntry;
}
async deleteDiaryEntry(diaryId, userId) {
async deleteDiaryEntry(diaryEntryId, hashedUserId) {
const userId = await this.checkUserAccess(hashedUserId);
const entryToDelete = await Diary.findOne({
where: { id: diaryId, userId: userId }
where: { id: diaryEntryId, userId: userId }
});
if (!entryToDelete) {
throw new Error('Diary entry not found or unauthorized access');
@@ -704,13 +697,15 @@ class SocialNetworkService extends BaseService {
return true;
}
async getDiaryEntries(userId) {
const entries = await Diary.findAll({
async getDiaryEntries(hashedUserId, page) {
const userId = await this.checkUserAccess(hashedUserId);
const entries = await Diary.findAndCountAll({
where: { userId: userId },
order: [['createdAt', 'DESC']]
order: [['createdAt', 'DESC']],
offset: (page - 1) * 20,
limit: 20,
});
return entries;
return { entries: entries.rows, totalPages: Math.ceil(entries.count / 20) };
}
}
export default SocialNetworkService;
export default SocialNetworkService;

View File

@@ -0,0 +1,48 @@
import Forum from '../models/forum/forum.js';
import ForumPermission from '../models/forum/forum_permission.js';
import ForumForumPermission from '../models/forum/forum_forum_permission.js';
const initializeForum = async () => {
const permissions = [
{ name: 'all', value: null },
{ name: 'user', value: null },
{ name: 'admin', value: null },
{ name: 'age', value: '14' },
{ name: 'teammember', value: null }
];
const forums = [
{ name: 'Intern', permissions: ['admin', 'teammember'] },
{ name: 'Flirt', permissions: ['age'] },
{ name: 'Falukant', permissions: ['all'] },
{ name: 'Politik', permissions: ['all'] },
{ name: 'Schule', permissions: ['all'] }
];
const permissionMap = {};
for (const perm of permissions) {
const [permission] = await ForumPermission.findOrCreate({
where: { name: perm.name },
defaults: { name: perm.name, value: perm.value }
});
permissionMap[perm.name] = permission.id;
}
for (const forum of forums) {
try {
const [createdForum] = await Forum.findOrCreate({
where: { name: forum.name },
defaults: { name: forum.name }
});
for (const permissionName of forum.permissions) {
const permissionId = permissionMap[permissionName];
await ForumForumPermission.findOrCreate({
where: { forumId: createdForum.id, permissionId: permissionId },
defaults: { forumId: createdForum.id, permissionId: permissionId },
});
}
console.log(`Forum '${forum.name}' erfolgreich erstellt oder aktualisiert.`);
} catch (error) {
console.error(`Fehler beim Erstellen des Forums '${forum.name}': ${error.message}`);
}
}
};
export default initializeForum;

View File

@@ -16,6 +16,7 @@ const createSchemas = async () => {
await sequelize.query('CREATE SCHEMA IF NOT EXISTS logs');
await sequelize.query('CREATE SCHEMA IF NOT EXISTS type');
await sequelize.query('CREATE SCHEMA IF NOT EXISTS service');
await sequelize.query('CREATE SCHEMA IF NOT EXISTS forum');
};
const initializeDatabase = async () => {
@@ -25,8 +26,8 @@ const initializeDatabase = async () => {
};
const syncModels = async (models) => {
for (const model of Object.values(models)) {
await model.sync({ alter: true });
for (const model of Object.values(models)) {
await model.sync({ alter: true, force: false });
}
};

View File

@@ -1,3 +1,5 @@
// syncDatabase.js
import { initializeDatabase, syncModels } from './sequelize.js';
import initializeTypes from './initializeTypes.js';
import initializeSettings from './initializeSettings.js';
@@ -6,18 +8,38 @@ import initializeImageTypes from './initializeImageTypes.js';
import setupAssociations from '../models/associations.js';
import models from '../models/index.js';
import { createTriggers } from '../models/trigger.js';
import initializeForum from './initializeForum.js';
const syncDatabase = async () => {
try {
await initializeDatabase();
await syncModels(models);
setupAssociations();
createTriggers();
console.log("Initializing database schemas...");
await initializeDatabase(); // Stellt sicher, dass alle Schemas erstellt sind
await initializeSettings();
await initializeTypes();
await initializeUserRights();
await initializeImageTypes();
console.log("Synchronizing models...");
await syncModels(models); // Modelle synchronisieren
console.log("Setting up associations...");
setupAssociations(); // Assoziationen definieren
console.log("Creating triggers...");
await createTriggers(); // Trigger erstellen
console.log("Initializing settings...");
await initializeSettings(); // Einstellungsdaten initialisieren
console.log("Initializing types...");
await initializeTypes(); // Typen initialisieren
console.log("Initializing user rights...");
await initializeUserRights(); // Benutzerrechte initialisieren
console.log("Initializing image types...");
await initializeImageTypes(); // Bildtypen initialisieren
console.log("Initializing forums...");
await initializeForum(); // Foren initialisieren
console.log('Database synchronization complete.');
} catch (error) {
console.error('Unable to synchronize the database:', error);
}