Updates the Dropbox integration to store app keys directly in the token file, resolves the issue with the incorrect Dropbox folder path, and searches for images in multiple actual gallery folders instead of a single non-existent one. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 517dfa7b-26ac-463d-a6e1-a58c6df97188 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: ce08de34-8538-403f-9a35-f9c3d376d112 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/517dfa7b-26ac-463d-a6e1-a58c6df97188/VgutZ7W Replit-Helium-Checkpoint-Created: true
274 lines
7.3 KiB
TypeScript
274 lines
7.3 KiB
TypeScript
import fs from "fs";
|
|
import path from "path";
|
|
|
|
const TOKEN_PATH = path.join(process.cwd(), "server/dropbox-token.json");
|
|
const GALLERY_FOLDERS = [
|
|
"/folx stadl bad tölz 25",
|
|
"/folx stadl bad tölz 23",
|
|
"/folx stadl 2022 attersee",
|
|
"/a guadi musi 2023",
|
|
"/hb fest 2023",
|
|
"/slike epd",
|
|
"/simon wild photos",
|
|
"/adria soccer 2024",
|
|
"/gipfelstammtisch 2024",
|
|
"/die kaiser fanreise",
|
|
"/sanabela 2023",
|
|
"/ainringer musikfrühling 2022",
|
|
];
|
|
|
|
interface DropboxTokens {
|
|
access_token: string;
|
|
refresh_token: string;
|
|
expires_at: number;
|
|
app_key?: string;
|
|
app_secret?: string;
|
|
}
|
|
|
|
interface GalleryImage {
|
|
folder: string;
|
|
fileName: string;
|
|
thumb: string;
|
|
large: string;
|
|
full: string;
|
|
}
|
|
|
|
function getAppKey(): string {
|
|
const tokens = loadTokens();
|
|
return tokens?.app_key || process.env.DROPBOX_APP_KEY || "";
|
|
}
|
|
|
|
function getAppSecret(): string {
|
|
const tokens = loadTokens();
|
|
return tokens?.app_secret || process.env.DROPBOX_APP_SECRET || "";
|
|
}
|
|
|
|
export function getAuthUrl(redirectUri: string): string {
|
|
const params = new URLSearchParams({
|
|
client_id: getAppKey(),
|
|
response_type: "code",
|
|
redirect_uri: redirectUri,
|
|
token_access_type: "offline",
|
|
});
|
|
return `https://www.dropbox.com/oauth2/authorize?${params.toString()}`;
|
|
}
|
|
|
|
export async function exchangeCodeForTokens(code: string, redirectUri: string): Promise<DropboxTokens> {
|
|
const appKey = getAppKey();
|
|
const appSecret = getAppSecret();
|
|
const resp = await fetch("https://api.dropboxapi.com/oauth2/token", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
body: new URLSearchParams({
|
|
code,
|
|
grant_type: "authorization_code",
|
|
redirect_uri: redirectUri,
|
|
client_id: appKey,
|
|
client_secret: appSecret,
|
|
}),
|
|
});
|
|
|
|
if (!resp.ok) {
|
|
const err = await resp.text();
|
|
throw new Error(`Token exchange failed: ${err}`);
|
|
}
|
|
|
|
const data = await resp.json();
|
|
const tokens: DropboxTokens = {
|
|
access_token: data.access_token,
|
|
refresh_token: data.refresh_token,
|
|
expires_at: Date.now() + data.expires_in * 1000,
|
|
app_key: appKey,
|
|
app_secret: appSecret,
|
|
};
|
|
|
|
fs.writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2));
|
|
return tokens;
|
|
}
|
|
|
|
function loadTokens(): DropboxTokens | null {
|
|
try {
|
|
if (fs.existsSync(TOKEN_PATH)) {
|
|
return JSON.parse(fs.readFileSync(TOKEN_PATH, "utf-8"));
|
|
}
|
|
} catch {}
|
|
return null;
|
|
}
|
|
|
|
async function refreshAccessToken(refreshToken: string): Promise<DropboxTokens> {
|
|
const appKey = getAppKey();
|
|
const appSecret = getAppSecret();
|
|
const resp = await fetch("https://api.dropboxapi.com/oauth2/token", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
body: new URLSearchParams({
|
|
grant_type: "refresh_token",
|
|
refresh_token: refreshToken,
|
|
client_id: appKey,
|
|
client_secret: appSecret,
|
|
}),
|
|
});
|
|
|
|
if (!resp.ok) {
|
|
const err = await resp.text();
|
|
throw new Error(`Token refresh failed: ${err}`);
|
|
}
|
|
|
|
const data = await resp.json();
|
|
const tokens: DropboxTokens = {
|
|
access_token: data.access_token,
|
|
refresh_token: refreshToken,
|
|
expires_at: Date.now() + data.expires_in * 1000,
|
|
app_key: appKey,
|
|
app_secret: appSecret,
|
|
};
|
|
|
|
fs.writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2));
|
|
return tokens;
|
|
}
|
|
|
|
async function getValidAccessToken(): Promise<string | null> {
|
|
let tokens = loadTokens();
|
|
if (!tokens) return null;
|
|
|
|
if (Date.now() > tokens.expires_at - 60000) {
|
|
try {
|
|
tokens = await refreshAccessToken(tokens.refresh_token);
|
|
} catch (err: any) {
|
|
console.error("Token refresh failed:", err.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return tokens.access_token;
|
|
}
|
|
|
|
export function isConnected(): boolean {
|
|
return loadTokens() !== null;
|
|
}
|
|
|
|
async function listFolder(accessToken: string, folderPath: string, recursive = false): Promise<any[]> {
|
|
const entries: any[] = [];
|
|
let cursor: string | null = null;
|
|
let hasMore = true;
|
|
|
|
while (hasMore) {
|
|
const url = cursor
|
|
? "https://api.dropboxapi.com/2/files/list_folder/continue"
|
|
: "https://api.dropboxapi.com/2/files/list_folder";
|
|
|
|
const body = cursor
|
|
? { cursor }
|
|
: { path: folderPath, recursive, limit: 2000 };
|
|
|
|
const resp = await fetch(url, {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!resp.ok) {
|
|
const err = await resp.text();
|
|
throw new Error(`List folder failed: ${err}`);
|
|
}
|
|
|
|
const data = await resp.json();
|
|
entries.push(...data.entries);
|
|
hasMore = data.has_more;
|
|
cursor = data.cursor;
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
|
|
async function getTemporaryLinks(accessToken: string, paths: string[]): Promise<Map<string, string>> {
|
|
const results = new Map<string, string>();
|
|
const batchSize = 10;
|
|
|
|
for (let i = 0; i < paths.length; i += batchSize) {
|
|
const batch = paths.slice(i, i + batchSize);
|
|
const promises = batch.map(async (p) => {
|
|
try {
|
|
const resp = await fetch("https://api.dropboxapi.com/2/files/get_temporary_link", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ path: p }),
|
|
});
|
|
|
|
if (resp.ok) {
|
|
const data = await resp.json();
|
|
results.set(p.toLowerCase(), data.link);
|
|
}
|
|
} catch {}
|
|
});
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
const galleryCache: { data: GalleryImage[]; timestamp: number } = { data: [], timestamp: 0 };
|
|
const CACHE_DURATION = 30 * 60 * 1000;
|
|
|
|
export async function fetchGalleryFromDropbox(): Promise<GalleryImage[]> {
|
|
if (galleryCache.data.length > 0 && Date.now() - galleryCache.timestamp < CACHE_DURATION) {
|
|
return galleryCache.data;
|
|
}
|
|
|
|
const accessToken = await getValidAccessToken();
|
|
if (!accessToken) return [];
|
|
|
|
try {
|
|
const images: GalleryImage[] = [];
|
|
|
|
for (const folderPath of GALLERY_FOLDERS) {
|
|
try {
|
|
const entries = await listFolder(accessToken, folderPath, true);
|
|
const imageFiles = entries.filter(
|
|
(e: any) => e[".tag"] === "file" && /\.(jpg|jpeg|png|webp|gif)$/i.test(e.name)
|
|
);
|
|
|
|
const limitedFiles = imageFiles.slice(0, 8);
|
|
const paths = limitedFiles.map((f: any) => f.path_lower);
|
|
const tempLinks = await getTemporaryLinks(accessToken, paths);
|
|
|
|
const folderName = folderPath.split("/").pop() || folderPath;
|
|
|
|
for (const file of limitedFiles) {
|
|
const link = tempLinks.get(file.path_lower) || "";
|
|
if (link) {
|
|
images.push({
|
|
folder: folderName,
|
|
fileName: file.name,
|
|
thumb: link,
|
|
large: link,
|
|
full: link,
|
|
});
|
|
}
|
|
}
|
|
} catch (err: any) {
|
|
console.error(`Folder ${folderPath} error:`, err.message);
|
|
}
|
|
}
|
|
|
|
if (images.length > 0) {
|
|
galleryCache.data = images;
|
|
galleryCache.timestamp = Date.now();
|
|
|
|
const galleryPath = path.join(process.cwd(), "server/gallery-data.json");
|
|
fs.writeFileSync(galleryPath, JSON.stringify(images, null, 2));
|
|
}
|
|
|
|
return images;
|
|
} catch (err: any) {
|
|
console.error("Dropbox gallery fetch error:", err.message);
|
|
return [];
|
|
}
|
|
}
|