Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed Recipe Image Storage & "Update Recipe" Page Improvements #434

Merged
merged 4 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,7 @@ PORT=8080
# Add your JWT secret token here, it can be anything eg: pineapple
SECRET=""
JWT_TIMEOUT='12h'
# Add your github username
OWNER=""
# Create a public repository and add the name of that repo below
REPO=""
# Add the name of your branch below eg:main
BRANCH=""
# Add your github email id
GITHUB_EMAIL=""
#Add your github access token
TOKEN=""

#Add your email id for communication medium for nodemailer
SMTP_EMAIL=
# Add your SMTP passkey (you need two factor authentication for it and this passkey you need to generate first by following steps :
Expand Down
170 changes: 6 additions & 164 deletions backend/Controllers/RecipeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ import Comment from "../models/Comment.js";
import axios from "axios";
import User from "../models/User.js";
import mongoose from "mongoose";
import { Octokit } from "@octokit/rest";
import { json } from "express";

const g_owner = process.env.OWNER;
const g_repo = process.env.REPO;
const g_branch = process.env.BRANCH;
const g_email = process.env.GITHUB_EMAIL;

/**
* @route {POST} /api/recipe/add
Expand All @@ -37,24 +30,15 @@ const addRecipe = async (req, res) => {
? lastDocument._id.toString().slice(-4)
: "0000";

// Upload the image to GitHub
const imageUrl = await imageToGithub(image, imagename, unique);

// If image upload fails, return an error
if (!imageUrl) {
return res
.status(422)
.json({ success: false, message: "Image upload failed" });
}

// Prepare the recipe data

const data = {
user,
name,
description,
ingredients,
steps,
image: imageUrl, // Save the URL of the uploaded image
image,
imagename,
author,
type,
};
Expand Down Expand Up @@ -126,87 +110,8 @@ const allRecipe = async (req, res) => {
}
};

/**
* @function
* @description Uploads Image to a github repo and returns the downloadable link
* @access private
*/

const imageToGithub = async (fileImage, name, unique) => {
const owner = process.env.OWNER;
const repo = process.env.REPO;
const branch = process.env.BRANCH;

// Validate environment variables
if (!owner || !repo || !branch || !process.env.TOKEN) {
console.error("Missing required environment variables");
return null;
}

console.log("Config:", { owner, repo, branch }); // Debug log
const base64Content = fileImage.split(";base64,").pop();
const fileContent = Buffer.from(base64Content, "base64").toString("base64");

// Use the correct repository structure
const path = `images/${unique}${name}`; // Make sure this directory exists in your repo
const message = `Add ${unique} ${name} via API`;
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`;

console.log("Request URL:", url); // Debug log

try {
// Correct GitHub token format
const headers = {
Authorization: `token ${process.env.TOKEN}`, // Changed from Bearer to token
"X-GitHub-Api-Version": "2022-11-28",
"Content-Type": "application/json",
};

// Check if file exists
let fileExists = false;
try {
const response = await axios.get(url, { headers });
fileExists = response.status === 200;
console.log("File exists check:", fileExists); // Debug log
} catch (error) {
if (error.response && error.response.status !== 404) {
console.error("Error checking file existence:", error.response.data);
throw error;
}
}

// Prepare request payload
const requestPayload = {
message,
content: fileContent,
branch,
};

if (fileExists) {
const existingFile = await axios.get(url, { headers });
requestPayload.sha = existingFile.data.sha;
}

// Upload or update file
console.log("Sending PUT request..."); // Debug log
const response = await axios.put(url, requestPayload, { headers });

if (response.status === 201 || response.status === 200) {
console.log("Upload successful:", response.data.content.download_url);
return response.data.content.download_url;
} else {
console.error("Upload failed:", response.status, response.statusText);
return null;
}
} catch (error) {
console.error("Upload error details:", {
status: error.response?.status,
data: error.response?.data,
message: error.message,
});
return null;
}
};
/**
* @POST /api/recipes/readall
* @description Returns recipe created by an User
Expand All @@ -229,7 +134,7 @@ const getOneUserRecipes = async (req, res) => {
*/
const updateRecipe = async (req, res) => {
try {
const { name, description, ingredients, steps, type, user, author, id } =
const { name, description, ingredients, steps, type, user, author, id, imagename, image } =
req.body;
const data = {
user,
Expand All @@ -239,6 +144,8 @@ const updateRecipe = async (req, res) => {
steps,
author,
type,
image,
imagename
};
const update = await Recipe.updateOne(
{ _id: id },
Expand Down Expand Up @@ -269,72 +176,8 @@ const deleteRecipe = async (req, res) => {
.json({ success: false, message: "Recipe not found" });
}

const imageName = recipe.image;
const owner = g_owner;
const repo = g_repo;
const branch = g_branch;
const result = trimUrl(imageName, owner, repo);

const octokit = new Octokit({
auth: process.env.TOKEN,
});

function trimUrl(url, owner, repo) {
// To remove the unncessary part from the URL
const parsedUrl = new URL(url);
const trimmedPath = decodeURIComponent(parsedUrl.pathname.slice(1)); // Decode the URL and remove leading '/'
const partToRemove = `${owner}/${repo}/${branch}/`;
const finalPath = trimmedPath.replace(partToRemove, "");

return finalPath;
}

// The function to fetch the file content
const fetchFileContent = async () => {
try {
// Make the request to the GitHub API
const response = await octokit.request(
"GET /repos/{owner}/{repo}/contents/{path}",
{
owner: owner,
repo: repo,
path: result,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
}
);
// Getting the SHA key so that it can assist in deletion
const sha = response.data.sha;
await octokit.request("DELETE /repos/{owner}/{repo}/contents/{path}", {
owner: owner,
repo: repo,
path: result,
message: `deleted the image ${result.replace(
"TastyTrails/Recipe/",
" "
)}`,
committer: {
name: recipe.author,
email: g_email,
},
sha: sha,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
});
} catch (error) {
// Handle and log any errors
console.error(
"Error fetching file content:",
error.response ? error.response.data : error.message
);
throw new Error("Failed to delete image from GitHub");
}
};

// Try to delete the file from GitHub
await fetchFileContent();

// If successful, delete the recipe from the database
await Recipe.deleteOne({ _id: req.body.id });
Expand Down Expand Up @@ -402,7 +245,6 @@ const addComment = async (req, res) => {
const getComments = async (req, res) => {
try {
const { recipeId } = req.params;
console.log("Fetching comments for recipe:", recipeId);

// Ensure the recipe exists
const recipe = await Recipe.findById(recipeId);
Expand Down
6 changes: 5 additions & 1 deletion backend/models/Recipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ const recipeSchema = new mongoose.Schema({
default: Date.now,
},
image: {
type: String,
type: String,
required: true,
},
imagename : {
type: String,
required: true,
},
likes: {
Expand Down
Loading