yeah
This commit is contained in:
commit
767417574e
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
out
|
||||
ios
|
||||
android
|
5
backend/.env
Normal file
5
backend/.env
Normal file
@ -0,0 +1,5 @@
|
||||
AWS_ACCESS_KEY_ID=178dce4e3eb2f06893ddc54b13712b52
|
||||
AWS_SECRET_ACCESS_KEY=c271b6b5edca93fc341165f6f803cf81617bb238586db07ba959739185020092
|
||||
AWS_REGION=ENAM
|
||||
|
||||
SECRET_KEY=taylorswiftFEARLESSSPEAKNOWRED1989REPUTATIONLOVERfolkloreevermoreMIDNIGHTS
|
3
backend/.gitignore
vendored
Normal file
3
backend/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.env
|
||||
node_modules/
|
||||
uploads/
|
10
backend/middleware/authenticate.js
Normal file
10
backend/middleware/authenticate.js
Normal file
@ -0,0 +1,10 @@
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
27
backend/models/DirectMessage.js
Normal file
27
backend/models/DirectMessage.js
Normal file
@ -0,0 +1,27 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const DirectMessageSchema = new Schema({
|
||||
sender: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
recipient: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
timestamp: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('DirectMessage', DirectMessageSchema);
|
12
backend/models/Notifications.js
Normal file
12
backend/models/Notifications.js
Normal 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
backend/models/Repost.js
Normal file
9
backend/models/Repost.js
Normal 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);
|
||||
|
19
backend/models/Swit.js
Normal file
19
backend/models/Swit.js
Normal file
@ -0,0 +1,19 @@
|
||||
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,
|
||||
likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }] // Add this line
|
||||
}],
|
||||
reposts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User'}]
|
||||
}, { timestamps: true });
|
||||
|
||||
|
||||
module.exports = mongoose.model('Swit', SwitSchema);
|
32
backend/models/User.js
Normal file
32
backend/models/User.js
Normal 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
backend/package.json
Normal file
19
backend/package.json
Normal 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
backend/passport.js
Normal file
32
backend/passport.js
Normal 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;
|
33
backend/routes/auth.js
Normal file
33
backend/routes/auth.js
Normal file
@ -0,0 +1,33 @@
|
||||
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 pfp = "https://source.boringavatars.com/marble/" + username
|
||||
console.log(pfp)
|
||||
const user = new User({ username, password, profilePhoto: pfp });
|
||||
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;
|
140
backend/routes/dm.js
Normal file
140
backend/routes/dm.js
Normal file
@ -0,0 +1,140 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const DirectMessage = require('../models/DirectMessage');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../models/User');
|
||||
|
||||
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('/', authenticate, async (req, res) => {
|
||||
try {
|
||||
const dms = await DirectMessage.find({
|
||||
$or: [
|
||||
{ sender: req.userId },
|
||||
{ recipient: req.userId },
|
||||
],
|
||||
}).populate('sender recipient', 'username');
|
||||
|
||||
// Group the messages by conversation
|
||||
let conversations = {};
|
||||
dms.forEach(dm => {
|
||||
const otherUserId = dm.sender._id.toString() === req.userId ? dm.recipient._id.toString() : dm.sender._id.toString();
|
||||
if (!conversations[otherUserId]) {
|
||||
conversations[otherUserId] = {
|
||||
userId: otherUserId,
|
||||
username: dm.sender._id.toString() === req.userId ? dm.recipient.username : dm.sender.username,
|
||||
messages: [],
|
||||
};
|
||||
}
|
||||
conversations[otherUserId].messages.push(dm);
|
||||
});
|
||||
|
||||
// Convert the conversations object to an array
|
||||
const conversationsArray = Object.values(conversations);
|
||||
|
||||
res.json(conversationsArray);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: "Could not get Direct Messages." });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
router.get('/:otherUserId', authenticate, async (req, res) => {
|
||||
const { otherUserId } = req.params;
|
||||
|
||||
try {
|
||||
const dms = await DirectMessage.find({
|
||||
$or: [
|
||||
{ sender: req.userId, recipient: otherUserId },
|
||||
{ sender: otherUserId, recipient: req.userId },
|
||||
],
|
||||
}).populate('sender recipient', 'username');
|
||||
|
||||
res.json(dms);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: "Could not get Direct Messages." });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:dmId', authenticate, async (req, res) => {
|
||||
const { dmId } = req.params;
|
||||
|
||||
try {
|
||||
const dm = await DirectMessage.findById(dmId);
|
||||
|
||||
if (!dm) {
|
||||
return res.status(404).json({ error: "Direct Message not found." });
|
||||
}
|
||||
|
||||
// Check if the authenticated user is the sender of the DM
|
||||
if (dm.sender.toString() !== req.userId) {
|
||||
return res.status(403).json({ error: "You can only delete your own Direct Messages." });
|
||||
}
|
||||
|
||||
await dm.remove();
|
||||
|
||||
res.json({ message: "Direct Message deleted." });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: "Could not delete Direct Message." });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/send', authenticate, async (req, res) => {
|
||||
const { recipientId, text, senderId } = req.body;
|
||||
console.log(req.user)
|
||||
|
||||
if (!recipientId || !text) {
|
||||
return res.status(400).json({ error: "Recipient ID and text are required." });
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if the recipient exists
|
||||
const recipient = await User.findById(recipientId);
|
||||
if (!recipient) {
|
||||
return res.status(404).json({ error: "Recipient not found." });
|
||||
}
|
||||
// Create the Direct Message
|
||||
const dm = new DirectMessage({
|
||||
sender: senderId,
|
||||
recipient: recipientId,
|
||||
content: text,
|
||||
});
|
||||
|
||||
// Save the Direct Message
|
||||
const savedDm = await dm.save();
|
||||
|
||||
// Populate the sender and recipient fields with full user objects
|
||||
await savedDm.populate('sender recipient', 'username').exec;
|
||||
|
||||
// Now emit the `new_dm` event with the populated Direct Message
|
||||
const io = req.app.io;
|
||||
const senderSocketId = req.app.onlineUsers[senderId];
|
||||
const recipientSocketId = req.app.onlineUsers[recipientId];
|
||||
if (senderSocketId) {
|
||||
io.to(senderSocketId).emit('new_dm', savedDm);
|
||||
}
|
||||
if (recipientSocketId) {
|
||||
io.to(recipientSocketId).emit('new_dm', savedDm);
|
||||
}
|
||||
|
||||
|
||||
|
||||
res.json(savedDm);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: "Could not send Direct Message." });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
55
backend/routes/notification.js
Normal file
55
backend/routes/notification.js
Normal 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;
|
234
backend/routes/swit.js
Normal file
234
backend/routes/swit.js
Normal file
@ -0,0 +1,234 @@
|
||||
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.get('/swits/user/:id', authenticate, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const swits = await Swit.find({ user: id });
|
||||
console.log(swits)
|
||||
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/:switId/comments/:commentId/like', authenticate, async (req, res) => {
|
||||
const swit = await Swit.findById(req.params.switId);
|
||||
if (!swit) return res.status(404).send('Swit not found');
|
||||
console.log(swit)
|
||||
const comment = swit.comments.id(req.params.commentId);
|
||||
if (!comment) return res.status(404).send('Comment not found');
|
||||
|
||||
if (comment.likes.includes(req.userId)) {
|
||||
return res.status(400).json({ error: 'You have already liked this comment' });
|
||||
}
|
||||
|
||||
comment.likes.push(req.userId);
|
||||
await swit.save();
|
||||
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
router.post('/swits/:switId/comments/:commentId/unlike', authenticate, async (req, res) => {
|
||||
const swit = await Swit.findById(req.params.switId);
|
||||
if (!swit) return res.status(404).send('Swit not found');
|
||||
|
||||
const comment = swit.comments.id(req.params.commentId);
|
||||
if (!comment) return res.status(404).send('Comment not found');
|
||||
|
||||
comment.likes = comment.likes.filter(id => id.toString() !== req.userId);
|
||||
await swit.save();
|
||||
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
router.delete('/swits/:switId/comments/:commentId', authenticate, async (req, res) => {
|
||||
const { switId, commentId } = req.params;
|
||||
|
||||
const swit = await Swit.findById(switId);
|
||||
if (!swit) return res.status(404).send('Swit not found');
|
||||
|
||||
const commentIndex = swit.comments.findIndex(comment => comment._id.toString() === commentId);
|
||||
if (commentIndex === -1) return res.status(404).send('Comment not found');
|
||||
|
||||
// Only allow the user who posted the comment to delete it
|
||||
if (swit.comments[commentIndex].user.toString() !== req.userId) {
|
||||
return res.status(403).send('You do not have permission to delete this comment');
|
||||
}
|
||||
|
||||
swit.comments.splice(commentIndex, 1);
|
||||
await swit.save();
|
||||
|
||||
res.sendStatus(204);
|
||||
});
|
||||
|
||||
|
||||
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
backend/routes/user.js
Normal file
154
backend/routes/user.js
Normal 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;
|
70
backend/server.js
Normal file
70
backend/server.js
Normal file
@ -0,0 +1,70 @@
|
||||
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 dmRoutes = require('./routes/dm');
|
||||
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 1989;
|
||||
const server = http.createServer(app);
|
||||
const io = new Server(server);
|
||||
app.io = io;
|
||||
|
||||
app.onlineUsers = {};
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
console.log('a user connected');
|
||||
|
||||
socket.on('user connected', (userId) => {
|
||||
console.log(`User ${userId} connected`); // log when a 'user connected' message is received
|
||||
app.onlineUsers[userId] = socket.id;
|
||||
console.log(app.onlineUsers); // log the current state of the `onlineUsers` object
|
||||
});
|
||||
socket.on('disconnect', () => {
|
||||
console.log('user disconnected');
|
||||
const userId = Object.keys(app.onlineUsers).find(key => app.onlineUsers[key] === socket.id);
|
||||
if (userId) delete app.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.use('/api/v1/app/dms', dmRoutes); // Mount the dm routes at /dms'
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Hello, World!');
|
||||
});
|
||||
|
||||
server.listen(port, '0.0.0.0', () => {
|
||||
console.log(`Server is running on http://localhost:${port}`);
|
||||
});
|
1131
backend/yarn-error.log
Normal file
1131
backend/yarn-error.log
Normal file
File diff suppressed because it is too large
Load Diff
1416
backend/yarn.lock
Normal file
1416
backend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend
Submodule
1
frontend
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit d916a34b8847a3325f4890fc6c5468d596e83e8f
|
17
start_processes.sh
Executable file
17
start_processes.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Start process1 and save its PID
|
||||
cd ~/dev/switter/backend && nodemon server.js &
|
||||
echo $! > /tmp/my_processes.pid
|
||||
|
||||
# Start process2 and append its PID
|
||||
cd ~/dev/switter/frontend && npx expo start &
|
||||
echo $! >> /tmp/my_processes.pid
|
||||
|
||||
# Start process3 and append its PID
|
||||
cd ~/dev/switter/frontend && yarn dev:tailwind &
|
||||
echo $! >> /tmp/my_processes.pid
|
||||
|
||||
# Start process4 and append its PID
|
||||
cd ~/dev && python3 pagekite.py 1989 staging.swifter.win &
|
||||
echo $! >> /tmp/my_processes.pid
|
9
stop_processes.sh
Executable file
9
stop_processes.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Read the PIDs from the file and terminate the processes
|
||||
while read pid; do
|
||||
kill $pid
|
||||
done < /tmp/my_processes.pid
|
||||
|
||||
# Optionally, remove the PID file
|
||||
rm /tmp/my_processes.pid
|
1
website
Submodule
1
website
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 07f8de1a3bfe4bbd4891bfc21b1ac65259549245
|
Loading…
Reference in New Issue
Block a user