Skip to content

Commit 92e2f6c

Browse files
committed
Add content moderation with OpenAI API, user profile management, and image uploads.
1 parent 59c9452 commit 92e2f6c

13 files changed

+147
-9
lines changed

models/Article.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ const articleSchema = new mongoose.Schema({
66
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
77
category: { type: String, required: true },
88
tags: [{ type: String }],
9-
status: { type: String, enum: ['draft', 'published'], default: 'draft' },
10-
image: { type: String } // Add this line for the image field
9+
status: { type: String, enum: ['draft', 'published', 'moderation_failed'], default: 'draft' },
10+
image: { type: String },
11+
moderationStatus: {
12+
flagged: { type: Boolean, default: false },
13+
categories: { type: Object, default: {} },
14+
scores: { type: Object, default: {} }
15+
}
1116
}, { timestamps: true });
1217

1318
const Article = mongoose.model('Article', articleSchema);

models/User.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ const bcrypt = require('bcrypt');
33

44
const userSchema = new mongoose.Schema({
55
username: { type: String, unique: true, required: true },
6-
password: { type: String, required: true }
6+
password: { type: String, required: true },
7+
openaiApiKey: { type: String }
78
});
89

910
userSchema.pre('save', function(next) {

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"moment": "^2.30.1",
2727
"mongoose": "^8.1.1",
2828
"multer": "^1.4.5-lts.1",
29-
"openai": "^4.63.0",
29+
"openai": "^4.71.1",
3030
"tinymce": "^7.5.0"
3131
}
3232
}
29.8 KB
Loading
29.8 KB
Loading
29.8 KB
Loading

routes/articleRoutes.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ const router = express.Router();
33
const multer = require('multer');
44
const { isAuthenticated } = require('./middleware/authMiddleware');
55
const Article = require('../models/Article');
6+
const User = require('../models/User');
67
const { saveFile } = require('../utils/fileUpload');
8+
const { moderateContent } = require('../services/openaiService');
79

810
const upload = multer({ storage: multer.memoryStorage() });
911

@@ -23,19 +25,36 @@ router.post('/create', isAuthenticated, upload.single('image'), async (req, res)
2325
imagePath = saveFile(req.file);
2426
}
2527

28+
const user = await User.findById(author);
29+
if (!user.openaiApiKey) {
30+
return res.status(400).send('OpenAI API key is required. Please update your profile.');
31+
}
32+
33+
// Moderate content
34+
const moderationResult = await moderateContent(content, user.openaiApiKey);
35+
2636
const newArticle = new Article({
2737
title,
2838
content,
2939
author,
3040
category,
3141
tags: tags ? tags.split(',').map(tag => tag.trim()) : [],
3242
image: imagePath,
33-
status: 'draft' // Set initial status as draft
43+
status: moderationResult.flagged ? 'moderation_failed' : 'published',
44+
moderationStatus: {
45+
flagged: moderationResult.flagged,
46+
categories: moderationResult.categories,
47+
scores: moderationResult.category_scores
48+
}
3449
});
3550

3651
await newArticle.save();
3752

38-
res.redirect(`/articles/${newArticle._id}`); // Redirect to the new article page
53+
if (moderationResult.flagged) {
54+
res.render('moderationFailed', { article: newArticle });
55+
} else {
56+
res.redirect(`/articles/${newArticle._id}`);
57+
}
3958
} catch (error) {
4059
console.error('Error handling article submission:', error);
4160
res.status(500).send('Error submitting article');

routes/authRoutes.js

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
const express = require('express');
2+
const router = express.Router();
23
const User = require('../models/User');
34
const bcrypt = require('bcrypt');
4-
const router = express.Router();
5+
const { isAuthenticated } = require('./middleware/authMiddleware');
6+
const OpenAI = require('openai');
57

68
router.get('/auth/register', (req, res) => {
79
res.render('register');
@@ -46,11 +48,43 @@ router.post('/auth/login', async (req, res) => {
4648
router.get('/auth/logout', (req, res) => {
4749
req.session.destroy(err => {
4850
if (err) {
49-
console.error('Error during session destruction:', err); // gpt_pilot_debugging_log
51+
console.error('Error during session destruction:', err);
5052
return res.status(500).send('Error logging out');
5153
}
5254
res.redirect('/auth/login');
5355
});
5456
});
5557

56-
module.exports = router;
58+
router.get('/profile', isAuthenticated, async (req, res) => {
59+
try {
60+
const user = await User.findById(req.session.userId);
61+
const errorMessage = req.session.errorMessage;
62+
const successMessage = req.session.successMessage;
63+
delete req.session.errorMessage;
64+
delete req.session.successMessage;
65+
res.render('profile', { user, errorMessage, successMessage });
66+
} catch (error) {
67+
console.error('Error fetching user profile:', error);
68+
res.status(500).send('Error fetching user profile');
69+
}
70+
});
71+
72+
router.post('/update-profile', isAuthenticated, async (req, res) => {
73+
try {
74+
const { openaiApiKey } = req.body;
75+
76+
// Verify the OpenAI API key
77+
const openai = new OpenAI({ apiKey: openaiApiKey });
78+
await openai.models.list(); // This will throw an error if the key is invalid
79+
80+
await User.findByIdAndUpdate(req.session.userId, { openaiApiKey });
81+
req.session.successMessage = 'Profile updated successfully';
82+
res.redirect('/profile');
83+
} catch (error) {
84+
console.error('Error updating user profile:', error);
85+
req.session.errorMessage = 'Invalid OpenAI API key or error updating profile';
86+
res.redirect('/profile');
87+
}
88+
});
89+
90+
module.exports = router;

services/openaiService.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const OpenAI = require("openai");
2+
3+
async function moderateContent(content, apiKey) {
4+
const openai = new OpenAI({ apiKey });
5+
6+
try {
7+
const response = await openai.moderations.create({
8+
input: content,
9+
});
10+
console.log('Moderation response received:', response);
11+
return {
12+
flagged: response.results[0].flagged,
13+
categories: response.results[0].categories,
14+
category_scores: response.results[0].category_scores
15+
};
16+
} catch (error) {
17+
console.error('Error in content moderation:', error.message, error.stack);
18+
throw error;
19+
}
20+
}
21+
22+
module.exports = { moderateContent };

views/createArticle.ejs

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
<%- include('partials/_header.ejs') %>
66
<main role="main" class="container mt-4">
77
<h1>Create New Article</h1>
8+
<div class="alert alert-info" role="alert">
9+
Please note: All articles are subject to AI-powered content moderation before publishing.
10+
</div>
811
<form action="/articles/create" method="POST" enctype="multipart/form-data">
912
<div class="mb-3">
1013
<label for="title" class="form-label">Title</label>

views/moderationFailed.ejs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<%- include('partials/_head') %>
2+
<%- include('partials/_header') %>
3+
4+
<div class="container mt-5">
5+
<h1>Article Moderation Failed</h1>
6+
<p>We're sorry, but your article could not be published due to content that violates our guidelines.</p>
7+
<p>Please review and revise your content before resubmitting.</p>
8+
<h2>Article Details:</h2>
9+
<p><strong>Title:</strong> <%= article.title %></p>
10+
<p><strong>Category:</strong> <%= article.category %></p>
11+
<h3>Moderation Results:</h3>
12+
<pre><%= JSON.stringify(article.moderationStatus, null, 2) %></pre>
13+
<a href="/articles/edit/<%= article._id %>" class="btn btn-primary">Edit Article</a>
14+
</div>
15+
16+
<%- include('partials/_footer') %>

views/partials/_header.ejs

+6
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@
1212
<li class="nav-item">
1313
<a class="nav-link" href="/articles/create">Create Article</a>
1414
</li>
15+
<li class="nav-item">
16+
<a class="nav-link" href="/profile">Profile</a>
17+
</li>
1518
<li class="nav-item">
1619
<a class="nav-link" href="/auth/logout">Logout</a>
1720
</li>
1821
<% } else { %>
1922
<li class="nav-item">
2023
<a class="nav-link" href="/auth/login">Login</a>
2124
</li>
25+
<li class="nav-item">
26+
<a class="nav-link" href="/auth/register">Register</a>
27+
</li>
2228
<% } %>
2329
</ul>
2430
</div>

views/profile.ejs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<%- include('partials/_head') %>
2+
<%- include('partials/_header') %>
3+
4+
<div class="container mt-5">
5+
<h1>User Profile</h1>
6+
7+
<% if (errorMessage) { %>
8+
<div class="alert alert-danger" role="alert">
9+
<%= errorMessage %>
10+
</div>
11+
<% } %>
12+
13+
<% if (successMessage) { %>
14+
<div class="alert alert-success" role="alert">
15+
<%= successMessage %>
16+
</div>
17+
<% } %>
18+
19+
<form action="/update-profile" method="POST">
20+
<div class="mb-3">
21+
<label for="username" class="form-label">Username</label>
22+
<input type="text" class="form-control" id="username" value="<%= user.username %>" readonly>
23+
</div>
24+
<div class="mb-3">
25+
<label for="openaiApiKey" class="form-label">OpenAI API Key</label>
26+
<input type="password" class="form-control" id="openaiApiKey" name="openaiApiKey" value="<%= user.openaiApiKey || '' %>">
27+
</div>
28+
<button type="submit" class="btn btn-primary">Update Profile</button>
29+
</form>
30+
</div>
31+
32+
<%- include('partials/_footer') %>

0 commit comments

Comments
 (0)