Fix the image gallery to display images from correct folders
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
This commit is contained in:
parent
72e34076fc
commit
146f797809
BIN
attached_assets/image_1772389305788.png
Normal file
BIN
attached_assets/image_1772389305788.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 307 KiB |
@ -1,5 +1,7 @@
|
||||
{
|
||||
"access_token": "sl.u.AGWEi26aooROq4uVMyilg68bMLhgbcsx6wdMxuB-CyH3_HBcfMInAEy7U0JR_oUdHVx4Y51L-GZ1yltJT-YTeZJVGVZ3sL8x9sSQpP1VsfSgCCu5hDqgFYCXGchJu4XwhWX42L6vmUQ-QUDrsokj2HU1SKJ0MGcLfMNVynMJZKwOl2lcZ88veeB782UoAn9IXei96v8wFCvKIJCFYyS7dosOFuTsqvjJZWy8zeZZ9GLdCAQiwQr2lRvhxvpMF8ZiUSzpZSjjld_y3A8cRtmJNVQLBL6ctB3TtnYtse4ccKPmt5pOhIFcWcd7OYSv6BiiWSEdkmVy3UTiStq8EQBc7sdRRYE1_cZKQbsDzgGk8vgd9QbaOuQyW949C90H7pGD3c-ZJraIjdeFlvW_yD99qQSb4hwnB1w0pGVXtiuVfvhEPasdbl8LWwPmM1q3P2OZLGOz34xj8gPfwVULcfpAtw5Ipsbe8Y4VBPz2hn0HxhYNDyFdV1e8wLFiAAbOabG8R7zzPodbtXLLuE2jWXJhQeMKtT763b6_YDMs3VJc43m5Uzgezcb_izupEtEB4hB9K1E3783F05MArnUQ7ntUDZ6lFPYkjbZM7__4rRv8_hmSiAQ_FkvFQo75PBIpIbkhoa3Fm8wDvOSG_9lx8c_wfJjV0BbXAJuvN0GPbDzKuwAOVqpibcx7aSR3Lawgs19Low-MTLzXtzDUDIdPybIB5rMOSchcQsYdOrDlKI-_0X--CkvDVBGXXYFK6t-OiQeqtfZDUqIRXNpVwt2XCO0rGBgXCgR8M3bjemMB3YDNqZEbEG85Vpjkelc_1jXwGkK6UfQMS0LPdfnZpuhSEnMG0SBbY8hM4J-z9RRK3qqWshTzvOE6QKZE8ldpfKviI0YMfjhxhDiVQvfveh9nFd1rafQipwltNbdRAi3l0bXMdpV2UAtspvgLfmvoFugVzmm6C8oTKlS-rjUlGk7_lZjX_29prMZc5WvuIUoRkgZUgJchMnGaE1JSvpH-Ky-63aZXBqi5uA2WW13F8or1Tng8AV-8YehwpZ4udf0chdJyFw9tnfdYHyEX-b1hLT8sAwERwV0_9cKycDl7h5JhGLHmaouOKl0JvsbvZsD7uyoQfPGtYd6zQpZUHJx72pzeghpTTW40ukRGRjuqxCe65hNzP6Bv6eQQI6H_Wf8YaBNoQYXAEc8JchO_PhKhDeBFvMxBnBuedW-QoAvxh20zmeiPYVxNUrcNSTVXT7EhCFwlpSzVuIlnZzivldA2arPtBlK5fpCsO9bFBqIXURO-X7TXI0Tb-9XgjEurtE6DviyUoHxWnA",
|
||||
"access_token": "sl.u.AGVAMk1isWjEfoERFxA5-4d0aQJgK5q3BvH4nQq7XKhUIV2q4YDpbYok3TQf7z2fF7ZBDZQJ2of2rnz4vzrfnkTrgRIirNckfsMkSfZvAXF8E2VK3fLrtXs5uy3g-A7CibB2I_TFehxQgiGJh77788vCDnGuwt-rYLSEm238-SS9OiDWUXNPvESABp2LHwJmKOnZg_Z6I9b6fy-BnppE_IOMPDkIhxy8T3a98-jgZGlR7SKEOPbAv5O9cQsmSi9hfh3pV53IV6lhfSpvPFXCxUCIUB990Cga1Cv70JlNSWpcB4pY_5mV-03ncRhxSBe-DjFAwY8DQskIsFyS5bcNloqk-nei3LxAXlk-PqTEpCMGcIjM5sVCazBPh1m3u169M5v_qV598nxc7S7uZcVT2jvT01bLIN9HqOvF3fpYeEY0v2ORQNMzr8AuZEGmpJbIkT0zpXSNatgVPzOtvmdTjoxfb0wjMjM1VEjGgX1v6UHVxLnyvSyvW7UZ2QNo4wmqOJQnq32l5sBFukHDImPDW3nKoATHJcn6dLLk0sjbl3-8UH9sZpezOxJIGmcla34DFbt2n_NymArdSA-m7ywGHqdR8Eqr-scWQi14WKcaHXnCQF_R_puhKiTdzcW4UFb5qmBfk78fDJtPhsrIagc31YYXx5co--qkTsdGDdhVKtE7fvDucn63uF79YNoZ087rD5P-gbDgSf8j90ijry4gbMsL0vxtWiMJ9nZxjT-k43ZDhmz_h5h0Fg6-1sAkRHq_cPQTjffn7rAWnb7zxOJueIMDWMTy2UXPERBuwuWa66QVqiyNpi_6URR2suyXZCK7xvqbHUy3fVP_k5xjZ84YcQXcBVUfZoxso1rlzsuTFEMrhZF_vUJqYBcQyw0VRNVWcPkz6_9ZcmGSsibS-112M8516EI-tl60id3Rd_KwH36nfqT0pkCUbkcAhreDzzU_ci0p3hB2WhfBDrsqolAW3Dc9S_pC9CuJn_dECjrngMbegARCXsychREPYx60h9Kg8ZyEmacrUklURVkXHT2y2HshrLxSbPL8CbHyXU4-3_8qquKrpTncLSxRuZCuPociVUxsNF4iyvUfSgi2ZjhuggJJteZvdfQ--aKt0s8aTdfThRQ6DyJTe9MoDlCUzW8byP7Z9JZ2AWFehqO1xKyWLD7wU6g7LV48G5n_S5kJUYpuPXmWDgpoj1-VehZGkVWEoQiW0deboadOT-MOvL1flV0gLU6nZv9D2F7jWWVTe26m1qC9iixHRyTmupMrL-P4tOEvTD41krapeFstuP61mzpl2FxrQge8P2NDfOY61olilQ",
|
||||
"refresh_token": "8-fiK0Io8Z8AAAAAAAAAAXn1QIGTwFTVWKF47COXY2bjqYlWyd4aRnFtQJ7usZ0y",
|
||||
"expires_at": 1772402616000
|
||||
}
|
||||
"expires_at": 1772403823104,
|
||||
"app_key": "sjwprgka82p8tpv",
|
||||
"app_secret": "g3vuczqo0rx3crz"
|
||||
}
|
||||
@ -2,12 +2,27 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const TOKEN_PATH = path.join(process.cwd(), "server/dropbox-token.json");
|
||||
const DROPBOX_FOLDER = "/Folx News Gallery";
|
||||
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 {
|
||||
@ -19,11 +34,13 @@ interface GalleryImage {
|
||||
}
|
||||
|
||||
function getAppKey(): string {
|
||||
return process.env.DROPBOX_APP_KEY || "";
|
||||
const tokens = loadTokens();
|
||||
return tokens?.app_key || process.env.DROPBOX_APP_KEY || "";
|
||||
}
|
||||
|
||||
function getAppSecret(): string {
|
||||
return process.env.DROPBOX_APP_SECRET || "";
|
||||
const tokens = loadTokens();
|
||||
return tokens?.app_secret || process.env.DROPBOX_APP_SECRET || "";
|
||||
}
|
||||
|
||||
export function getAuthUrl(redirectUri: string): string {
|
||||
@ -37,6 +54,8 @@ export function getAuthUrl(redirectUri: string): string {
|
||||
}
|
||||
|
||||
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" },
|
||||
@ -44,8 +63,8 @@ export async function exchangeCodeForTokens(code: string, redirectUri: string):
|
||||
code,
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: redirectUri,
|
||||
client_id: getAppKey(),
|
||||
client_secret: getAppSecret(),
|
||||
client_id: appKey,
|
||||
client_secret: appSecret,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -59,6 +78,8 @@ export async function exchangeCodeForTokens(code: string, redirectUri: string):
|
||||
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));
|
||||
@ -75,14 +96,16 @@ function loadTokens(): DropboxTokens | 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: getAppKey(),
|
||||
client_secret: getAppSecret(),
|
||||
client_id: appKey,
|
||||
client_secret: appSecret,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -96,6 +119,8 @@ async function refreshAccessToken(refreshToken: string): Promise<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));
|
||||
@ -107,7 +132,12 @@ async function getValidAccessToken(): Promise<string | null> {
|
||||
if (!tokens) return null;
|
||||
|
||||
if (Date.now() > tokens.expires_at - 60000) {
|
||||
tokens = await refreshAccessToken(tokens.refresh_token);
|
||||
try {
|
||||
tokens = await refreshAccessToken(tokens.refresh_token);
|
||||
} catch (err: any) {
|
||||
console.error("Token refresh failed:", err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return tokens.access_token;
|
||||
@ -117,7 +147,7 @@ export function isConnected(): boolean {
|
||||
return loadTokens() !== null;
|
||||
}
|
||||
|
||||
async function listFolder(accessToken: string, folderPath: string): Promise<any[]> {
|
||||
async function listFolder(accessToken: string, folderPath: string, recursive = false): Promise<any[]> {
|
||||
const entries: any[] = [];
|
||||
let cursor: string | null = null;
|
||||
let hasMore = true;
|
||||
@ -129,7 +159,7 @@ async function listFolder(accessToken: string, folderPath: string): Promise<any[
|
||||
|
||||
const body = cursor
|
||||
? { cursor }
|
||||
: { path: folderPath, recursive: false, limit: 2000 };
|
||||
: { path: folderPath, recursive, limit: 2000 };
|
||||
|
||||
const resp = await fetch(url, {
|
||||
method: "POST",
|
||||
@ -154,62 +184,30 @@ async function listFolder(accessToken: string, folderPath: string): Promise<any[
|
||||
return entries;
|
||||
}
|
||||
|
||||
async function getThumbnailBatch(accessToken: string, paths: string[]): Promise<Map<string, string>> {
|
||||
async function getTemporaryLinks(accessToken: string, paths: string[]): Promise<Map<string, string>> {
|
||||
const results = new Map<string, string>();
|
||||
const batchSize = 25;
|
||||
const batchSize = 10;
|
||||
|
||||
for (let i = 0; i < paths.length; i += batchSize) {
|
||||
const batch = paths.slice(i, i + batchSize);
|
||||
const entries = batch.map((p) => ({
|
||||
path: p,
|
||||
format: "jpeg",
|
||||
size: "w256h256",
|
||||
mode: "fitone_bestfit",
|
||||
}));
|
||||
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 }),
|
||||
});
|
||||
|
||||
try {
|
||||
const resp = await fetch("https://content.dropboxapi.com/2/files/get_thumbnail_batch", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ entries }),
|
||||
});
|
||||
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
for (const entry of data.entries) {
|
||||
if (entry[".tag"] === "success") {
|
||||
results.set(entry.metadata.path_lower, `data:image/jpeg;base64,${entry.thumbnail}`);
|
||||
}
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
results.set(p.toLowerCase(), data.link);
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async function getTemporaryLinks(accessToken: string, paths: string[]): Promise<Map<string, string>> {
|
||||
const results = new Map<string, string>();
|
||||
|
||||
for (const p of paths) {
|
||||
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 {}
|
||||
} catch {}
|
||||
});
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
return results;
|
||||
@ -227,37 +225,45 @@ export async function fetchGalleryFromDropbox(): Promise<GalleryImage[]> {
|
||||
if (!accessToken) return [];
|
||||
|
||||
try {
|
||||
const topEntries = await listFolder(accessToken, DROPBOX_FOLDER);
|
||||
const folders = topEntries.filter((e: any) => e[".tag"] === "folder");
|
||||
const images: GalleryImage[] = [];
|
||||
|
||||
for (const folder of folders) {
|
||||
const folderName = folder.name;
|
||||
const folderEntries = await listFolder(accessToken, folder.path_lower);
|
||||
const imageFiles = folderEntries.filter(
|
||||
(e: any) => e[".tag"] === "file" && /\.(jpg|jpeg|png|webp|gif)$/i.test(e.name)
|
||||
);
|
||||
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 paths = imageFiles.map((f: any) => f.path_lower);
|
||||
const tempLinks = await getTemporaryLinks(accessToken, paths);
|
||||
const limitedFiles = imageFiles.slice(0, 8);
|
||||
const paths = limitedFiles.map((f: any) => f.path_lower);
|
||||
const tempLinks = await getTemporaryLinks(accessToken, paths);
|
||||
|
||||
for (const file of imageFiles) {
|
||||
const link = tempLinks.get(file.path_lower) || "";
|
||||
images.push({
|
||||
folder: folderName,
|
||||
fileName: file.name,
|
||||
thumb: link ? `${link}&size=256x256` : "",
|
||||
large: link,
|
||||
full: link,
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
galleryCache.data = images;
|
||||
galleryCache.timestamp = Date.now();
|
||||
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));
|
||||
const galleryPath = path.join(process.cwd(), "server/gallery-data.json");
|
||||
fs.writeFileSync(galleryPath, JSON.stringify(images, null, 2));
|
||||
}
|
||||
|
||||
return images;
|
||||
} catch (err: any) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user