const express = require("express"); const multer = require("multer"); const { google } = require("googleapis"); const axios = require("axios"); const Photo = require("../models/Photo"); const GoogleConfig = require("../models/GoogleConfig"); const router = express.Router(); const oauth2Client = new google.auth.OAuth2( process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, process.env.GOOGLE_REDIRECT_URI ); const PHOTOS_SCOPE = [ "https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata", "https://www.googleapis.com/auth/photoslibrary.appendonly" ]; const storage = multer.memoryStorage(); const upload = multer({ storage }); // ... (OAuth endpoints remain the same) ... router.post("/upload", upload.single("file"), async (req, res) => { try { if (!req.file) { return res.status(400).json({ message: "File is required" }); } // 1. Check Google Photos connection const config = await GoogleConfig.findOne({ key: "photos_auth" }); if (!config || !config.tokens) { return res.status(401).json({ message: "Google Photos not connected" }); } oauth2Client.setCredentials(config.tokens); // Refresh token if needed if (config.tokens.expiry_date <= Date.now()) { const { tokens } = await oauth2Client.refreshAccessToken(); config.tokens = tokens; await config.save(); oauth2Client.setCredentials(tokens); } const { token: accessToken } = await oauth2Client.getAccessToken(); // 2. Upload bytes to Google Photos to get uploadToken const uploadResponse = await axios.post( "https://photoslibrary.googleapis.com/v1/uploads", req.file.buffer, { headers: { Authorization: `Bearer ${accessToken}`, "Content-type": "application/octet-stream", "X-Goog-Upload-Content-Type": req.file.mimetype, "X-Goog-Upload-Protocol": "raw", }, } ); const uploadToken = uploadResponse.data; // 3. Create media item using uploadToken const createResponse = await axios.post( "https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate", { newMediaItems: [ { description: req.body.caption || "", simpleMediaItem: { uploadToken: uploadToken, fileName: req.file.originalname, }, }, ], }, { headers: { Authorization: `Bearer ${accessToken}`, "Content-type": "application/json", }, } ); const result = createResponse.data; if (!result.newMediaItemResults || result.newMediaItemResults.length === 0) { throw new Error("Failed to create media item"); } const itemResult = result.newMediaItemResults[0]; if (itemResult.status.message !== "Success" && itemResult.status.message !== "OK") { throw new Error(`Google Photos Error: ${itemResult.status.message}`); } const mediaItem = itemResult.mediaItem; // 4. Save metadata to local DB const photo = await Photo.create({ url: `${mediaItem.baseUrl}=w2048-h1024`, // Store the Google Photos URL caption: mediaItem.description || "", active: req.body.active !== "false", source: "google", googleId: mediaItem.id }); res.status(201).json(photo); } catch (error) { console.error("Upload error:", error.response?.data || error.message); res.status(500).json({ message: "Failed to upload photo to Google Photos" }); } }); router.delete("/:id", async (req, res) => { try { const photo = await Photo.findByIdAndDelete(req.params.id); if (!photo) { return res.status(404).json({ message: "Photo not found" }); } res.json({ ok: true }); } catch (error) { res.status(400).json({ message: "Failed to delete photo" }); } }); module.exports = router;