5 Commits
16 changed files with 118 additions and 60 deletions
+41
View File
@@ -0,0 +1,41 @@
name: Publish Docker image
on:
release:
types: [published]
jobs:
push_to_registry:
name: Build and Push Docker image
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: git.stws.cc
username: ${{ github.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: git.stws.cc/${{ github.repository }}
tags: |
type=ref,event=tag
type=raw,value=latest
- name: Build and push Docker images
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
-24
View File
@@ -1,24 +0,0 @@
stages:
- Build
"Build Docker":
stage: Build
image: docker:24.0.5
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
script:
- echo "Building Docker-image..."
- >
docker build
--label "org.opencontainers.image.url=$CI_PROJECT_URL"
--label "org.opencontainers.image.source=$CI_PROJECT_URL"
-t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
-t $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
- docker push $CI_REGISTRY_IMAGE:latest
rules:
- if: $CI_COMMIT_TAG
+8 -4
View File
@@ -1,10 +1,14 @@
// app/actions/files.ts
'use server';
import crypto from 'crypto';
import fs from 'fs';
import fsPromises from 'fs/promises';
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> => {
return new Promise((resolve, reject) => {
@@ -19,15 +23,15 @@ const generateFileHash = (filePath: string): Promise<string> => {
export async function getLocalFileMeta(fileUrl: string) {
try {
const cleanPath = fileUrl.startsWith('/') ? fileUrl.substring(1) : fileUrl;
const fullPath = path.join(process.cwd(), 'public', cleanPath);
const cleanName = decodeURIComponent(fileUrl.split('/').pop() || '');
const fullPath = path.join(FILES_DIR, cleanName);
const stats = await fsPromises.stat(fullPath);
const sizeMb = (stats.size / (1024 * 1024)).toFixed(2) + ' MB';
const fileHash = await generateFileHash(fullPath);
return { size: sizeMb, version: fileHash };
} 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" };
}
}
+4 -4
View File
@@ -30,8 +30,8 @@ export function DocumentCard({ doc, isOffline, isCached }: DocumentCardProps) {
target="_blank"
rel="noopener noreferrer"
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'
: 'bg-white/50 hover:bg-white border-slate-teal/10'
? 'opacity-50 grayscale cursor-not-allowed pointer-events-none border-transparent bg-eggshell/50'
: 'bg-white/50 hover:bg-white border-slate-teal/10'
}`}
>
<div className="flex items-center text-slate-teal">
@@ -74,8 +74,8 @@ export function ExternalLinkCard({ link, isOffline }: ExternalLinkCardProps) {
target={isOffline ? '_self' : '_blank'}
rel="noopener noreferrer"
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'
: 'bg-white/50 hover:bg-white border-slate-teal/10'
? 'opacity-40 grayscale cursor-not-allowed pointer-events-none bg-eggshell/50 border-transparent'
: '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>
+20 -6
View File
@@ -3,32 +3,42 @@
{
"title": "Turistkarta",
"icon": "Map",
"file": "/files/Turistkarta - Kullaberg.pdf"
"file": "Turistkarta - Kullaberg.pdf"
},
{
"title": "Orienteringskarta",
"icon": "MapPlus",
"file": "/files/Orienteringskarta - Kullaberg.pdf"
"file": "Orienteringskarta - Kullaberg.pdf"
},
{
"title": "Badplatser att besöka",
"icon": "WavesLadder",
"file": "/files/Badplatser på Kullaberg.pdf"
"file": "Badplatser på Kullaberg.pdf"
},
{
"title": "Vandringsrutter",
"icon": "MapPinned",
"file": "/files/Vandringsrutter.pdf"
"file": "Vandringsrutter.pdf"
},
{
"title": "Underlag för guidediplomering",
"icon": "BookCopy",
"file": "/files/Underlag för guidediplomering.pdf"
"file": "Underlag för guidediplomering.pdf"
},
{
"title": "Destinationskunskap Kullahalvön",
"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": [
@@ -48,6 +58,10 @@
"title": "Skåneleden",
"url": "https://www.skaneleden.se/en"
},
{
"title": "RSNV Brandriskprognos",
"url": "https://rsnv.se/brandriskprognos/"
},
{
"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"
Binary file not shown.
+2 -22
View File
@@ -19,27 +19,6 @@ const IconMap: Record<string, any> = {
"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() {
const [isHydrated, setIsHydrated] = useState(false);
const [isOffline, setIsOffline] = useState(false);
@@ -73,7 +52,8 @@ export default function Documents() {
const docsWithMeta = await Promise.all(
(res.data.docs || []).map(async (doc: DocumentItem) => {
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 {
...doc,
+42
View File
@@ -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 });
}
}
+1
View File
@@ -23,6 +23,7 @@ const nextConfig: NextConfig = {
output: "standalone",
allowedDevOrigins: [
'10.10.0.121',
'10.11.0.122',
'localhost',
],
};