init commit

This commit is contained in:
Quint Ulchar 2023-07-27 23:36:07 -04:00
commit adf6a9516f
Signed by: quiint
GPG Key ID: 2AA6A533C6197F44
31 changed files with 3161 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.env
node_modules/

View File

@ -0,0 +1,9 @@
function authenticate(req, res, next) {
const token = req.header('Authorization');
if (!token) return res.sendStatus(401);
jwt.verify(token, 'SECRET_KEY', (err, decoded) => {
if (err) return res.sendStatus(401);
req.userId = decoded.userId;
next();
});
}

12
models/Notifications.js Normal file
View File

@ -0,0 +1,12 @@
const mongoose = require('mongoose');
const notificationSchema = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
type: String, // 'like', 'comment', or 'follow'
from: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
swit: { type: mongoose.Schema.Types.ObjectId, ref: 'Swit' },
read: { type: Boolean, default: false },
date: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Notification', notificationSchema);

9
models/Repost.js Normal file
View File

@ -0,0 +1,9 @@
const mongoose = require('mongoose');
const RepostSchema = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
swit: { type: mongoose.Schema.Types.ObjectId, ref: 'Swit', required: true },
}, { timestamps: true });
module.exports = mongoose.model('Repost', RepostSchema);

18
models/Swit.js Normal file
View File

@ -0,0 +1,18 @@
const mongoose = require('mongoose');
const SwitSchema = new mongoose.Schema({
text: { type: String, required: true },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
username: { type: String, required: true },
comments: [{
text: { type: String },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User'},
date: { type: Date, default: Date.now},
username: String
}],
reposts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User'}]
}, { timestamps: true });
module.exports = mongoose.model('Swit', SwitSchema);

32
models/User.js Normal file
View File

@ -0,0 +1,32 @@
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const UserSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
profilePicture: { type: String, default: "" },
bio: { type: String, default: "" },
pronouns: { type: String, default: "" },
email: { type: String, default: "" },
name: { type: String, default: "" },
});
UserSchema.methods.validPassword = function (password) {
return bcrypt.compareSync(password, this.password);
};
UserSchema.pre('save', function (next) {
const user = this;
if (!user.isModified('password')) return next();
bcrypt.genSalt(10, function (err, salt) {
if (err) return next(err);
bcrypt.hash(user.password, salt, function (err, hash) {
if (err) return next(err);
user.password = hash;
next();
});
});
});
module.exports = mongoose.model('User', UserSchema);

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "backend",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"aws-sdk": "^2.1412.0",
"bcrypt": "^5.1.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-session": "^1.17.3",
"jsonwebtoken": "^9.0.0",
"mongoose": "^7.3.1",
"multer": "^1.4.5-lts.1",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"socket.io": "^4.7.1"
}
}

32
passport.js Normal file
View File

@ -0,0 +1,32 @@
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('./models/User');
passport.use(new LocalStrategy(
async (username, password, done) => {
try {
const user = await User.findOne({ username: username });
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) { // you will need to implement validPassword method in your User model
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
} catch (err) {
return done(err);
}
}
));
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
const user = await User.findById(id);
done(null, user);
});
module.exports = passport;

31
routes/auth.js Normal file
View File

@ -0,0 +1,31 @@
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const User = require('../models/User');
const passport = require('passport');
router.post('/register', async (req, res) => {
const { username, password } = req.body;
if (!username || !password) return res.status(400).json({ error: 'Missing fields' });
const user = new User({ username, password });
await user.save();
res.sendStatus(201);
console.log("register success")
});
router.post('/login', async (req, res) => {
const { username, password } = req.body;
if (!username || !password) return res.status(400).json({ error: 'Missing fields' });
const user = await User.findOne({ username });
if (!user) return res.sendStatus(401);
const isPasswordCorrect = await bcrypt.compare(password, user.password);
if (!isPasswordCorrect) return res.sendStatus(401);
const token = jwt.sign({ userId: user._id }, 'SECRET_KEY');
const userId = user._id
// req.app.io.emit('user connected', { userId: userId });
res.send({ token, userId});
console.log("login success")
});
module.exports = router;

55
routes/notification.js Normal file
View File

@ -0,0 +1,55 @@
const express = require('express');
const router = express.Router();
const Notification = require('../models/Notifications');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
function authenticate(req, res, next) {
const token = req.header('Authorization');
if (!token) return res.sendStatus(401);
jwt.verify(token, 'SECRET_KEY', (err, decoded) => {
if (err) return res.sendStatus(401);
req.userId = decoded.userId;
next();
});
}
// Fetch the 15 most recent notifications for the authenticated user
// Fetch notifications for the authenticated user, paginated
router.get('/notifications', authenticate, async (req, res) => {
// Parse the page number from the query string (default to 1 if not provided or invalid)
const page = parseInt(req.query.page) || 1;
console.log(req.query)
// Set the number of notifications per page
const pageSize = 15;
// Calculate the skip index (for the MongoDB query) based on the current page number and page size
const skipIndex = (page - 1) * pageSize;
const notifications = await Notification.find({ user: req.userId })
.sort({ read: 1, createdAt: -1 })
.skip(skipIndex)
.limit(pageSize);
res.json(notifications);
});
// Fetch a single notification by its ID (optional)
router.get('/notifications/:id', authenticate, async (req, res) => {
const notification = await Notification.findById(req.params.id);
if (!notification) return res.status(404).send('Notification not found');
res.json(notification);
});
// Mark a notification as read
router.put('/notifications/:id', authenticate, async (req, res) => {
const notification = await Notification.findById(req.params.id);
if (!notification) return res.status(404).send('Notification not found');
notification.read = true;
await notification.save();
res.json(notification);
});
module.exports = router;

175
routes/swit.js Normal file
View File

@ -0,0 +1,175 @@
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const Swit = require('../models/Swit');
const User = require('../models/User');
const Repost = require('../models/Repost');
// Verify the JWT in the Authorization header
function authenticate(req, res, next) {
const token = req.header('Authorization');
if (!token) return res.sendStatus(401);
jwt.verify(token, 'SECRET_KEY', (err, decoded) => {
if (err) return res.sendStatus(401);
req.userId = decoded.userId;
next();
});
}
router.get('/search', authenticate, async (req, res) => {
const query = req.query.q;
console.log(query)
const swits = await Swit.find({ text: { $regex: query, $options: 'i' } });
console.log(swits)
res.json(swits);
});
router.post('/swits', authenticate, async (req, res) => {
const { text } = req.body;
const user = await User.findById(req.userId);
const swit = new Swit({ text, user: req.userId, username: user.username });
await swit.save();
res.sendStatus(201);
console.log("swit success")
});
router.get('/swits', authenticate, async (req, res) => {
const swit = await Swit.find();
res.send(swit.reverse());
console.log("get swit success")
});
router.get('/swits/following', authenticate, async (req, res) => {
const me = await User.findById(req.userId);
const swits = await Swit.find({ user: { $in: me.following } });
res.json(swits.reverse());
});
router.post('/swits/:id/like', authenticate, async (req, res) => {
const swit = await Swit.findById(req.params.id);
if (!swit) return res.status(404).send('Swit not found');
if (swit.likes.includes(req.userId)) {
return res.status(400).json({ error: 'You have already liked this swit' });
}
if (!swit.likes.includes(req.userId)) {
swit.likes.push(req.userId);
await swit.save();
// if (socketId) req.app.io.to(socketId).emit('like', { userId: req.userId, switId: req.params.id }); // Emit the 'like' event to the user who posted the swit
}
res.sendStatus(200);
});
router.get('/swits/:id/likes', authenticate, async (req, res) => {
const swit = await Swit.findById(req.params.id).populate('likes');
if (!swit) return res.status(404).send('Swit not found');
res.json(swit.likes);
});
router.post('/swits/:id/unlike', authenticate, async (req, res) => {
const swit = await Swit.findById(req.params.id);
if (!swit) return res.status(404).send('Swit not found');
swit.likes = swit.likes.filter(id => id.toString() !== req.userId);
await swit.save();
res.sendStatus(200);
});
router.post('/swits/:id/comments', authenticate, async (req, res) => {
const { id } = req.params;
const { text } = req.body;
const swit = await Swit.findById(id);
if (!swit) return res.status(404).send('Swit not found');
const user = await User.findById(req.userId);
if (!user) return res.status(404).send('User not found');
const comment = { text, user: req.userId, username: user.username };
swit.comments.push(comment);
await swit.save();
// if (socketId) req.app.io.to(socketId).emit('comment', { userId: req.userId, switId: req.params.id }); // Emit the 'like' event to the user who posted the swit
res.sendStatus(200);
});
router.get('/swits/:id/comments', authenticate, async (req, res) => {
const { id } = req.params;
const swit = await Swit.findById(id);
if (!swit) return res.status(404).send('Swit not found');
res.send(swit.comments);
});
router.post('/swits/:id/repost', authenticate, async (req, res) => {
const swit = await Swit.findById(req.params.id);
if (!swit) return res.status(404).send('Swit not found');
// Check if the user has already reposted the swit
const existingRepost = await Repost.findOne({ user: req.userId, swit: req.params.id });
if (existingRepost) return res.status(400).json({ error: 'You have already reposted this swit' });
// Create a new Repost document
const repost = new Repost({ user: req.userId, swit: req.params.id });
console.log(repost)
await repost.save();
// Optionally, send a notification to the user who made the original swit
// Return the created repost
res.status(200).json(repost);
});
router.post('/swits/:id/unrepost', authenticate, async (req, res) => {
// Delete the Repost document
const result = await Repost.deleteOne({ user: req.userId, swit: req.params.id });
if (result.deletedCount === 0) return res.status(404).send('Repost not found');
res.status(200).json({message: 'Unreposted'});
});
router.put('/swits/:id', authenticate, async (req, res) => {
const swit = await Swit.findById(req.params.id);
if (swit.user.toString() !== req.userId) {
return res.status(403).send('You do not have permission to update this swit');
}
swit.text = req.body.text;
await swit.save();
res.send(swit);
});
router.get('/swits/:id', authenticate, async (req, res) => {
const swit = await Swit.findById(req.params.id);
if (!swit) return res.sendStatus(404);
res.send(swit);
});
router.delete('/swits/:id', authenticate, async (req, res) => {
const { id } = req.params;
// Check if the swit exists and belongs to the user
const swit = await Swit.findOne({ _id: id, user: req.userId });
if (!swit) return res.sendStatus(404);
// Delete the swit
await Swit.deleteOne({ _id: id });
res.sendStatus(204);
});
module.exports = router;

154
routes/user.js Normal file
View File

@ -0,0 +1,154 @@
const express = require('express');
const router = express.Router();
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const fs = require('fs');
const AWS = require('aws-sdk');
AWS.config.update({
accessKeyId: "178dce4e3eb2f06893ddc54b13712b52",
secretAccessKey: "c271b6b5edca93fc341165f6f803cf81617bb238586db07ba959739185020092",
region: "us-east-1" //
});
const s3 = new AWS.S3({
endpoint: 'https://084fb3347d23f6194c7b68aabe0073c8.r2.cloudflarestorage.com/', // Use custom endpoint
s3ForcePathStyle: true // Needed when using a custom endpoint
});
// Verify the JWT in the Authorization header
function authenticate(req, res, next) {
const token = req.header('Authorization');
if (!token) return res.sendStatus(401);
jwt.verify(token, 'SECRET_KEY', (err, decoded) => {
if (err) return res.sendStatus(401);
req.userId = decoded.userId;
next();
});
}
router.post('/follow/:id', authenticate, async (req, res) => {
const { id } = req.params;
const { userId } = req; // Changed from req.user to req
// First, find the user
const user = await User.findById(userId);
// Check if the user is already following the person
if (user.following.includes(id)) {
return res.status(400).json({ error: 'You are already following this user' });
}
if (id === userId) {
return res.status(304).json({ error: 'You cannot follow yourself' });
}
// If not, add the user to the following list
user.following.push(id);
await user.save();
console.log(user.following.length)
console.log(user.following)
// if (socketId) req.app.io.to(socketId).emit('follow', { userId: req.userId, switId: req.params.id }); // Emit the 'like' event to the user who posted the swit
res.status(200).json({ message: 'Followed user', "count": user.following.length });
});
router.post('/unfollow/:id', authenticate, async (req, res) => {
const { id } = req.params;
const user = await User.findById(id);
if (!user) return res.sendStatus(404);
const me = await User.findById(req.userId);
// Use the $pull operator to remove user._id from me.following
await User.findByIdAndUpdate(req.userId, {
$pull: { following: user._id }
});
console.log(user.following.length)
console.log(user.following)
res.status(200).json({ message: 'Unfollowed user', "count": user.following.length });
});
router.get('/search', authenticate, async (req, res) => {
const query = req.query.q;
const users = await User.find({ username: { $regex: query, $options: 'i' } }).select('-password -__v');
console.log(users)
res.json(users);
});
router.get('/data/:id', authenticate, async (req, res) => {
const { id } = req.params;
const user = await User.findById(id).select('-password -__v').populate('following');
if (!user) return res.sendStatus(404);
res.json(user);
});
router.put('/data/:id/edit', authenticate, async (req, res) => {
const { username, profilePicture, bio, pronouns, email, name } = req.body;
const userId = req.userId;
const user = await User.findById(userId);
if (!user) return res.sendStatus(404);
if (username !== undefined) user.username = username;
if (profilePicture !== undefined) user.profilePicture = profilePicture;
if (bio !== undefined) user.bio = bio;
if (pronouns !== undefined) user.pronouns = pronouns;
if (email !== undefined) user.email = email;
if (name !== undefined) user.name = name;
await user.save();
res.status(200).json({ message: 'Profile updated successfully' });
});
router.get('/followers/:id', authenticate, async (req, res) => {
const { id } = req.params;
const user = await User.findById(id);
if (!user) return res.sendStatus(404);
const followers = await User.find({ following: id });
res.json(followers);
});
router.post('/profilePicture', authenticate, upload.single('profilePicture'), async (req, res) => {
const user = await User.findById(req.userId);
const uploadParams = {
Bucket: 'swifter',
Key: "cdn/pfp/" + req.file.filename, // you might want to add a directory prefix or a unique identifier
Body: fs.createReadStream(req.file.path), // create a read stream from the uploaded file
ACL: 'public-read', // so the file is publicly readable
ContentType: req.file.mimetype,
};
console.log(uploadParams)
s3.upload(uploadParams, async function(err, data) {
if (err) {
console.log("Error", err);
} if (data) {
console.log("Upload Success: ", data);
user.profilePicture = "https://swifter.jiafeiproducts.xyz/" + data.Key;
console.warn("user: ", user)
await user.save();
console.log('saved')
res.sendStatus(200);
console.log('sent')
}
});
});
module.exports = router;

65
server.js Normal file
View File

@ -0,0 +1,65 @@
const express = require('express');
const mongoose = require('mongoose');
const authRoutes = require('./routes/auth'); // assuming auth.js is in the same directory
const switRoutes = require('./routes/swit')
const userRoutes = require('./routes/user')
const notifyRoutes = require('./routes/notification')
const session = require('express-session');
require('dotenv').config();
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const port = process.env.PORT || 1989;
const server = http.createServer(app);
const io = new Server(server);
app.io = io;
let onlineUsers = {};
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('user connected', (userId) => {
onlineUsers[userId] = socket.id;
});
socket.on('disconnect', () => {
console.log('user disconnected');
const userId = Object.keys(onlineUsers).find(key => onlineUsers[key] === socket.id);
if (userId) delete onlineUsers[userId];
});
});
app.use(session({
secret: 'taylorswiftFEARLESSSPEAKNOWRED1989REPUTATIONLOVERfolkloreevermoreMIDNIGHTS',
resave: false,
saveUninitialized: false,
}));
mongoose.connect('mongodb://localhost:27017/switter', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function () {
console.log('Connected to MongoDB');
});
app.use(express.json()); // This line is necessary to parse JSON request body
app.use('/api/v1/app/auth', authRoutes); // Mount the auth routes at /auth
app.use('/api/v1/app/swit', switRoutes); // Mount the swit routes at /swits
app.use('/api/v1/app/user', userRoutes); // Mount the user routes at /user
app.use('/api/v1/app/notify', notifyRoutes); // Mount the notify routes at /notify
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(port, '0.0.0.0', () => {
console.log(`Server is running on http://localhost:${port}`);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

1131
yarn-error.log Normal file

File diff suppressed because it is too large Load Diff

1416
yarn.lock Normal file

File diff suppressed because it is too large Load Diff