Updated files path to dynamically load new files.
This commit is contained in:
1 parent
ead6b5a9f7
commit
db6303e206
14 files changed
+77
-36
No files matched your search
@@ -1,10 +1,14 @@
|
|||||||
// app/actions/files.ts
|
// app/actions/files.ts
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
|
import crypto from 'crypto';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import fsPromises from 'fs/promises';
|
import fsPromises from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import crypto from 'crypto';
|
|
||||||
|
const isProd = process.env.NODE_ENV === "production";
|
||||||
|
const DATA_DIR = isProd ? "/app/data" : path.join(process.cwd(), "app/data");
|
||||||
|
const FILES_DIR = path.join(DATA_DIR, "files");
|
||||||
|
|
||||||
const generateFileHash = (filePath: string): Promise<string> => {
|
const generateFileHash = (filePath: string): Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -19,15 +23,15 @@ const generateFileHash = (filePath: string): Promise<string> => {
|
|||||||
|
|
||||||
export async function getLocalFileMeta(fileUrl: string) {
|
export async function getLocalFileMeta(fileUrl: string) {
|
||||||
try {
|
try {
|
||||||
const cleanPath = fileUrl.startsWith('/') ? fileUrl.substring(1) : fileUrl;
|
const cleanName = decodeURIComponent(fileUrl.split('/').pop() || '');
|
||||||
const fullPath = path.join(process.cwd(), 'public', cleanPath);
|
const fullPath = path.join(FILES_DIR, cleanName);
|
||||||
const stats = await fsPromises.stat(fullPath);
|
const stats = await fsPromises.stat(fullPath);
|
||||||
const sizeMb = (stats.size / (1024 * 1024)).toFixed(2) + ' MB';
|
const sizeMb = (stats.size / (1024 * 1024)).toFixed(2) + ' MB';
|
||||||
const fileHash = await generateFileHash(fullPath);
|
const fileHash = await generateFileHash(fullPath);
|
||||||
|
|
||||||
return { size: sizeMb, version: fileHash };
|
return { size: sizeMb, version: fileHash };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Kunde inte läsa filen: ${fileUrl}`, error);
|
console.error(`Kunde inte läsa filen (actions): ${fileUrl}`, error);
|
||||||
return { size: "Okänd", version: "v1" };
|
return { size: "Okänd", version: "v1" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,8 +30,8 @@ export function DocumentCard({ doc, isOffline, isCached }: DocumentCardProps) {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className={`group flex items-center justify-between p-4 rounded-2xl border transition-colors ${isOffline && !isCached
|
className={`group flex items-center justify-between p-4 rounded-2xl border transition-colors ${isOffline && !isCached
|
||||||
? 'opacity-50 grayscale cursor-not-allowed pointer-events-none border-transparent bg-eggshell/50'
|
? 'opacity-50 grayscale cursor-not-allowed pointer-events-none border-transparent bg-eggshell/50'
|
||||||
: 'bg-white/50 hover:bg-white border-slate-teal/10'
|
: 'bg-white/50 hover:bg-white border-slate-teal/10'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center text-slate-teal">
|
<div className="flex items-center text-slate-teal">
|
||||||
@@ -74,8 +74,8 @@ export function ExternalLinkCard({ link, isOffline }: ExternalLinkCardProps) {
|
|||||||
target={isOffline ? '_self' : '_blank'}
|
target={isOffline ? '_self' : '_blank'}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className={`group flex items-center justify-between p-4 rounded-2xl border transition-colors ${isOffline
|
className={`group flex items-center justify-between p-4 rounded-2xl border transition-colors ${isOffline
|
||||||
? 'opacity-40 grayscale cursor-not-allowed pointer-events-none bg-eggshell/50 border-transparent'
|
? 'opacity-40 grayscale cursor-not-allowed pointer-events-none bg-eggshell/50 border-transparent'
|
||||||
: 'bg-white/50 hover:bg-white border-slate-teal/10'
|
: 'bg-white/50 hover:bg-white border-slate-teal/10'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="font-bold text-slate-teal text-sm leading-tight pr-4">{link.title}</span>
|
<span className="font-bold text-slate-teal text-sm leading-tight pr-4">{link.title}</span>
|
||||||
|
|||||||
+20
-6
@@ -3,32 +3,42 @@
|
|||||||
{
|
{
|
||||||
"title": "Turistkarta",
|
"title": "Turistkarta",
|
||||||
"icon": "Map",
|
"icon": "Map",
|
||||||
"file": "/files/Turistkarta - Kullaberg.pdf"
|
"file": "Turistkarta - Kullaberg.pdf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Orienteringskarta",
|
"title": "Orienteringskarta",
|
||||||
"icon": "MapPlus",
|
"icon": "MapPlus",
|
||||||
"file": "/files/Orienteringskarta - Kullaberg.pdf"
|
"file": "Orienteringskarta - Kullaberg.pdf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Badplatser att besöka",
|
"title": "Badplatser att besöka",
|
||||||
"icon": "WavesLadder",
|
"icon": "WavesLadder",
|
||||||
"file": "/files/Badplatser på Kullaberg.pdf"
|
"file": "Badplatser på Kullaberg.pdf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Vandringsrutter",
|
"title": "Vandringsrutter",
|
||||||
"icon": "MapPinned",
|
"icon": "MapPinned",
|
||||||
"file": "/files/Vandringsrutter.pdf"
|
"file": "Vandringsrutter.pdf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Underlag för guidediplomering",
|
"title": "Underlag för guidediplomering",
|
||||||
"icon": "BookCopy",
|
"icon": "BookCopy",
|
||||||
"file": "/files/Underlag för guidediplomering.pdf"
|
"file": "Underlag för guidediplomering.pdf"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Destinationskunskap Kullahalvön",
|
"title": "Destinationskunskap Kullahalvön",
|
||||||
"icon": "BookCopy",
|
"icon": "BookCopy",
|
||||||
"file": "/files/Destinationskunskap.pdf"
|
"file": "Destinationskunskap.pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Tjänstgöringsrapport",
|
||||||
|
"icon": "BookCopy",
|
||||||
|
"file": "Tjänstgöringsrapport 2026.pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Information om ditt sommarjobb",
|
||||||
|
"icon": "BookCopy",
|
||||||
|
"file": "Information om ditt sommarjobb 2026.pdf"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [
|
"links": [
|
||||||
@@ -48,6 +58,10 @@
|
|||||||
"title": "Skåneleden",
|
"title": "Skåneleden",
|
||||||
"url": "https://www.skaneleden.se/en"
|
"url": "https://www.skaneleden.se/en"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "RSNV Brandriskprognos",
|
||||||
|
"url": "https://rsnv.se/brandriskprognos/"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Väderprognos Mölle",
|
"title": "Väderprognos Mölle",
|
||||||
"url": "https://www.smhi.se/vader/prognoser-och-varningar/vaderprognos/q/H%C3%B6gan%C3%A4s/M%C3%B6lle/2691501"
|
"url": "https://www.smhi.se/vader/prognoser-och-varningar/vaderprognos/q/H%C3%B6gan%C3%A4s/M%C3%B6lle/2691501"
|
||||||
|
|||||||
File renamed without changes.
Binary file not shown.
Binary file not shown.
File renamed without changes.
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-22
@@ -19,27 +19,6 @@ const IconMap: Record<string, any> = {
|
|||||||
"FileText": FileText
|
"FileText": FileText
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchFileMeta = async (url: string) => {
|
|
||||||
try {
|
|
||||||
const res = await fetch(url, { method: 'HEAD', cache: 'no-store' });
|
|
||||||
const bytes = res.headers.get('content-length');
|
|
||||||
const lastModified = res.headers.get('last-modified');
|
|
||||||
const etag = res.headers.get('etag');
|
|
||||||
|
|
||||||
let sizeStr = "Okänd";
|
|
||||||
if (bytes) {
|
|
||||||
const mb = (parseInt(bytes) / (1024 * 1024)).toFixed(2);
|
|
||||||
sizeStr = `${mb} MB`;
|
|
||||||
}
|
|
||||||
const version = etag ? etag.replace(/"/g, '') :
|
|
||||||
(lastModified ? new Date(lastModified).getTime().toString() : 'v1');
|
|
||||||
|
|
||||||
return { size: sizeStr, version };
|
|
||||||
} catch (error) {
|
|
||||||
return { size: "Okänd", version: "v1" };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Documents() {
|
export default function Documents() {
|
||||||
const [isHydrated, setIsHydrated] = useState(false);
|
const [isHydrated, setIsHydrated] = useState(false);
|
||||||
const [isOffline, setIsOffline] = useState(false);
|
const [isOffline, setIsOffline] = useState(false);
|
||||||
@@ -73,7 +52,8 @@ export default function Documents() {
|
|||||||
const docsWithMeta = await Promise.all(
|
const docsWithMeta = await Promise.all(
|
||||||
(res.data.docs || []).map(async (doc: DocumentItem) => {
|
(res.data.docs || []).map(async (doc: DocumentItem) => {
|
||||||
const meta = await getLocalFileMeta(doc.file);
|
const meta = await getLocalFileMeta(doc.file);
|
||||||
const versionedUrl = `${doc.file}?v=${meta.version}`;
|
const fileNameOnly = doc.file.split('/').pop();
|
||||||
|
const versionedUrl = `/files/${fileNameOnly}?v=${meta.version}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...doc,
|
...doc,
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// app/files/[...slug]/route.ts
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const isProd = process.env.NODE_ENV === "production";
|
||||||
|
const DATA_DIR = isProd ? "/app/data" : path.join(process.cwd(), "app/data");
|
||||||
|
const FILES_DIR = path.join(DATA_DIR, "files");
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
request: NextRequest,
|
||||||
|
{ params }: { params: Promise<{ slug: string[] }> }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const resolvedParams = await params;
|
||||||
|
const filename = decodeURIComponent(resolvedParams.slug[resolvedParams.slug.length - 1]);
|
||||||
|
const filePath = path.join(FILES_DIR, filename);
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.error(`404 - Filen finns inte på disk: ${filePath}`);
|
||||||
|
return new NextResponse('Filen hittades inte', { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileBuffer = fs.readFileSync(filePath);
|
||||||
|
|
||||||
|
let contentType = 'application/pdf';
|
||||||
|
if (filename.toLowerCase().endsWith('.png')) contentType = 'image/png';
|
||||||
|
else if (filename.toLowerCase().endsWith('.jpg') || filename.toLowerCase().endsWith('.jpeg')) contentType = 'image/jpeg';
|
||||||
|
else if (filename.toLowerCase().endsWith('.doc') || filename.toLowerCase().endsWith('.docx')) contentType = 'application/msword';
|
||||||
|
|
||||||
|
return new NextResponse(fileBuffer, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': contentType,
|
||||||
|
'Content-Disposition': `inline; filename="${filename}"`
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Fel vid fildelning:", error);
|
||||||
|
return new NextResponse('Ett internt serverfel uppstod', { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ const nextConfig: NextConfig = {
|
|||||||
output: "standalone",
|
output: "standalone",
|
||||||
allowedDevOrigins: [
|
allowedDevOrigins: [
|
||||||
'10.10.0.121',
|
'10.10.0.121',
|
||||||
|
'10.11.0.122',
|
||||||
'localhost',
|
'localhost',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user