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",
|
"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";
|
import path from "path";
|
||||||
|
|
||||||
const TOKEN_PATH = path.join(process.cwd(), "server/dropbox-token.json");
|
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 {
|
interface DropboxTokens {
|
||||||
access_token: string;
|
access_token: string;
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
expires_at: number;
|
expires_at: number;
|
||||||
|
app_key?: string;
|
||||||
|
app_secret?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GalleryImage {
|
interface GalleryImage {
|
||||||
@ -19,11 +34,13 @@ interface GalleryImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAppKey(): string {
|
function getAppKey(): string {
|
||||||
return process.env.DROPBOX_APP_KEY || "";
|
const tokens = loadTokens();
|
||||||
|
return tokens?.app_key || process.env.DROPBOX_APP_KEY || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAppSecret(): string {
|
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 {
|
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> {
|
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", {
|
const resp = await fetch("https://api.dropboxapi.com/oauth2/token", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
@ -44,8 +63,8 @@ export async function exchangeCodeForTokens(code: string, redirectUri: string):
|
|||||||
code,
|
code,
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
redirect_uri: redirectUri,
|
redirect_uri: redirectUri,
|
||||||
client_id: getAppKey(),
|
client_id: appKey,
|
||||||
client_secret: getAppSecret(),
|
client_secret: appSecret,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -59,6 +78,8 @@ export async function exchangeCodeForTokens(code: string, redirectUri: string):
|
|||||||
access_token: data.access_token,
|
access_token: data.access_token,
|
||||||
refresh_token: data.refresh_token,
|
refresh_token: data.refresh_token,
|
||||||
expires_at: Date.now() + data.expires_in * 1000,
|
expires_at: Date.now() + data.expires_in * 1000,
|
||||||
|
app_key: appKey,
|
||||||
|
app_secret: appSecret,
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2));
|
fs.writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2));
|
||||||
@ -75,14 +96,16 @@ function loadTokens(): DropboxTokens | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function refreshAccessToken(refreshToken: string): Promise<DropboxTokens> {
|
async function refreshAccessToken(refreshToken: string): Promise<DropboxTokens> {
|
||||||
|
const appKey = getAppKey();
|
||||||
|
const appSecret = getAppSecret();
|
||||||
const resp = await fetch("https://api.dropboxapi.com/oauth2/token", {
|
const resp = await fetch("https://api.dropboxapi.com/oauth2/token", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
grant_type: "refresh_token",
|
grant_type: "refresh_token",
|
||||||
refresh_token: refreshToken,
|
refresh_token: refreshToken,
|
||||||
client_id: getAppKey(),
|
client_id: appKey,
|
||||||
client_secret: getAppSecret(),
|
client_secret: appSecret,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -96,6 +119,8 @@ async function refreshAccessToken(refreshToken: string): Promise<DropboxTokens>
|
|||||||
access_token: data.access_token,
|
access_token: data.access_token,
|
||||||
refresh_token: refreshToken,
|
refresh_token: refreshToken,
|
||||||
expires_at: Date.now() + data.expires_in * 1000,
|
expires_at: Date.now() + data.expires_in * 1000,
|
||||||
|
app_key: appKey,
|
||||||
|
app_secret: appSecret,
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2));
|
fs.writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2));
|
||||||
@ -107,7 +132,12 @@ async function getValidAccessToken(): Promise<string | null> {
|
|||||||
if (!tokens) return null;
|
if (!tokens) return null;
|
||||||
|
|
||||||
if (Date.now() > tokens.expires_at - 60000) {
|
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;
|
return tokens.access_token;
|
||||||
@ -117,7 +147,7 @@ export function isConnected(): boolean {
|
|||||||
return loadTokens() !== null;
|
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[] = [];
|
const entries: any[] = [];
|
||||||
let cursor: string | null = null;
|
let cursor: string | null = null;
|
||||||
let hasMore = true;
|
let hasMore = true;
|
||||||
@ -129,7 +159,7 @@ async function listFolder(accessToken: string, folderPath: string): Promise<any[
|
|||||||
|
|
||||||
const body = cursor
|
const body = cursor
|
||||||
? { cursor }
|
? { cursor }
|
||||||
: { path: folderPath, recursive: false, limit: 2000 };
|
: { path: folderPath, recursive, limit: 2000 };
|
||||||
|
|
||||||
const resp = await fetch(url, {
|
const resp = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -154,62 +184,30 @@ async function listFolder(accessToken: string, folderPath: string): Promise<any[
|
|||||||
return entries;
|
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 results = new Map<string, string>();
|
||||||
const batchSize = 25;
|
const batchSize = 10;
|
||||||
|
|
||||||
for (let i = 0; i < paths.length; i += batchSize) {
|
for (let i = 0; i < paths.length; i += batchSize) {
|
||||||
const batch = paths.slice(i, i + batchSize);
|
const batch = paths.slice(i, i + batchSize);
|
||||||
const entries = batch.map((p) => ({
|
const promises = batch.map(async (p) => {
|
||||||
path: p,
|
try {
|
||||||
format: "jpeg",
|
const resp = await fetch("https://api.dropboxapi.com/2/files/get_temporary_link", {
|
||||||
size: "w256h256",
|
method: "POST",
|
||||||
mode: "fitone_bestfit",
|
headers: {
|
||||||
}));
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ path: p }),
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
if (resp.ok) {
|
||||||
const resp = await fetch("https://content.dropboxapi.com/2/files/get_thumbnail_batch", {
|
const data = await resp.json();
|
||||||
method: "POST",
|
results.set(p.toLowerCase(), data.link);
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} catch {}
|
||||||
} catch {}
|
});
|
||||||
}
|
await Promise.all(promises);
|
||||||
|
|
||||||
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 {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
@ -227,37 +225,45 @@ export async function fetchGalleryFromDropbox(): Promise<GalleryImage[]> {
|
|||||||
if (!accessToken) return [];
|
if (!accessToken) return [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const topEntries = await listFolder(accessToken, DROPBOX_FOLDER);
|
|
||||||
const folders = topEntries.filter((e: any) => e[".tag"] === "folder");
|
|
||||||
const images: GalleryImage[] = [];
|
const images: GalleryImage[] = [];
|
||||||
|
|
||||||
for (const folder of folders) {
|
for (const folderPath of GALLERY_FOLDERS) {
|
||||||
const folderName = folder.name;
|
try {
|
||||||
const folderEntries = await listFolder(accessToken, folder.path_lower);
|
const entries = await listFolder(accessToken, folderPath, true);
|
||||||
const imageFiles = folderEntries.filter(
|
const imageFiles = entries.filter(
|
||||||
(e: any) => e[".tag"] === "file" && /\.(jpg|jpeg|png|webp|gif)$/i.test(e.name)
|
(e: any) => e[".tag"] === "file" && /\.(jpg|jpeg|png|webp|gif)$/i.test(e.name)
|
||||||
);
|
);
|
||||||
|
|
||||||
const paths = imageFiles.map((f: any) => f.path_lower);
|
const limitedFiles = imageFiles.slice(0, 8);
|
||||||
const tempLinks = await getTemporaryLinks(accessToken, paths);
|
const paths = limitedFiles.map((f: any) => f.path_lower);
|
||||||
|
const tempLinks = await getTemporaryLinks(accessToken, paths);
|
||||||
|
|
||||||
for (const file of imageFiles) {
|
const folderName = folderPath.split("/").pop() || folderPath;
|
||||||
const link = tempLinks.get(file.path_lower) || "";
|
|
||||||
images.push({
|
for (const file of limitedFiles) {
|
||||||
folder: folderName,
|
const link = tempLinks.get(file.path_lower) || "";
|
||||||
fileName: file.name,
|
if (link) {
|
||||||
thumb: link ? `${link}&size=256x256` : "",
|
images.push({
|
||||||
large: link,
|
folder: folderName,
|
||||||
full: link,
|
fileName: file.name,
|
||||||
});
|
thumb: link,
|
||||||
|
large: link,
|
||||||
|
full: link,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(`Folder ${folderPath} error:`, err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryCache.data = images;
|
if (images.length > 0) {
|
||||||
galleryCache.timestamp = Date.now();
|
galleryCache.data = images;
|
||||||
|
galleryCache.timestamp = Date.now();
|
||||||
|
|
||||||
const galleryPath = path.join(process.cwd(), "server/gallery-data.json");
|
const galleryPath = path.join(process.cwd(), "server/gallery-data.json");
|
||||||
fs.writeFileSync(galleryPath, JSON.stringify(images, null, 2));
|
fs.writeFileSync(galleryPath, JSON.stringify(images, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
return images;
|
return images;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user