feat: Initial commit of backend services and AGENTS.md
This commit is contained in:
67
AGENTS.md
Normal file
67
AGENTS.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Adopt-a-Street Agents
|
||||
|
||||
This document outlines the various agents (user roles and automated systems) within the Adopt-a-Street application, their capabilities, and how they interact with the platform.
|
||||
|
||||
## User Agents
|
||||
|
||||
### 1. Regular User
|
||||
|
||||
Regular users are the primary actors in the Adopt-a-Street ecosystem. They are community members who volunteer to maintain and improve their local streets.
|
||||
|
||||
**Capabilities:**
|
||||
|
||||
* **Authentication:** Register for a new account, log in, and log out.
|
||||
* **Street Adoption:** View available streets on a map and adopt one or more streets to care for.
|
||||
* **Task Management:** View and complete maintenance tasks associated with their adopted streets (e.g., trash pickup, graffiti removal).
|
||||
* **Social Feed:** Share updates and photos of their work, view posts from other users, and engage with the community.
|
||||
* **Events:** Participate in community cleanup events and other local initiatives.
|
||||
* **Rewards:** Earn points and badges for completing tasks and participating in events, which can be redeemed for rewards.
|
||||
* **Profile Management:** View their profile, including adopted streets, completed tasks, points, and badges.
|
||||
|
||||
### 2. Premium User
|
||||
|
||||
Premium users have all the capabilities of regular users, with potential access to additional features and benefits.
|
||||
|
||||
**Capabilities:**
|
||||
|
||||
* All capabilities of a Regular User.
|
||||
* **Premium Features:** Access to exclusive rewards, advanced analytics, or other premium features (as defined by the application).
|
||||
|
||||
## Automated Agents
|
||||
|
||||
### 1. AI Agent
|
||||
|
||||
The AI agent provides intelligent features and support to users, helping to streamline the street adoption process and enhance the user experience.
|
||||
|
||||
**Functionality:**
|
||||
|
||||
* **Task Suggestions:** Recommends maintenance tasks based on street conditions, user activity, and other data.
|
||||
* **Community Insights:** Provides analytics and insights into community engagement and environmental impact.
|
||||
* **Gamification:** Manages the points, badges, and rewards system to encourage user participation.
|
||||
|
||||
## System Architecture
|
||||
|
||||
The Adopt-a-Street application is a full-stack web application with a React frontend and a Node.js/Express backend.
|
||||
|
||||
### Frontend Components
|
||||
|
||||
* **`MapView`:** Displays an interactive map of streets, allowing users to view available streets and adopt them.
|
||||
* **`TaskList`:** Lists the maintenance tasks for a user's adopted streets.
|
||||
* **`SocialFeed`:** A social media-style feed for users to share updates and connect with others.
|
||||
* **`Profile`:** Displays user information, including adopted streets, points, and badges.
|
||||
* **`Events`:** Shows upcoming community events and allows users to RSVP.
|
||||
* **`Rewards`:** A marketplace where users can redeem points for rewards.
|
||||
* **`Login` and `Register`:** User authentication components.
|
||||
* **`AuthContext`:** Manages user authentication state throughout the application.
|
||||
|
||||
### Backend API Endpoints
|
||||
|
||||
* `/api/auth`: User registration and login.
|
||||
* `/api/streets`: Get street data and adopt streets.
|
||||
* `/api/tasks`: Manage maintenance tasks.
|
||||
* `/api/posts`: Create and view posts in the social feed.
|
||||
* `/api/events`: Manage community events.
|
||||
* `/api/rewards`: Manage rewards and user points.
|
||||
* `/api/reports`: User-submitted reports on street conditions.
|
||||
* `/api/ai`: AI-powered suggestions and insights.
|
||||
* `/api/payments`: Handle premium subscriptions and other payments.
|
||||
2
backend/.gitignore
vendored
Normal file
2
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
.env
|
||||
18
backend/eslint.config.js
Normal file
18
backend/eslint.config.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const globals = require("globals");
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "commonjs",
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-unused-vars": "warn",
|
||||
"no-undef": "warn",
|
||||
},
|
||||
},
|
||||
];
|
||||
20
backend/middleware/auth.js
Normal file
20
backend/middleware/auth.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const jwt = require("jsonwebtoken");
|
||||
|
||||
module.exports = function (req, res, next) {
|
||||
// Get token from header
|
||||
const token = req.header("x-auth-token");
|
||||
|
||||
// Check if not token
|
||||
if (!token) {
|
||||
return res.status(401).json({ msg: "No token, authorization denied" });
|
||||
}
|
||||
|
||||
// Verify token
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.user = decoded.user;
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(401).json({ msg: "Token is not valid" });
|
||||
}
|
||||
};
|
||||
33
backend/models/Event.js
Normal file
33
backend/models/Event.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const EventSchema = new mongoose.Schema(
|
||||
{
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
date: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
location: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
participants: [
|
||||
{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("Event", EventSchema);
|
||||
29
backend/models/Post.js
Normal file
29
backend/models/Post.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const PostSchema = new mongoose.Schema(
|
||||
{
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: true,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
imageUrl: {
|
||||
type: String,
|
||||
},
|
||||
likes: [
|
||||
{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("Post", PostSchema);
|
||||
30
backend/models/Report.js
Normal file
30
backend/models/Report.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const ReportSchema = new mongoose.Schema(
|
||||
{
|
||||
street: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Street",
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: true,
|
||||
},
|
||||
issue: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ["open", "resolved"],
|
||||
default: "open",
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("Report", ReportSchema);
|
||||
27
backend/models/Reward.js
Normal file
27
backend/models/Reward.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const RewardSchema = new mongoose.Schema(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
cost: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
isPremium: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("Reward", RewardSchema);
|
||||
37
backend/models/Street.js
Normal file
37
backend/models/Street.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const StreetSchema = new mongoose.Schema(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
location: {
|
||||
type: {
|
||||
type: String,
|
||||
enum: ["Point"],
|
||||
required: true,
|
||||
},
|
||||
coordinates: {
|
||||
type: [Number],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
adoptedBy: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ["available", "adopted"],
|
||||
default: "available",
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
|
||||
StreetSchema.index({ location: "2dsphere" });
|
||||
|
||||
module.exports = mongoose.model("Street", StreetSchema);
|
||||
29
backend/models/Task.js
Normal file
29
backend/models/Task.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const TaskSchema = new mongoose.Schema(
|
||||
{
|
||||
street: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Street",
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
completedBy: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ["pending", "completed"],
|
||||
default: "pending",
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("Task", TaskSchema);
|
||||
45
backend/models/User.js
Normal file
45
backend/models/User.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const UserSchema = new mongoose.Schema(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isPremium: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
points: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
adoptedStreets: [
|
||||
{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Street",
|
||||
},
|
||||
],
|
||||
completedTasks: [
|
||||
{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Task",
|
||||
},
|
||||
],
|
||||
badges: [String],
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("User", UserSchema);
|
||||
2891
backend/package-lock.json
generated
Normal file
2891
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
backend/package.json
Normal file
28
backend/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "adopt-a-street",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"axios": "^1.8.3",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"globals": "^16.4.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mongoose": "^8.12.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"socket.io": "^4.8.1",
|
||||
"stripe": "^17.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^9.38.0"
|
||||
}
|
||||
}
|
||||
23
backend/routes/ai.js
Normal file
23
backend/routes/ai.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const express = require("express");
|
||||
const auth = require("../middleware/auth");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get AI task suggestions
|
||||
router.get("/task-suggestions", auth, async (req, res) => {
|
||||
try {
|
||||
// In a real application, you would use a more sophisticated AI model to generate task suggestions.
|
||||
// For this example, we'll just return some mock data.
|
||||
const suggestions = [
|
||||
{ street: "Main St", description: "Clean up litter" },
|
||||
{ street: "Elm St", description: "Remove graffiti" },
|
||||
{ street: "Oak Ave", description: "Mow the lawn" },
|
||||
];
|
||||
res.json(suggestions);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
98
backend/routes/auth.js
Normal file
98
backend/routes/auth.js
Normal file
@@ -0,0 +1,98 @@
|
||||
const express = require("express");
|
||||
const bcrypt = require("bcryptjs");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const User = require("../models/User");
|
||||
const auth = require("../middleware/auth");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get user
|
||||
router.get("/", auth, async (req, res) => {
|
||||
try {
|
||||
const user = await User.findById(req.user.id).select("-password");
|
||||
res.json(user);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Register
|
||||
router.post("/register", async (req, res) => {
|
||||
const { name, email, password } = req.body;
|
||||
|
||||
try {
|
||||
let user = await User.findOne({ email });
|
||||
if (user) {
|
||||
return res.status(400).json({ msg: "User already exists" });
|
||||
}
|
||||
|
||||
user = new User({
|
||||
name,
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
user.password = await bcrypt.hash(password, salt);
|
||||
|
||||
await user.save();
|
||||
|
||||
const payload = {
|
||||
user: {
|
||||
id: user.id,
|
||||
},
|
||||
};
|
||||
|
||||
jwt.sign(
|
||||
payload,
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: 3600 },
|
||||
(err, token) => {
|
||||
if (err) throw err;
|
||||
res.json({ token });
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Login
|
||||
router.post("/login", async (req, res) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
try {
|
||||
let user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
return res.status(400).json({ msg: "Invalid credentials" });
|
||||
}
|
||||
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
if (!isMatch) {
|
||||
return res.status(400).json({ msg: "Invalid credentials" });
|
||||
}
|
||||
|
||||
const payload = {
|
||||
user: {
|
||||
id: user.id,
|
||||
},
|
||||
};
|
||||
|
||||
jwt.sign(
|
||||
payload,
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: 3600 },
|
||||
(err, token) => {
|
||||
if (err) throw err;
|
||||
res.json({ token });
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
66
backend/routes/events.js
Normal file
66
backend/routes/events.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const express = require("express");
|
||||
const Event = require("../models/Event");
|
||||
const auth = require("../middleware/auth");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all events
|
||||
router.get("/", async (req, res) => {
|
||||
try {
|
||||
const events = await Event.find();
|
||||
res.json(events);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Create an event
|
||||
router.post("/", auth, async (req, res) => {
|
||||
const { title, description, date, location } = req.body;
|
||||
|
||||
try {
|
||||
const newEvent = new Event({
|
||||
title,
|
||||
description,
|
||||
date,
|
||||
location,
|
||||
});
|
||||
|
||||
const event = await newEvent.save();
|
||||
res.json(event);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// RSVP to an event
|
||||
router.put("/rsvp/:id", auth, async (req, res) => {
|
||||
try {
|
||||
const event = await Event.findById(req.params.id);
|
||||
if (!event) {
|
||||
return res.status(404).json({ msg: "Event not found" });
|
||||
}
|
||||
|
||||
// Check if the user has already RSVPed
|
||||
if (
|
||||
event.participants.filter(
|
||||
(participant) => participant.toString() === req.user.id,
|
||||
).length > 0
|
||||
) {
|
||||
return res.status(400).json({ msg: "Already RSVPed" });
|
||||
}
|
||||
|
||||
event.participants.unshift(req.user.id);
|
||||
|
||||
await event.save();
|
||||
|
||||
res.json(event.participants);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
27
backend/routes/payments.js
Normal file
27
backend/routes/payments.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const express = require("express");
|
||||
const auth = require("../middleware/auth");
|
||||
const User = require("../models/User");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Handle premium subscription
|
||||
router.post("/subscribe", auth, async (req, res) => {
|
||||
try {
|
||||
// In a real application, you would integrate with a payment gateway like Stripe.
|
||||
// For this example, we'll just mock a successful payment.
|
||||
const user = await User.findById(req.user.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ msg: "User not found" });
|
||||
}
|
||||
|
||||
user.isPremium = true;
|
||||
await user.save();
|
||||
|
||||
res.json({ msg: "Subscription successful" });
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
63
backend/routes/posts.js
Normal file
63
backend/routes/posts.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const express = require("express");
|
||||
const Post = require("../models/Post");
|
||||
const auth = require("../middleware/auth");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all posts
|
||||
router.get("/", async (req, res) => {
|
||||
try {
|
||||
const posts = await Post.find().populate("user", ["name"]);
|
||||
res.json(posts);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Create a post
|
||||
router.post("/", auth, async (req, res) => {
|
||||
const { content, imageUrl } = req.body;
|
||||
|
||||
try {
|
||||
const newPost = new Post({
|
||||
user: req.user.id,
|
||||
content,
|
||||
imageUrl,
|
||||
});
|
||||
|
||||
const post = await newPost.save();
|
||||
res.json(post);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Like a post
|
||||
router.put("/like/:id", auth, async (req, res) => {
|
||||
try {
|
||||
const post = await Post.findById(req.params.id);
|
||||
if (!post) {
|
||||
return res.status(404).json({ msg: "Post not found" });
|
||||
}
|
||||
|
||||
// Check if the post has already been liked by this user
|
||||
if (
|
||||
post.likes.filter((like) => like.toString() === req.user.id).length > 0
|
||||
) {
|
||||
return res.status(400).json({ msg: "Post already liked" });
|
||||
}
|
||||
|
||||
post.likes.unshift(req.user.id);
|
||||
|
||||
await post.save();
|
||||
|
||||
res.json(post.likes);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
58
backend/routes/reports.js
Normal file
58
backend/routes/reports.js
Normal file
@@ -0,0 +1,58 @@
|
||||
const express = require("express");
|
||||
const Report = require("../models/Report");
|
||||
const auth = require("../middleware/auth");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all reports
|
||||
router.get("/", async (req, res) => {
|
||||
try {
|
||||
const reports = await Report.find()
|
||||
.populate("street", ["name"])
|
||||
.populate("user", ["name"]);
|
||||
res.json(reports);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Create a report
|
||||
router.post("/", auth, async (req, res) => {
|
||||
const { street, issue } = req.body;
|
||||
|
||||
try {
|
||||
const newReport = new Report({
|
||||
street,
|
||||
user: req.user.id,
|
||||
issue,
|
||||
});
|
||||
|
||||
const report = await newReport.save();
|
||||
res.json(report);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Resolve a report
|
||||
router.put("/:id", auth, async (req, res) => {
|
||||
try {
|
||||
const report = await Report.findById(req.params.id);
|
||||
if (!report) {
|
||||
return res.status(404).json({ msg: "Report not found" });
|
||||
}
|
||||
|
||||
report.status = "resolved";
|
||||
|
||||
await report.save();
|
||||
|
||||
res.json(report);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
70
backend/routes/rewards.js
Normal file
70
backend/routes/rewards.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const express = require("express");
|
||||
const Reward = require("../models/Reward");
|
||||
const User = require("../models/User");
|
||||
const auth = require("../middleware/auth");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all rewards
|
||||
router.get("/", async (req, res) => {
|
||||
try {
|
||||
const rewards = await Reward.find();
|
||||
res.json(rewards);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Create a reward
|
||||
router.post("/", auth, async (req, res) => {
|
||||
const { name, description, cost, isPremium } = req.body;
|
||||
|
||||
try {
|
||||
const newReward = new Reward({
|
||||
name,
|
||||
description,
|
||||
cost,
|
||||
isPremium,
|
||||
});
|
||||
|
||||
const reward = await newReward.save();
|
||||
res.json(reward);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Redeem a reward
|
||||
router.post("/redeem/:id", auth, async (req, res) => {
|
||||
try {
|
||||
const reward = await Reward.findById(req.params.id);
|
||||
if (!reward) {
|
||||
return res.status(404).json({ msg: "Reward not found" });
|
||||
}
|
||||
|
||||
const user = await User.findById(req.user.id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ msg: "User not found" });
|
||||
}
|
||||
|
||||
if (user.points < reward.cost) {
|
||||
return res.status(400).json({ msg: "Not enough points" });
|
||||
}
|
||||
|
||||
if (reward.isPremium && !user.isPremium) {
|
||||
return res.status(403).json({ msg: "Premium reward not available" });
|
||||
}
|
||||
|
||||
user.points -= reward.cost;
|
||||
await user.save();
|
||||
|
||||
res.json({ msg: "Reward redeemed successfully" });
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
74
backend/routes/streets.js
Normal file
74
backend/routes/streets.js
Normal file
@@ -0,0 +1,74 @@
|
||||
const express = require("express");
|
||||
const Street = require("../models/Street");
|
||||
const auth = require("../middleware/auth");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all streets
|
||||
router.get("/", async (req, res) => {
|
||||
try {
|
||||
const streets = await Street.find();
|
||||
res.json(streets);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Get single street
|
||||
router.get("/:id", async (req, res) => {
|
||||
try {
|
||||
const street = await Street.findById(req.params.id);
|
||||
if (!street) {
|
||||
return res.status(404).json({ msg: "Street not found" });
|
||||
}
|
||||
res.json(street);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Create a street
|
||||
router.post("/", auth, async (req, res) => {
|
||||
const { name, location } = req.body;
|
||||
|
||||
try {
|
||||
const newStreet = new Street({
|
||||
name,
|
||||
location,
|
||||
});
|
||||
|
||||
const street = await newStreet.save();
|
||||
res.json(street);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Adopt a street
|
||||
router.put("/adopt/:id", auth, async (req, res) => {
|
||||
try {
|
||||
const street = await Street.findById(req.params.id);
|
||||
if (!street) {
|
||||
return res.status(404).json({ msg: "Street not found" });
|
||||
}
|
||||
|
||||
if (street.status === "adopted") {
|
||||
return res.status(400).json({ msg: "Street already adopted" });
|
||||
}
|
||||
|
||||
street.adoptedBy = req.user.id;
|
||||
street.status = "adopted";
|
||||
|
||||
await street.save();
|
||||
|
||||
res.json(street);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
56
backend/routes/tasks.js
Normal file
56
backend/routes/tasks.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const express = require("express");
|
||||
const Task = require("../models/Task");
|
||||
const auth = require("../middleware/auth");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all tasks for user
|
||||
router.get("/", auth, async (req, res) => {
|
||||
try {
|
||||
const tasks = await Task.find({ completedBy: req.user.id });
|
||||
res.json(tasks);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Create a task
|
||||
router.post("/", auth, async (req, res) => {
|
||||
const { street, description } = req.body;
|
||||
|
||||
try {
|
||||
const newTask = new Task({
|
||||
street,
|
||||
description,
|
||||
});
|
||||
|
||||
const task = await newTask.save();
|
||||
res.json(task);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
// Complete a task
|
||||
router.put("/:id", auth, async (req, res) => {
|
||||
try {
|
||||
const task = await Task.findById(req.params.id);
|
||||
if (!task) {
|
||||
return res.status(404).json({ msg: "Task not found" });
|
||||
}
|
||||
|
||||
task.completedBy = req.user.id;
|
||||
task.status = "completed";
|
||||
|
||||
await task.save();
|
||||
|
||||
res.json(task);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
21
backend/routes/users.js
Normal file
21
backend/routes/users.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const express = require('express');
|
||||
const User = require('../models/User');
|
||||
const auth = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get user by ID
|
||||
router.get('/:id', auth, async (req, res) => {
|
||||
try {
|
||||
const user = await User.findById(req.params.id).populate('adoptedStreets');
|
||||
if (!user) {
|
||||
return res.status(404).json({ msg: 'User not found' });
|
||||
}
|
||||
res.json(user);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send('Server error');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
68
backend/server.js
Normal file
68
backend/server.js
Normal file
@@ -0,0 +1,68 @@
|
||||
require("dotenv").config();
|
||||
const express = require("express");
|
||||
const mongoose = require("mongoose");
|
||||
const cors = require("cors");
|
||||
const http = require("http");
|
||||
const socketio = require("socket.io");
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = socketio(server);
|
||||
const port = process.env.PORT || 5000;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// MongoDB Connection
|
||||
mongoose
|
||||
.connect(process.env.MONGO_URI, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
})
|
||||
.then(() => console.log("MongoDB connected"))
|
||||
.catch((err) => console.log("MongoDB connection error:", err));
|
||||
|
||||
// Socket.IO Setup
|
||||
io.on("connection", (socket) => {
|
||||
console.log("New client connected");
|
||||
socket.on("joinEvent", (eventId) => {
|
||||
socket.join(eventId);
|
||||
});
|
||||
socket.on("eventUpdate", (data) => {
|
||||
io.to(data.eventId).emit("update", data.message);
|
||||
});
|
||||
socket.on("disconnect", () => {
|
||||
console.log("Client disconnected");
|
||||
});
|
||||
});
|
||||
|
||||
// Routes
|
||||
const authRoutes = require("./routes/auth");
|
||||
const streetRoutes = require("./routes/streets");
|
||||
const taskRoutes = require("./routes/tasks");
|
||||
const postRoutes = require("./routes/posts");
|
||||
const eventRoutes = require("./routes/events");
|
||||
const rewardRoutes = require("./routes/rewards");
|
||||
const reportRoutes = require("./routes/reports");
|
||||
const aiRoutes = require("./routes/ai");
|
||||
const paymentRoutes = require("./routes/payments");
|
||||
const userRoutes = require("./routes/users");
|
||||
|
||||
app.use("/api/auth", authRoutes);
|
||||
app.use("/api/streets", streetRoutes);
|
||||
app.use("/api/tasks", taskRoutes);
|
||||
app.use("/api/posts", postRoutes);
|
||||
app.use("/api/events", eventRoutes);
|
||||
app.use("/api/rewards", rewardRoutes);
|
||||
app.use("/api/reports", reportRoutes);
|
||||
app.use("/api/ai", aiRoutes);
|
||||
app.use("/api/payments", paymentRoutes);
|
||||
app.use("/api/users", userRoutes);
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.send("Street Adoption App Backend");
|
||||
});
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log(`Server running on port ${port}`);
|
||||
});
|
||||
1
frontend
Submodule
1
frontend
Submodule
Submodule frontend added at 2b98fcfd52
Reference in New Issue
Block a user