diff --git a/README.md b/README.md
index dfa05e177..f32541403 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,70 @@
# Project Auth API
-Replace this readme with your own information about your project.
+## Description
+This project, "Project Authorization Fullstack," is a full-stack application that demonstrates a user authentication system with image upload functionality. The backend is built with Express.js and integrates MongoDB for data persistence, while the frontend is developed using React.
-Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
+## Backend - Project Auth Backend
+
+### Features
+- User authentication (login, registration, and logout).
+- Image upload with Cloudinary integration.
+- CRUD operations for ads.
+- User and ads management.
+
+### Technologies Used
+- Node.js, Express.js
+- Mongoose for MongoDB integration.
+- bcrypt for password hashing.
+- JWT for maintaining user sessions.
+- multer and Cloudinary for image uploads.
+
+### Installation
+1. Clone the repository.
+2. Navigate to the backend directory.
+3. Run `npm install` to install dependencies.
+4. Create a `.env` file and configure your environment variables (e.g., MongoDB URI, JWT secret, Cloudinary details).
+
+### Usage
+- Use `npm start` to run the server.
+- Use `npm run dev` for development mode with hot reload.
+
+## Frontend
+
+### Features
+- User interface for login, registration, and ad management.
+- Responsive design using styled-components.
+- State management with Zustand.
+
+### Technologies Used
+- React
+- React Router for routing.
+- Styled-components for styling.
+- Zustand for state management.
+
+### Installation
+1. Clone the repository.
+2. Navigate to the frontend directory.
+3. Run `npm install` to install dependencies.
+
+### Usage
+- Use `npm run dev` to start the development server.
+- Use `npm run build` to create a production build.
+
+## Common
+
+### Installation
+- Root directory contains common dependencies and post-install scripts.
+- Run `npm install` at the root to set up both frontend and backend.
+
+### Scripts
+- `postinstall`: Automatically set up the backend upon installing the root dependencies.
## The problem
-Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next?
+If I had more time I would work on the styling and I would create a feature to change/edit a post. Probably I would add the possibility to save a post of someone else and display it under a collection.
## View it live
-Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
+Backend project deployed at: https://project-authorization-fullstack.onrender.com
+
+Frontend project deployed at: https://fullstack-auth-project.netlify.app/
diff --git a/backend/config/cloudinaryConfig.js b/backend/config/cloudinaryConfig.js
new file mode 100644
index 000000000..9e2eda871
--- /dev/null
+++ b/backend/config/cloudinaryConfig.js
@@ -0,0 +1,13 @@
+import cloudinaryFramework from 'cloudinary';
+import dotenv from 'dotenv';
+
+dotenv.config();
+
+// Correct the usage here
+cloudinaryFramework.v2.config({
+ cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
+ api_key: process.env.CLOUDINARY_API_KEY,
+ api_secret: process.env.CLOUDINARY_API_SECRET
+});
+
+export default cloudinaryFramework.v2;
diff --git a/backend/config/db.js b/backend/config/db.js
new file mode 100644
index 000000000..704f67c86
--- /dev/null
+++ b/backend/config/db.js
@@ -0,0 +1,24 @@
+import mongoose from "mongoose";
+import dotenv from "dotenv";
+
+// Load environment variables from the .env file
+dotenv.config();
+
+// Define an asynchronous function 'connectDB' to connect to the MongoDB database
+export const connectDB = async () => {
+ try {
+ // Attempt to connect to the MongoDB database using the URL from the environment variables
+ // Mongoose Method: mongoose.connect()
+ // Description: This line of code serves the crucial purpose of connecting the Node.js application to the MongoDB database specified by the URL provided in the environment variable MONGO_URL. Once this connection is established, the application can perform various database operations, such as querying and modifying data in the MongoDB database. It's a critical step in setting up the database connection for the application to work with MongoDB.
+ const conn = await mongoose.connect(process.env.MONGO_URL);
+
+ // If the connection is successful, log a message indicating that the MongoDB is connected
+ console.log(`Mongo DB Connected: ${conn.connection.host}`);
+ } catch (error) {
+ // If an error occurs during the connection attempt, log the error message
+ console.log(error);
+
+ // Exit the Node.js process with an exit code of 1 to indicate an error
+ process.exit(1);
+ }
+ };
\ No newline at end of file
diff --git a/backend/controllers/adController.js b/backend/controllers/adController.js
new file mode 100644
index 000000000..16e745ecd
--- /dev/null
+++ b/backend/controllers/adController.js
@@ -0,0 +1,170 @@
+import { AdModel } from "../models/AdModel";
+//asyncHandler: We use asyncHandler to simplify error handling in asynchronous code. It helps us avoid writing repetitive try-catch blocks by automatically catching errors and passing them to our error handling middleware. This makes our code cleaner and more readable, reducing the risk of unhandled exceptions that could crash the server.
+import asyncHandler from "express-async-handler";
+// We need to import the userModel to check for the famous accesstoken
+import { UserModel } from "../models/UserModel";
+// Import cloudinary configuration
+import cloudinary from "../config/cloudinaryConfig";
+
+// desciption: Get Ads
+// route: /getAllAds
+// access: Private
+export const getAllAdsController = asyncHandler(async (req, res) => {
+ const userStorage = req.user;
+ const allAds = await AdModel.find().populate("user", "username");
+ res.status(200).json(allAds);
+});
+
+
+// desciption: Get Ads
+// route: /getAds
+// access: Private
+export const getAdsController = asyncHandler(async (req, res) => {
+ const userStorage = req.user;
+ const ads = await AdModel.find({ user: userStorage })
+ .sort("-createdAt")
+ .populate("user", "username"); // Populate the user field
+
+ res.json(ads);
+});
+
+// desciption: POST Ad
+// route: /add
+// access: Private
+export const createAdController = asyncHandler(async (req, res) => {
+ try {
+ console.log("Request body:", req.body); // Log the entire request body
+ console.log("req.file", req.file);
+ const { brand, model } = req.body;
+ const accessToken = req.header("Authorization");
+ const userFromStorage = await UserModel.findOne({ accessToken });
+
+ if (!userFromStorage) {
+ return res.status(401).json({ message: "Unauthorized: User not found." });
+ }
+
+ if (!req.file) {
+ return res.status(400).json({ message: "No image file provided." });
+ }
+
+ let imageUrl, imageId;
+ try {
+ // Upload the image file to Cloudinary
+ const result = await cloudinary.uploader.upload(req.file.path);
+ imageUrl = result.url;
+ imageId = result.public_id; // or use req.file.filename for filename
+
+ } catch (uploadError) {
+ console.error('Cloudinary Upload Error:', uploadError);
+ return res.status(500).json({ message: "Error uploading image to Cloudinary.", error: uploadError });
+ }
+
+ // Define and save new AD
+ const newAd = new AdModel({
+ brand,
+ model,
+ image: imageUrl,
+ imageId: imageId,
+ user: userFromStorage,
+ });
+
+ const savedAd = await newAd.save();
+ res.json(savedAd);
+ } catch (error) {
+ console.error(error); // Log the detailed error
+ res.status(500).json({ message: "Internal server error", error });
+ }
+});
+
+// desciption: PUT/PATCH a specific AD
+// route: /update/:id
+// access: Private
+export const updateAdController = asyncHandler(async (req, res) => {
+ const { id } = req.params;
+ const updateData = req.body; // This contains the fields to be updated
+
+ // Optionally, if you're updating the image, handle the image file upload and get the new image URL and ID
+ if (req.file) {
+ try {
+ // Upload the new image file to Cloudinary
+ const result = await cloudinary.uploader.upload(req.file.path);
+ updateData.image = result.url;
+ updateData.imageId = result.public_id;
+ } catch (uploadError) {
+ console.error('Cloudinary Upload Error:', uploadError);
+ return res.status(500).json({ message: "Error uploading new image to Cloudinary.", error: uploadError });
+ }
+ }
+
+ // Make sure to check that the user making the update is the owner of the ad
+ const userFromStorage = await UserModel.findOne({ accessToken: req.header("Authorization") });
+ if (!userFromStorage) {
+ return res.status(401).json({ message: "Unauthorized: User not found." });
+ }
+
+ // Update the ad with the new data
+ AdModel.findByIdAndUpdate(id, updateData, { new: true }) // {new: true} will return the updated document
+ .then((updatedAd) => {
+ if (!updatedAd) {
+ return res.status(404).json({ message: "Ad not found." });
+ }
+ res.json(updatedAd);
+ })
+ .catch((err) => res.status(500).json({ message: "Error updating ad.", error: err }));
+});
+
+
+// desciption: DELETE all ads
+// route: /deleteAll
+// access: Private
+export const deleteAllAdsController = asyncHandler(async (req, res) => {
+ const accessToken = req.header("Authorization");
+
+ const userFromStorage = await UserModel.findOne({ accessToken });
+ if (!userFromStorage) {
+ return res.status(401).json({ message: "Unauthorized: User not found." });
+ }
+
+ // Find all ads for the user
+ const ads = await AdModel.find({ user: userFromStorage });
+
+ // Iterate over all ads and delete associated images from Cloudinary
+ for (const ad of ads) {
+ await cloudinary.uploader.destroy(ad.imageId);
+ }
+
+ // After all images are deleted, delete the ads from the database
+ const result = await AdModel.deleteMany({ user: userFromStorage });
+ res.json({
+ message: "All ads and associated images deleted",
+ deletedCount: result.deletedCount,
+ });
+});
+
+// desciption: DELETE AD by its ID
+// route: /delete/:id
+// access: Private
+export const deleteSpecificAdController = asyncHandler(async (req, res) => {
+ const { id } = req.params;
+
+ const ad = await AdModel.findById(id);
+ if (!ad) {
+ return res.status(404).json({ message: "Ad not found" });
+ }
+
+ try {
+ // Delete the image from Cloudinary using the imageId
+ await cloudinary.uploader.destroy(ad.imageId);
+
+ // Then delete the ad from the database
+ const result = await AdModel.findByIdAndDelete(id);
+ res.json({
+ message: "Ad and associated image deleted successfully",
+ deletedAd: result,
+ });
+ } catch (err) {
+ console.error('Error during ad deletion:', err);
+ res.status(500).json({ message: "Failed to delete ad and/or image", error: err });
+ }
+});
+
diff --git a/backend/controllers/userController.js b/backend/controllers/userController.js
new file mode 100644
index 000000000..a12311c39
--- /dev/null
+++ b/backend/controllers/userController.js
@@ -0,0 +1,92 @@
+import { UserModel } from "../models/UserModel";
+import asyncHandler from "express-async-handler";
+import bcrypt from "bcrypt";
+
+export const showAllUsersController = asyncHandler(async (req, res) => {
+ const users = await UserModel.find();
+ res.status(200).json(users);
+});
+
+//Set up a route to handle user registration (Sign-up)
+export const registerUserController = asyncHandler(async (req, res) => {
+
+ const { username, password, email } = req.body; //defines what to request from the body
+
+ try {
+ if (!username || !email || !password) {
+ res.status(400);
+ throw new Error("Please fill in all fields"); //error message shown on the server side
+ }
+
+ const existingUser = await UserModel.findOne({
+ $or: [{ username }, { email }],
+ });
+ if (existingUser) {
+ res.status(400);
+ throw new Error(
+ `User with ${existingUser.username === username ? "username" : "email"
+ } already exists`
+ );
+ }
+
+ const salt = bcrypt.genSaltSync(10); //Add extra layers of security
+
+ const hashedPassword = bcrypt.hashSync(password, salt);
+
+ //create a new user instance with the hashed password
+ const newUser = new UserModel({
+ username,
+ email,
+ password: hashedPassword, //passes the variable with the encrypted password
+ });
+
+ await newUser.save(); //Mongoose method to save the new user instance to the database
+
+ // Respond with a success message, user details, and the JWT token
+ res.status(201).json({
+ success: true,
+ response: {
+ username: newUser.username,
+ email: newUser.email,
+ id: newUser._id,
+ accessToken: newUser.accessToken,
+ },
+ });
+ } catch (err) {
+ // Handle any errors that occur during the registration process
+ res.status(500).json({ success: false, response: err.message });
+ }
+});
+
+//Set up a route for logging in
+export const loginUserController = asyncHandler(async (req, res) => {
+ const { username, password } = req.body;
+
+ try {
+ const user = await UserModel.findOne({ username });
+ if (!user) {
+ return res
+ .status(401)
+ .json({ success: false, response: "User not found" });
+ }
+
+ const isMatch = await bcrypt.compare(password, user.password);
+ if (!isMatch) {
+ return res
+ .status(401)
+ .json({ success: false, response: "Incorrect Password" });
+
+ } res.status(200).json({
+ success: true,
+ response: {
+ username: user.username,
+ id: user._id,
+ accessToken: user.accessToken,
+ }
+ });
+ } catch (err) {
+ // Handle any errors that occur during the login process
+ res.status(500).json({ success: false, response: err.message });
+ }
+});
+
diff --git a/backend/middlewares/authenticateUser.js b/backend/middlewares/authenticateUser.js
new file mode 100644
index 000000000..6bdc9e42f
--- /dev/null
+++ b/backend/middlewares/authenticateUser.js
@@ -0,0 +1,22 @@
+//Checks and verifies requests, responses and next
+import { UserModel } from "../models/UserModel";
+
+export const authenticateUser = async (req, res, next) => {
+ //retrieve the access token from the request header (why is it sometimes header and not body?)
+ const accessToken = req.header("Authorization");
+ console.log("Access Token:", accessToken);
+ try {
+ const user = await UserModel.findOne({ accessToken: accessToken });
+ console.log("Retrieved User:", user);
+ if (user) {
+ req.user = user; // Store the user in the request for later use
+ next(); //Continue to the next middleware route
+ } else {
+ //if user not found show message
+ res.status(401).json({ success: false, response: "Please log in" });
+ }
+ } catch (e) {
+ // handle erros that occur during the db query or authentication
+ res.status(500).json({ success: false, response: e.message });
+ }
+};
\ No newline at end of file
diff --git a/backend/middlewares/imageUpload.js b/backend/middlewares/imageUpload.js
new file mode 100644
index 000000000..ecd37c3d4
--- /dev/null
+++ b/backend/middlewares/imageUpload.js
@@ -0,0 +1,16 @@
+import multer from "multer";
+import { CloudinaryStorage } from "multer-storage-cloudinary";
+import cloudinary from "../config/cloudinaryConfig.js";
+
+const storage = new CloudinaryStorage({
+ cloudinary,
+ params: {
+ folder: 'sneakers',
+ allowedFormats: ['jpg', 'png'],
+ transformation: [{ width: 500, height: 500, crop: 'limit' }],
+ },
+})
+
+const parser = multer({ storage });
+
+export default parser;
diff --git a/backend/middlewares/validateInputData.js b/backend/middlewares/validateInputData.js
new file mode 100644
index 000000000..8656ed5d9
--- /dev/null
+++ b/backend/middlewares/validateInputData.js
@@ -0,0 +1,26 @@
+import { AdModel } from "../models/AdModel";
+
+// Middleware function for validating ad data
+export const validateInputData = (req, res, next) => {
+ const { brand, imageUrl, size, model, price } = req.body;
+
+ // Validate the incoming data against the schema
+ const newAd = new AdModel({
+ brand,
+ imageUrl,
+ size,
+ model,
+ price,
+ user: req.user._id, // Assuming you have the user ID in the request object after authentication
+ });
+
+ // Validate the ad data synchronously
+ const validationError = newAd.validateSync();
+
+ if (validationError) {
+ return res.status(400).json({ error: validationError.message }); // Send validation error
+ }
+
+ req.newAd = newAd; // Attach validated ad object to the request
+ next(); // Move to the next middleware or route handler
+};
\ No newline at end of file
diff --git a/backend/models/AdModel.js b/backend/models/AdModel.js
new file mode 100644
index 000000000..a0133f606
--- /dev/null
+++ b/backend/models/AdModel.js
@@ -0,0 +1,42 @@
+import mongoose from "mongoose";
+
+// Import the Schema class from the Mongoose library
+// Destructures the Schema class from the Mongoose library, allowing us to create a schema.
+const { Schema } = mongoose;
+
+// Create a new Mongoose schema named 'adSchema'
+// Creates a new Mongoose schema named adSchema that defines the structure of a document in the MongoDB collection. It includes fields like brand, createdAt, and done, specifying their data types, validation rules, and default values.
+export const adSchema = new Schema(
+ {
+ brand: {
+ type: String,
+ required: true,
+ minlength: 2,
+ },
+ model: {
+ type: String,
+ required: true,
+ },
+ image: {
+ type: String, // Store URL for the image
+ required: true
+ },
+ imageId: {
+ type: String, // Store the unique identifier for the image
+ required: true
+ },
+ // Define the relaitonship between the user and his/her ad -- 1:1 relationship with the user or 1 usar can have many ads
+ user: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "User",
+ },
+ },
+ {
+ timestamps: true,
+ }
+);
+
+// Create a Mongoose model named 'AdModel' based on the 'adSchema' for the 'ads' collection
+// This model is used to interact with the "ads" collection in the MongoDB database. It allows you to perform CRUD operations on documents in that collection and provides methods for data validation based on the schema.
+export const AdModel = mongoose.model("Ad", adSchema);
+
diff --git a/backend/models/UserModel.js b/backend/models/UserModel.js
new file mode 100644
index 000000000..9f709ac5c
--- /dev/null
+++ b/backend/models/UserModel.js
@@ -0,0 +1,36 @@
+import mongoose from "mongoose";
+import crypto from "crypto";
+
+const { Schema } = mongoose; //destructures the Schema class from Mongoose to create a Schema
+
+//Below Schema defines the structure of the user document in the MongoDB collection
+export const userSchema = new Schema(
+ {
+ username: {
+ type: String,
+ required: true,
+ unique: true,
+ minlength: 2,
+ },
+ password: {
+ type: String,
+ required: true,
+ minlength: 6,
+ },
+ email: {
+ type: String,
+ required: true,
+ unique: true,
+ },
+ accessToken: {
+ type: String,
+ default: () => crypto.randomBytes(128).toString("hex"),
+ },
+ },
+ // Add timestamp to tell when the user object is created
+ {
+ timestamps: true, //always outside of the initial object you create in the schema
+ }
+);
+
+export const UserModel = mongoose.model("User", userSchema);
\ No newline at end of file
diff --git a/backend/package.json b/backend/package.json
index 8de5c4ce0..5939d3d37 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -12,9 +12,20 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
+ "bcrypt": "^5.1.1",
+ "body-parser": "^1.20.2",
+ "cloudinary": "^1.41.1",
"cors": "^2.8.5",
+ "crypto": "^1.0.1",
+ "dotenv": "^16.3.1",
"express": "^4.17.3",
- "mongoose": "^8.0.0",
+ "express-async-handler": "^1.2.0",
+ "express-list-endpoints": "^6.0.0",
+ "jsonwebtoken": "^9.0.2",
+ "mongodb": "^6.3.0",
+ "mongoose": "^8.0.3",
+ "multer": "^1.4.5-lts.1",
+ "multer-storage-cloudinary": "^4.0.0",
"nodemon": "^3.0.1"
}
}
diff --git a/backend/routes/adRoutes.js b/backend/routes/adRoutes.js
new file mode 100644
index 000000000..4d22d2188
--- /dev/null
+++ b/backend/routes/adRoutes.js
@@ -0,0 +1,39 @@
+// Import the necessary modules and functions
+import express from "express";
+import { authenticateUser } from "../middlewares/authenticateUser"; // Import middleware for user authentication
+//import { validateInputData } from "../middlewares/validateInputData";
+import parser from "../middlewares/imageUpload"; // Import the parser middleware for image upload
+
+import {
+ getAllAdsController,
+ getAdsController,
+ updateAdController,
+ deleteAllAdsController,
+ deleteSpecificAdController,
+ createAdController,
+} from "../controllers/adController"; // Import controller functions for ads
+
+// Create an instance of the Express router
+const router = express.Router();
+
+// Define a route for handling GET requests to retrieve all ads
+router.get("/getAllAds", getAllAdsController); // When a GET request is made to /get, authenticate the user using middleware and then execute the getAdsController function
+
+// Define a route for handling GET requests to retrieve all ads
+router.get("/getAds", authenticateUser, getAdsController); // When a GET request is made to /get, authenticate the user using middleware and then execute the getAdsController function
+
+// Define a route for handling PUT requests to update a specific ad by ID
+router.put("/update/:id", updateAdController); // When a PUT request is made to /update/:id, execute the updateAdController function
+
+// Define a route for handling DELETE requests to delete all ads
+router.delete("/deleteAll", deleteAllAdsController); // When a DELETE request is made to /deleteAll, execute the deleteAllAdsController function
+
+// Define a route for handling DELETE requests to delete a specific ads by ID
+router.delete("/delete/:id", deleteSpecificAdController); // When a DELETE request is made to /delete/:id, execute the deleteSpecificAdController function
+
+// Define a route for handling POST requests to add a new AD
+router.post("/createAd", authenticateUser, parser.single('image'), createAdController);
+
+// Export the router for use in the main application
+export default router;
+
diff --git a/backend/routes/imageRoute.js b/backend/routes/imageRoute.js
new file mode 100644
index 000000000..cf243daaa
--- /dev/null
+++ b/backend/routes/imageRoute.js
@@ -0,0 +1,44 @@
+import { ImageModel } from "../models/ImageModel";
+import express from "express";
+//import { authenticateUser } from "../middlewares/authenticateUser";
+
+// Create an instance of the Express router
+const router = express.Router();
+
+//GET
+router.get("/getimage", (req, res) => {
+ try {
+ ImageModel.find({}).then(data => {
+ res.json(data)
+ }).catch(error => {
+ res.status(408).json({error})
+ })
+ } catch (error) {
+ res.json({ error })
+ }
+})
+
+
+router.post("/imageupload", async (req, res) => {
+ console.log("Request body:", req.body); // Debugging
+
+ try {
+ const { imageData } = req.body;
+
+ if (!imageData) {
+ return res.status(400).json({ error: 'Required data missing.' });
+ }
+
+ const newImage = await ImageModel.create({ imageData });
+ await newImage.save();
+ res.status(201).json({ message: "Image successfully uploaded" });
+ } catch (error) {
+ console.error(error); // Log the detailed error
+ res.status(409).json({ message: error.message });
+ }
+});
+
+
+
+// Export the router for use in the main application
+export default router;
\ No newline at end of file
diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js
new file mode 100644
index 000000000..d457a5100
--- /dev/null
+++ b/backend/routes/userRoutes.js
@@ -0,0 +1,31 @@
+import express from "express";
+
+import {
+ showAllUsersController,
+ registerUserController,
+ loginUserController,
+} from "../controllers/userController";
+
+// Create an instance of the Express router
+const router = express.Router();
+
+// SHOW USERS: show all users
+router.get(
+ "/users",
+ showAllUsersController
+);
+
+// REGISTER ROUTE: Handle user registration
+router.post(
+ "/register",
+ registerUserController
+);
+
+// LOGIN ROUTE: Handle user login
+router.post(
+ "/login",
+ loginUserController
+);
+
+// Export the router for use in the main application
+export default router;
\ No newline at end of file
diff --git a/backend/server.js b/backend/server.js
index 2d7ae8aa1..293a4ceef 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -1,27 +1,27 @@
import express from "express";
import cors from "cors";
-import mongoose from "mongoose";
+import dotenv from "dotenv";
+import listEndpoints from 'express-list-endpoints';
+import { connectDB } from "./config/db";
+import userRoutes from "./routes/userRoutes";
+import adRoutes from "./routes/adRoutes";
-const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo";
-mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true });
-mongoose.Promise = Promise;
-
-// Defines the port the app will run on. Defaults to 8080, but can be overridden
-// when starting the server. Example command to overwrite PORT env variable value:
-// PORT=9000 npm start
-const port = process.env.PORT || 8080;
+dotenv.config();
const app = express();
+const port = process.env.PORT;
+
+app.use(cors()); // Enable CORS (Cross-Origin Resource Sharing)
+app.use(express.json()); // Parse incoming JSON data
+app.use(express.urlencoded({ extended: false })); // Parse URL-encoded data
-// Add middlewares to enable cors and json body parsing
-app.use(cors());
-app.use(express.json());
+app.use(adRoutes);
+app.use(userRoutes);
-// Start defining your routes here
app.get("/", (req, res) => {
- res.send("Hello Technigo!");
+ res.json(listEndpoints(app));
});
-// Start the server
+connectDB();
app.listen(port, () => {
- console.log(`Server running on http://localhost:${port}`);
+ console.log(`Server running on http://localhost:${port}`);
});
diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs
index 4dcb43901..88cbbaf2d 100644
--- a/frontend/.eslintrc.cjs
+++ b/frontend/.eslintrc.cjs
@@ -2,19 +2,20 @@ module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
- 'eslint:recommended',
- 'plugin:react/recommended',
- 'plugin:react/jsx-runtime',
- 'plugin:react-hooks/recommended',
+ "eslint:recommended",
+ "plugin:react/recommended",
+ "plugin:react/jsx-runtime",
+ "plugin:react-hooks/recommended",
],
- ignorePatterns: ['dist', '.eslintrc.cjs'],
- parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
- settings: { react: { version: '18.2' } },
- plugins: ['react-refresh'],
+ ignorePatterns: ["dist", ".eslintrc.cjs"],
+ parserOptions: { ecmaVersion: "latest", sourceType: "module" },
+ settings: { react: { version: "18.2" } },
+ plugins: ["react-refresh"],
rules: {
- 'react-refresh/only-export-components': [
- 'warn',
+ "react/prop-types": "off",
+ "react-refresh/only-export-components": [
+ "warn",
{ allowConstantExport: true },
],
},
-}
+};
\ No newline at end of file
diff --git a/frontend/index.html b/frontend/index.html
index 0c589eccd..0b6fa57bd 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -2,9 +2,9 @@
-
+
- Vite + React
+ Share Your Sneakers
diff --git a/frontend/package.json b/frontend/package.json
index e9c95b79f..e2b86fe58 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,7 +11,10 @@
},
"dependencies": {
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.20.1",
+ "styled-components": "^6.1.1",
+ "zustand": "^4.4.7"
},
"devDependencies": {
"@types/react": "^18.2.15",
diff --git a/frontend/public/icons/sneaker-favicon.svg b/frontend/public/icons/sneaker-favicon.svg
new file mode 100644
index 000000000..a7571a87a
--- /dev/null
+++ b/frontend/public/icons/sneaker-favicon.svg
@@ -0,0 +1,40 @@
+
+
+
\ No newline at end of file
diff --git a/frontend/public/icons/sneaks-logo.png b/frontend/public/icons/sneaks-logo.png
new file mode 100644
index 000000000..b50cbd0f0
Binary files /dev/null and b/frontend/public/icons/sneaks-logo.png differ
diff --git a/frontend/public/photos/ad-1.jpg b/frontend/public/photos/ad-1.jpg
new file mode 100644
index 000000000..e4c8b701c
Binary files /dev/null and b/frontend/public/photos/ad-1.jpg differ
diff --git a/frontend/public/photos/ad-10.jpg b/frontend/public/photos/ad-10.jpg
new file mode 100644
index 000000000..43bfb2cfb
Binary files /dev/null and b/frontend/public/photos/ad-10.jpg differ
diff --git a/frontend/public/photos/ad-2.jpg b/frontend/public/photos/ad-2.jpg
new file mode 100644
index 000000000..44a3ebc27
Binary files /dev/null and b/frontend/public/photos/ad-2.jpg differ
diff --git a/frontend/public/photos/ad-3.jpg b/frontend/public/photos/ad-3.jpg
new file mode 100644
index 000000000..e9577568e
Binary files /dev/null and b/frontend/public/photos/ad-3.jpg differ
diff --git a/frontend/public/photos/ad-4.jpg b/frontend/public/photos/ad-4.jpg
new file mode 100644
index 000000000..8dc4405c8
Binary files /dev/null and b/frontend/public/photos/ad-4.jpg differ
diff --git a/frontend/public/photos/ad-5.jpg b/frontend/public/photos/ad-5.jpg
new file mode 100644
index 000000000..d63496d83
Binary files /dev/null and b/frontend/public/photos/ad-5.jpg differ
diff --git a/frontend/public/photos/ad-6.jpg b/frontend/public/photos/ad-6.jpg
new file mode 100644
index 000000000..7f67afe2f
Binary files /dev/null and b/frontend/public/photos/ad-6.jpg differ
diff --git a/frontend/public/photos/ad-7.jpg b/frontend/public/photos/ad-7.jpg
new file mode 100644
index 000000000..e6897c6c8
Binary files /dev/null and b/frontend/public/photos/ad-7.jpg differ
diff --git a/frontend/public/photos/ad-8.jpg b/frontend/public/photos/ad-8.jpg
new file mode 100644
index 000000000..880bc110d
Binary files /dev/null and b/frontend/public/photos/ad-8.jpg differ
diff --git a/frontend/public/photos/ad-9.jpg b/frontend/public/photos/ad-9.jpg
new file mode 100644
index 000000000..f4be81834
Binary files /dev/null and b/frontend/public/photos/ad-9.jpg differ
diff --git a/frontend/public/photos/mike-von-NnLj_jd6p7k-unsplash.jpg b/frontend/public/photos/mike-von-NnLj_jd6p7k-unsplash.jpg
new file mode 100644
index 000000000..15224e02e
Binary files /dev/null and b/frontend/public/photos/mike-von-NnLj_jd6p7k-unsplash.jpg differ
diff --git a/frontend/public/photos/sneaker-1.jpg b/frontend/public/photos/sneaker-1.jpg
new file mode 100644
index 000000000..57b39a801
Binary files /dev/null and b/frontend/public/photos/sneaker-1.jpg differ
diff --git a/frontend/public/photos/sneaker-2.jpg b/frontend/public/photos/sneaker-2.jpg
new file mode 100644
index 000000000..165376a52
Binary files /dev/null and b/frontend/public/photos/sneaker-2.jpg differ
diff --git a/frontend/public/photos/sneaker-3.jpg b/frontend/public/photos/sneaker-3.jpg
new file mode 100644
index 000000000..c0c1be834
Binary files /dev/null and b/frontend/public/photos/sneaker-3.jpg differ
diff --git a/frontend/public/photos/sneaker-4.jpg b/frontend/public/photos/sneaker-4.jpg
new file mode 100644
index 000000000..1688ef67c
Binary files /dev/null and b/frontend/public/photos/sneaker-4.jpg differ
diff --git a/frontend/public/photos/sneaker-5.jpg b/frontend/public/photos/sneaker-5.jpg
new file mode 100644
index 000000000..8fb258d67
Binary files /dev/null and b/frontend/public/photos/sneaker-5.jpg differ
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 1091d4310..13d9aa10f 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,3 +1,16 @@
+import { BrowserRouter, Routes } from "react-router-dom";
+import { routes } from "./routes/routes";
+import "./index.css";
+
export const App = () => {
- return
Find me in src/app.jsx!
;
-};
+ return (
+ <>
+
+
+ {/* {routes} */}
+ {routes}
+
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/frontend/src/assets/upload.png b/frontend/src/assets/upload.png
new file mode 100644
index 000000000..0dcdf70d8
Binary files /dev/null and b/frontend/src/assets/upload.png differ
diff --git a/frontend/src/components/AdCard.jsx b/frontend/src/components/AdCard.jsx
new file mode 100644
index 000000000..c31eea9d4
--- /dev/null
+++ b/frontend/src/components/AdCard.jsx
@@ -0,0 +1,11 @@
+export const AdCard = ({ ad }) => {
+
+ return (
+
+
+ >
+ );
+};
+
+
diff --git a/frontend/src/components/reusableComponents/BackButton.jsx b/frontend/src/components/reusableComponents/BackButton.jsx
new file mode 100644
index 000000000..581deee2a
--- /dev/null
+++ b/frontend/src/components/reusableComponents/BackButton.jsx
@@ -0,0 +1,12 @@
+// This component represents a "Go Back" button to navigate back to the list of movies. It's a reusable component.
+import { Link } from 'react-router-dom';
+
+export function BackButton({ redirectTo }) {
+ return (
+