const express = require("express"); const path = require("path"); const fs = require("fs"); 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 uploadsDir = path.join(__dirname, "..", "uploads"); fs.mkdirSync(uploadsDir, { recursive: true }); 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"]; const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, uploadsDir); }, filename: (req, file, cb) => { const timestamp = Date.now(); const safeName = file.originalname.replace(/\s+/g, "-"); cb(null, `${timestamp}-${safeName}`); }, }); const upload = multer({ storage }); // OAuth Endpoints router.get("/auth/url", (req, res) => { const url = oauth2Client.generateAuthUrl({ access_type: "offline", scope: PHOTOS_SCOPE, prompt: "consent", }); res.json({ url }); }); router.get("/auth/callback", async (req, res) => { const { code } = req.query; try { const { tokens } = await oauth2Client.getToken(code); await GoogleConfig.findOneAndUpdate( { key: "photos_auth" }, { tokens }, { upsert: true } ); res.send("

Authentication successful!

You can close this window and return to the application.

"); } catch (error) { console.error("Auth error:", error); res.status(500).send("Authentication failed"); } }); router.get("/status", async (req, res) => { try { const config = await GoogleConfig.findOne({ key: "photos_auth" }); res.json({ connected: !!config && !!config.tokens }); } catch (error) { res.status(500).json({ message: "Failed to check status" }); } }); router.get("/disconnect", async (req, res) => { try { await GoogleConfig.deleteOne({ key: "photos_auth" }); res.json({ ok: true }); } catch (error) { res.status(500).json({ message: "Failed to disconnect" }); } }); // Fetching Routes router.get("/", async (req, res) => { try { const filter = {}; if (req.query.active === "true") { filter.active = true; } // Get local photos const localPhotos = await Photo.find(filter).sort({ createdAt: -1 }); // Check if Google Photos is connected const config = await GoogleConfig.findOne({ key: "photos_auth" }); if (config && config.tokens) { try { 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 tokensInfo = await oauth2Client.getAccessToken(); const accessToken = tokensInfo.token; const response = await axios.get( "https://photoslibrary.googleapis.com/v1/mediaItems?pageSize=100", { headers: { Authorization: `Bearer ${accessToken}` }, } ); const googlePhotos = (response.data.mediaItems || []).map((item) => ({ id: item.id, url: `${item.baseUrl}=w2048-h1024`, caption: item.description || "Google Photo", active: true, source: "google" })); // Combine local and google photos return res.json([...localPhotos, ...googlePhotos]); } catch (gError) { console.error("Error fetching Google Photos:", gError); // Fallback to local photos only if Google fails return res.json(localPhotos); } } res.json(localPhotos); } catch (error) { console.error("Fetch error:", error); res.status(500).json({ message: "Failed to fetch photos" }); } }); router.post("/", async (req, res) => { try { const photo = await Photo.create(req.body); res.status(201).json(photo); } catch (error) { res.status(400).json({ message: "Failed to create photo" }); } }); router.post("/upload", upload.single("file"), async (req, res) => { try { if (!req.file) { return res.status(400).json({ message: "File is required" }); } const baseUrl = `${req.protocol}://${req.get("host")}`; const url = `${baseUrl}/uploads/${req.file.filename}`; const photo = await Photo.create({ url, caption: req.body.caption || "", active: req.body.active !== "false", }); res.status(201).json(photo); } catch (error) { res.status(400).json({ message: "Failed to upload photo" }); } }); 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;