filament uses postgres

This commit is contained in:
2025-08-18 22:28:57 +02:00
parent db03c8a375
commit 8c79806318
11 changed files with 334 additions and 238 deletions

View File

@@ -96,6 +96,7 @@ steps:
commands: commands:
- mkdir -p /root/.kube - mkdir -p /root/.kube
- echo "IMAGE=ghcr.io/kevinmidboe/${DRONE_REPO_NAME}:${DRONE_COMMIT_SHA}" > /root/.kube/.env - echo "IMAGE=ghcr.io/kevinmidboe/${DRONE_REPO_NAME}:${DRONE_COMMIT_SHA}" > /root/.kube/.env
- echo "VARNISH_IMAGE=ghcr.io/kevinmidboe/varnish-${DRONE_REPO_NAME}" >> /root/.kube/.env
- echo "NAMESPACE=${DRONE_REPO_NAME}" >> /root/.kube/.env - echo "NAMESPACE=${DRONE_REPO_NAME}" >> /root/.kube/.env
- 'curl -s - 'curl -s
-H "X-Vault-Token: $VAULT_TOKEN" -H "X-Vault-Token: $VAULT_TOKEN"
@@ -145,6 +146,6 @@ volumes:
temp: {} temp: {}
--- ---
kind: signature kind: signature
hmac: 8e51cded679321781a79fa1f393fe3c23b56426129aa557eee6f14f4f2618076 hmac: b3cc991813f340024e65c68d5509cb23025796914a2e2ac72f71657a347e0708
... ...

View File

@@ -14,3 +14,4 @@ data:
HTTP_HEALTH_ENDPOINTS: ${HTTP_HEALTH_ENDPOINTS} HTTP_HEALTH_ENDPOINTS: ${HTTP_HEALTH_ENDPOINTS}
KUBERNETES_SERVICE_HOST: ${KUBERNETES_SERVICE_HOST} KUBERNETES_SERVICE_HOST: ${KUBERNETES_SERVICE_HOST}
KUBERNETES_SA_TOKEN: ${KUBERNETES_SA_TOKEN} KUBERNETES_SA_TOKEN: ${KUBERNETES_SA_TOKEN}
DATABASE_URL: ${DATABASE_URL}

View File

@@ -1,97 +1,8 @@
---
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: varnish-vcl name: varnish-vcl
namespace: ${NAMESPACE} namespace: ${NAMESPACE}
data: binaryData:
default.vcl: |- default.vcl: dmNsIDQuMDsKCmltcG9ydCBzdGQ7CmltcG9ydCBkaWdlc3Q7CgojIERlZmluZSBiYWNrZW5kIHBvaW50aW5nIHRvIEhvbWUgQXNzaXN0YW50IElQCmJhY2tlbmQgaGFzc19iYWNrZW5kIHsKICAgIC5ob3N0ID0gIjEwLjAuMC44MiI7CiAgICAucG9ydCA9ICI4MTIzIjsKfQoKc3ViIHZjbF9yZWN2IHsKICAgICMgSGFuZGxlIENPUlMgcHJlZmxpZ2h0CiAgICBpZiAocmVxLm1ldGhvZCA9PSAiT1BUSU9OUyIpIHsKICAgICAgICByZXR1cm4gKHN5bnRoKDIwNCwgIlByZWZsaWdodCIpKTsKICAgIH0KCiAgICAjIFJld3JpdGUgaW1hZ2UgVVJMCiAgICBpZiAocmVxLnVybCB+ICJeL2ltYWdlLyIpIHsKICAgICAgICAjIEV4dHJhY3QgZXZlcnl0aGluZyBhZnRlciAvaW1hZ2UvIGFuZCBzdG9yZSBpdAogICAgICAgIHNldCByZXEuaHR0cC5YLUltYWdlLVVSTCA9IHJlZ3N1YihyZXEudXJsLCAiXi9pbWFnZS8oLiopIiwgIlwxIik7CiAgICAgICAgIyBSZXdyaXRlIHJlcS51cmwgdG8gbWF0Y2ggYmFja2VuZCBleHBlY3RhdGlvbnMKICAgICAgICBzZXQgcmVxLnVybCA9IHJlZ3N1YihyZXEuaHR0cC5YLUltYWdlLVVSTCwgIl5odHRwOi8vW14vXSsiLCAiIik7CiAgICB9CgogICAgIyBSZW1vdmUgY29va2llcyBzbyBjb250ZW50IGlzIGNhY2hlYWJsZQogICAgdW5zZXQgcmVxLmh0dHAuQ29va2llOwp9CgpzdWIgdmNsX3N5bnRoIHsKICAgIGlmIChyZXNwLnN0YXR1cyA9PSAyMDQpIHsKICAgICAgICBzZXQgcmVzcC5odHRwLkFjY2Vzcy1Db250cm9sLUFsbG93LU9yaWdpbiA9ICIqIjsKICAgICAgICBzZXQgcmVzcC5odHRwLkFjY2Vzcy1Db250cm9sLUFsbG93LU1ldGhvZHMgPSAiR0VULCBPUFRJT05TIjsKICAgICAgICBzZXQgcmVzcC5odHRwLkFjY2Vzcy1Db250cm9sLUFsbG93LUhlYWRlcnMgPSAiQ29udGVudC1UeXBlLCBYLUNhY2hlLUlEIjsKICAgICAgICBzZXQgcmVzcC5odHRwLkNvbnRlbnQtTGVuZ3RoID0gIjAiOwogICAgICAgIHJldHVybiAoZGVsaXZlcik7CiAgICB9CgogICAgaWYgKHJlc3Auc3RhdHVzID09IDMwNCkgewogICAgICAgIHNldCByZXNwLmh0dHAuRVRhZyA9IHJlcS5odHRwLklmLU5vbmUtTWF0Y2g7CiAgICAgICAgc2V0IHJlc3AuaHR0cC5Db250ZW50LUxlbmd0aCA9ICIwIjsKICAgICAgICByZXR1cm4gKGRlbGl2ZXIpOwogICAgfQp9CgpzdWIgdmNsX2JhY2tlbmRfZmV0Y2ggewogICAgIyBBbHdheXMgdXNlIHRoZSBIQVNTIGJhY2tlbmQKICAgIHNldCBiZXJlcS5iYWNrZW5kID0gaGFzc19iYWNrZW5kOwoKICAgICMgU2V0IHByb3BlciBIb3N0IGhlYWRlciBmcm9tIG9yaWdpbmFsIFVSTAogICAgIyBpZiAoYmVyZXEuaHR0cC5YLUltYWdlLVVSTCkgewogICAgIyAgICAgc2V0IGJlcmVxLmh0dHAuSG9zdCA9IHJlZ3N1YihiZXJlcS5odHRwLlgtSW1hZ2UtVVJMLCAiXmh0dHA6Ly8oW14vXSspLioiLCAiXDEiKTsKICAgICMgICAgIHNldCBiZXJlcS5odHRwLkhvc3QgPSByZWdzdWIoYmVyZXEuaHR0cC5Ib3N0LCAiOlswLTldKyQiLCAiIik7CiAgICAjIH0KfQoKc3ViIHZjbF9iYWNrZW5kX3Jlc3BvbnNlIHsKICAgIHNldCBiZXJlc3AudHRsID0gMXM7CiAgICBzZXQgYmVyZXNwLmdyYWNlID0gNjBzOwogICAgc2V0IGJlcmVzcC5rZWVwID0gNjBzOwoKICAgICMgRW5zdXJlIEVUYWcgaXMgcGFzc2VkIHRvIGNsaWVudAogICAgaWYgKGJlcmVzcC5odHRwLkVUYWcpIHsKICAgICAgICBzZXQgYmVyZXNwLmh0dHAuWC1DYWNoZS1FVGFnID0gYmVyZXNwLmh0dHAuRVRhZzsKICAgIH0gZWxzZSB7CiAgICAgICAgIyBPcHRpb25hbDogZ2VuZXJhdGUgb25lIGlmIG5vdCBwcm92aWRlZAogICAgICAgICMgc2V0IGJlcmVzcC5odHRwLkVUYWcgPSBkaWdlc3QuaGFzaF9tZDUoYmVyZXNwLmJvZHkpOwogICAgICAgIHNldCBiZXJlc3AuaHR0cC5FVGFnID0gYmVyZXNwLmh0dHAuQ29udGVudC1MZW5ndGg7CiAgICAgICAgc2V0IGJlcmVzcC5odHRwLlgtQ2FjaGUtRVRhZyA9IGJlcmVzcC5odHRwLkVUYWc7CiAgICB9Cn0KCnN1YiB2Y2xfaGl0IHsKICAgIGlmIChvYmoudHRsIDwgMHMgJiYgc3RkLmhlYWx0aHkocmVxLmJhY2tlbmRfaGludCkpIHsKICAgICAgICByZXR1cm4gKGRlbGl2ZXIpOwogICAgfQp9CgpzdWIgdmNsX2RlbGl2ZXIgewogICAgdW5zZXQgcmVzcC5odHRwLlgtSW1hZ2UtVVJMOwogICAgc2V0IHJlc3AuaHR0cC5BY2Nlc3MtQ29udHJvbC1BbGxvdy1PcmlnaW4gPSAiKiI7CgogICAgIyBIYW5kbGUgY29uZGl0aW9uYWwgcmVxdWVzdCB3aXRoIEVUYWcKICAgIGlmICgKICAgICAgICByZXEuaHR0cC5JZi1Ob25lLU1hdGNoICYmCiAgICAgICAgcmVxLmh0dHAuSWYtTm9uZS1NYXRjaCA9PSByZXNwLmh0dHAuRVRhZwogICAgKSB7CiAgICAgICAgcmV0dXJuIChzeW50aCgzMDQpKTsKICAgIH0KfQo=
vcl 4.0;
import std;
import digest;
# Define backend pointing to Home Assistant IP
backend hass_backend {
.host = "${HOMEASSISTANT_IP}";
.port = "8123";
}
sub vcl_recv {
# Handle CORS preflight
if (req.method == "OPTIONS") {
return (synth(204, "Preflight"));
}
# Rewrite image URL
if (req.url ~ "^/image/") {
# Extract everything after /image/ and store it
set req.http.X-Image-URL = regsub(req.url, "^/image/(.*)", "\1");
# Rewrite req.url to match backend expectations
set req.url = regsub(req.http.X-Image-URL, "^http://[^/]+", "");
}
# Remove cookies so content is cacheable
unset req.http.Cookie;
}
sub vcl_synth {
if (resp.status == 204) {
set resp.http.Access-Control-Allow-Origin = "*";
set resp.http.Access-Control-Allow-Methods = "GET, OPTIONS";
set resp.http.Access-Control-Allow-Headers = "Content-Type, X-Cache-ID";
set resp.http.Content-Length = "0";
return (deliver);
}
if (resp.status == 304) {
set resp.http.ETag = req.http.If-None-Match;
set resp.http.Content-Length = "0";
return (deliver);
}
}
sub vcl_backend_fetch {
# Always use the HASS backend
set bereq.backend = hass_backend;
# Set proper Host header from original URL
# if (bereq.http.X-Image-URL) {
# set bereq.http.Host = regsub(bereq.http.X-Image-URL, "^http://([^/]+).*", "\1");
# set bereq.http.Host = regsub(bereq.http.Host, ":[0-9]+$", "");
# }
}
sub vcl_backend_response {
set beresp.ttl = 1s;
set beresp.grace = 60s;
set beresp.keep = 60s;
# Ensure ETag is passed to client
if (beresp.http.ETag) {
set beresp.http.X-Cache-ETag = beresp.http.ETag;
} else {
# Optional: generate one if not provided
# set beresp.http.ETag = digest.hash_md5(beresp.body);
set beresp.http.ETag = beresp.http.Content-Length;
set beresp.http.X-Cache-ETag = beresp.http.ETag;
}
}
sub vcl_hit {
if (obj.ttl < 0s && std.healthy(req.backend_hint)) {
return (deliver);
}
}
sub vcl_deliver {
unset resp.http.X-Image-URL;
set resp.http.Access-Control-Allow-Origin = "*";
# Handle conditional request with ETag
if (
req.http.If-None-Match &&
req.http.If-None-Match == resp.http.ETag
) {
return (synth(304));
}
}

View File

@@ -31,7 +31,7 @@ spec:
requests: requests:
cpu: 250m cpu: 250m
memory: 64Mi memory: 64Mi
- image: varnish:7.4 - image: ${VARNISH_IMAGE}:latest
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
name: varnish name: varnish
command: ['varnishd'] command: ['varnishd']

View File

@@ -16,28 +16,29 @@
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.2.5", "@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0", "@eslint/js": "^9.18.0",
"@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/kit": "^2.27.0",
"@sveltejs/vite-plugin-svelte": "^6.1.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.0.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"sass-embedded": "^1.86.0",
"svelte": "^5.38.2",
"svelte-check": "^4.0.0",
"typescript": "^5.9.0",
"typescript-eslint": "^8.20.0",
"vite": "^7.1.2",
"@kubernetes/client-node": "^1.1.0", "@kubernetes/client-node": "^1.1.0",
"@microsoft/fetch-event-source": "^2.0.1", "@microsoft/fetch-event-source": "^2.0.1",
"@sveltejs/adapter-auto": "^6.1.0", "@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/kit": "^2.27.0",
"@sveltejs/vite-plugin-svelte": "^6.1.0",
"@zerodevx/svelte-json-view": "^1.0.11", "@zerodevx/svelte-json-view": "^1.0.11",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.0.0",
"force-graph": "^1.50.1", "force-graph": "^1.50.1",
"pg": "^8.16.3",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"sass-embedded": "^1.86.0",
"sqlite": "^5.1.1", "sqlite": "^5.1.1",
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",
"sveltekit-sse": "^0.13.16" "svelte": "^5.38.2",
"svelte-check": "^4.0.0",
"sveltekit-sse": "^0.13.16",
"typescript": "^5.9.0",
"typescript-eslint": "^8.20.0",
"vite": "^7.1.2"
}, },
"dependencies": {} "dependencies": {}
} }

View File

@@ -2,7 +2,7 @@ export interface Filament {
hex: string; hex: string;
color: string; color: string;
material: string; material: string;
weight: string; weight: number;
count: number; count: number;
link: string; link: string;
created: number; created: number;

View File

@@ -1,108 +1,104 @@
import { currentFilament } from './filament'; import { currentFilament } from './filament';
import pg from 'pg';
import { open } from 'sqlite'; import { env } from '$env/dynamic/private';
import sqlite3 from 'sqlite3';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import type { Filament } from '$lib/interfaces/printer'; import type { Filament } from '$lib/interfaces/printer';
const __filename = fileURLToPath(import.meta.url); const { Pool } = pg;
const __dirname = dirname(__filename);
const dbPath = resolve(__dirname, '../../../db.sqlite');
let db; let pool: InstanceType<typeof Pool> | undefined;
async function initDb() { async function initDb() {
const db = await open({ if (pool) return pool;
filename: dbPath,
driver: sqlite3.Database pool = new Pool({
connectionString: env.DATABASE_URL // e.g. postgres://user:pass@localhost:5432/mydb
}); });
// Transaction to run schemas const client = await pool.connect();
await db.exec('BEGIN TRANSACTION');
try { try {
await client.query('BEGIN');
for (const stmt of schemas) { for (const stmt of schemas) {
await db.run(stmt); await client.query(stmt);
} }
await db.exec('COMMIT');
} catch (err) { await client.query('COMMIT');
} catch (err: any) {
console.error('Failed to create tables:', err.message); console.error('Failed to create tables:', err.message);
await db.exec('ROLLBACK'); await client.query('ROLLBACK');
throw err;
} finally {
client.release();
} }
return db; return pool;
} }
const schemas = [ const schemas = [
` `
CREATE TABLE IF NOT EXISTS filament ( CREATE TABLE IF NOT EXISTS filament (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
hex TEXT NOT NULL, hex TEXT NOT NULL,
color TEXT NOT NULL, color TEXT NOT NULL,
material TEXT, material TEXT,
weight REAL, weight REAL,
link TEXT, link TEXT,
added INTEGER, -- epoch seconds added INTEGER, -- epoch seconds
updated INTEGER -- epoch seconds updated INTEGER, -- epoch seconds
UNIQUE (hex, updated)
) )
` `
]; ];
async function seedData(db) { async function seedData(pool: InstanceType<typeof Pool>) {
const baseTimestamp = Math.floor(new Date('2025-04-01T05:47:01+00:00').getTime() / 1000); const baseTimestamp = Math.floor(new Date('2025-04-01T05:47:01+00:00').getTime() / 1000);
const filaments = currentFilament(); const filaments = currentFilament();
const stmt = await db.prepare(` const client = await pool.connect();
INSERT OR IGNORE INTO filament (hex, color, material, weight, link, added, updated)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);
await db.exec('BEGIN TRANSACTION');
try { try {
for (const f of filaments) { await client.query('BEGIN');
const existing = await db.get('SELECT 1 FROM filament WHERE hex = ? AND updated = ?', [
f.hex,
baseTimestamp
]);
if (!existing) { for (const f of filaments) {
await db.run( await client.query(
`INSERT INTO filament (hex, color, material, weight, link, added, updated) `INSERT INTO filament (hex, color, material, weight, link, added, updated)
VALUES (?, ?, ?, ?, ?, ?, ?)`, VALUES ($1, $2, $3, $4, $5, $6, $7)
[f.hex, f.color, f.material, f.weight, f.link, baseTimestamp, baseTimestamp] ON CONFLICT (hex, updated) DO NOTHING`,
); [f.hex, f.color, f.material, f.weight, f.link, baseTimestamp, baseTimestamp]
} );
} }
await db.exec('COMMIT'); await client.query('COMMIT');
} catch (err) { } catch (err: any) {
console.error('Failed to seed data:', err.message); console.error('Failed to seed data:', err.message);
await db.exec('ROLLBACK'); await client.query('ROLLBACK');
throw err;
} finally { } finally {
await stmt.finalize(); client.release();
} }
} }
// Export helper to use db elsewhere // Export helper to use db elsewhere
async function getDb() { async function getDb() {
if (db !== undefined) return db; if (pool) return pool;
db = await initDb(); const p = await initDb();
await seedData(db); await seedData(p);
console.log('Database setup and seeding complete!'); console.log('Database setup and seeding complete!');
return p;
} }
export async function getAllFilament(): Promise<Array<Filament>> { export async function getAllFilament(): Promise<Array<Filament>> {
const db = await getDb(); const pool = await getDb();
const result = await db?.all('SELECT * FROM filament'); const result = await pool.query('SELECT * FROM filament');
return result || []; return result.rows || [];
} }
export async function getFilamentByColor(name: string) { export async function getFilamentByColor(name: string) {
const db = await getDb(); const pool = await getDb();
const result = await db?.get('SELECT * FROM filament WHERE LOWER(color) = ?', [name]); const result = await pool.query('SELECT * FROM filament WHERE LOWER(color) = LOWER($1) LIMIT 1', [
return result || undefined; name
]);
return result.rows[0] || undefined;
} }
export async function addFilament( export async function addFilament(
@@ -114,22 +110,62 @@ export async function addFilament(
) { ) {
const timestamp = Math.floor(new Date().getTime() / 1000); const timestamp = Math.floor(new Date().getTime() / 1000);
const db = await getDb(); const pool = await getDb();
const result = await db.run( const result = await pool.query(
`INSERT INTO filament (hex, color, material, weight, link, added, updated) `INSERT INTO filament (hex, color, material, weight, link, added, updated)
VALUES (?, ?, ?, ?, ?, ?, ?)`, VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id`,
[hex, color, material, weight, link, timestamp, timestamp] [hex, color, material, weight, link, timestamp, timestamp]
); );
return { id: result.lastID }; return { id: result.rows[0].id };
} }
export async function updatefilament({ id, make, model, year }) { export async function updateFilament({
const db = await getDb(); id,
await db.run( hex,
'UPDATE filaments SET make = ?, model = ?, year = ? WHERE id = ?', color,
make, material,
model, weight,
year, link
id }: {
); id: number;
hex?: string;
color?: string;
material?: string;
weight?: number;
link?: string;
}) {
const pool = await getDb();
// Dynamically build query based on provided fields
const fields = [];
const values = [];
let i = 1;
if (hex !== undefined) {
fields.push(`hex = $${i++}`);
values.push(hex);
}
if (color !== undefined) {
fields.push(`color = $${i++}`);
values.push(color);
}
if (material !== undefined) {
fields.push(`material = $${i++}`);
values.push(material);
}
if (weight !== undefined) {
fields.push(`weight = $${i++}`);
values.push(weight);
}
if (link !== undefined) {
fields.push(`link = $${i++}`);
values.push(link);
}
if (fields.length === 0) return; // nothing to update
values.push(id);
const query = `UPDATE filament SET ${fields.join(', ')}, updated = EXTRACT(EPOCH FROM NOW())::INT WHERE id = $${i}`;
await pool.query(query, values);
} }

View File

@@ -2,84 +2,84 @@ import type { Filament } from '$lib/interfaces/printer';
const filament: Filament[] = [ const filament: Filament[] = [
{ {
Hex: '#DD4344', hex: '#DD4344',
Color: 'Scarlet Red', color: 'Scarlet Red',
Material: 'PLA Matte', material: 'PLA Matte',
Weight: '1kg', weight: 1,
Count: 2, count: 2,
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742848731' link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742848731'
}, },
{ {
Hex: '#61C57F', hex: '#61C57F',
Color: 'Grass Green', color: 'Grass Green',
Material: 'PLA Matte', material: 'PLA Matte',
Weight: '1kg', weight: 1,
Count: 2, count: 2,
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742783195' link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742783195'
}, },
{ {
Hex: '#F7DA5A', hex: '#F7DA5A',
Color: 'Lemon Yellow', color: 'Lemon Yellow',
Material: 'PLA Matte', material: 'PLA Matte',
Weight: '1kg', weight: 1,
Count: 2, count: 2,
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742717659' link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742717659'
}, },
{ {
Hex: '#E8DBB7', hex: '#E8DBB7',
Color: 'Desert Tan', color: 'Desert Tan',
Material: 'PLA Matte', material: 'PLA Matte',
Weight: '1kg', weight: 1,
Count: 1, count: 1,
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=48612736401756' link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=48612736401756'
}, },
{ {
Hex: "url('https://www.transparenttextures.com/patterns/asfalt-dark.png'", hex: "url('https://www.transparenttextures.com/patterns/asfalt-dark.png'",
Color: 'White Marble', color: 'White Marble',
Material: 'PLA Marble', material: 'PLA Marble',
Weight: '1kg', weight: 1,
Count: 1, count: 1,
Link: 'https://eu.store.bambulab.com/en-no/products/pla-marble?variant=43964050964699' link: 'https://eu.store.bambulab.com/en-no/products/pla-marble?variant=43964050964699'
}, },
{ {
Hex: '#0078C0', hex: '#0078C0',
Color: 'Marine Blue', color: 'Marine Blue',
Material: 'PLA Matte', material: 'PLA Matte',
Weight: '1kg', weight: 1,
Count: 1, count: 1,
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996751073499' link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996751073499'
}, },
{ {
Hex: '#000000', hex: '#000000',
Color: 'Charcoal', color: 'Charcoal',
Material: 'PLA Matte', material: 'PLA Matte',
Weight: '1kg', weight: 1,
Count: 2, count: 2,
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742750427' link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742750427'
}, },
{ {
Hex: '#ffffff', hex: '#ffffff',
Color: 'Ivory White', color: 'Ivory White',
Material: 'PLA Matte', material: 'PLA Matte',
Weight: '1kg', weight: 1,
Count: 2, count: 2,
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742586587' link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742586587'
}, },
{ {
Hex: '#E8AFCE', hex: '#E8AFCE',
Color: 'Sakura Pink', color: 'Sakura Pink',
Material: 'PLA Matte', material: 'PLA Matte',
Weight: '1kg', weight: 1,
Count: 1, count: 1,
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742684891' link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742684891'
}, },
{ {
Hex: '#AE96D5', hex: '#AE96D5',
Color: 'Lilac Purple', color: 'Lilac Purple',
Material: 'PLA Matte', material: 'PLA Matte',
Weight: '1kg', weight: 1,
Count: 1, count: 1,
Link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742914267' link: 'https://eu.store.bambulab.com/en-no/collections/pla/products/pla-matte?variant=42996742914267'
} }
]; ];

View File

@@ -0,0 +1,59 @@
import { addFilament, updateFilament } from '$lib/server/database';
import { json } from '@sveltejs/kit';
export const PUT: RequestHandler = async ({ params, request }) => {
try {
const id = Number(params.id);
if (isNaN(id)) {
return new Response(JSON.stringify({ error: 'Invalid id' }), { status: 400 });
}
const body = await request.json();
await updateFilament({ id, ...body });
return json({ success: true });
} catch (err: any) {
console.log(err);
console.error('Failed to update filament:', err.message);
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
status: 500
});
}
};
export const POST: RequestHandler = async ({ request }) => {
try {
const formData = await request.formData();
// Extract values by input `name` attributes
const hex = formData.get('Hex')?.toString().trim();
const color = formData.get('Color name')?.toString().trim();
const material = formData.get('Material')?.toString().trim();
const weightStr = formData.get('Weight')?.toString().trim();
const link = formData.get('Link')?.toString().trim();
if (!hex || !color || !material || !weightStr || !link) {
return new Response(JSON.stringify({ error: 'All fields are required' }), { status: 400 });
}
// convert "0.5 kg" → 0.5, "1 kg" → 1, etc
const weight = parseFloat(weightStr);
if (!hex || !color) {
return new Response(JSON.stringify({ error: 'hex and color are required' }), {
status: 400
});
}
await addFilament(hex, color, material, weight, link);
return Response.redirect(`${request.url}/${color}`, 303);
} catch (err: any) {
console.log(err);
console.error('Failed to add filament:', err.message);
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
status: 500
});
}
};

View File

@@ -8,6 +8,5 @@ export const load = async ({ params }: Parameters<PageServerLoad>[0]) => {
} }
const filament = await getFilamentByColor(id); const filament = await getFilamentByColor(id);
console.log('fil:', filament);
return { id, filament: filament }; return { id, filament: filament };
}; };

View File

@@ -2465,6 +2465,62 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
pg-cloudflare@^1.2.7:
version "1.2.7"
resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz#a1f3d226bab2c45ae75ea54d65ec05ac6cfafbef"
integrity sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==
pg-connection-string@^2.9.1:
version "2.9.1"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.9.1.tgz#bb1fd0011e2eb76ac17360dc8fa183b2d3465238"
integrity sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==
pg-int8@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
pg-pool@^3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.10.1.tgz#481047c720be2d624792100cac1816f8850d31b2"
integrity sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==
pg-protocol@^1.10.3:
version "1.10.3"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.10.3.tgz#ac9e4778ad3f84d0c5670583bab976ea0a34f69f"
integrity sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==
pg-types@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
dependencies:
pg-int8 "1.0.1"
postgres-array "~2.0.0"
postgres-bytea "~1.0.0"
postgres-date "~1.0.4"
postgres-interval "^1.1.0"
pg@^8.16.3:
version "8.16.3"
resolved "https://registry.yarnpkg.com/pg/-/pg-8.16.3.tgz#160741d0b44fdf64680e45374b06d632e86c99fd"
integrity sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==
dependencies:
pg-connection-string "^2.9.1"
pg-pool "^3.10.1"
pg-protocol "^1.10.3"
pg-types "2.2.0"
pgpass "1.0.5"
optionalDependencies:
pg-cloudflare "^1.2.7"
pgpass@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d"
integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==
dependencies:
split2 "^4.1.0"
picocolors@^1.0.0, picocolors@^1.1.1: picocolors@^1.0.0, picocolors@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
@@ -2515,6 +2571,28 @@ postcss@^8.4.49, postcss@^8.5.6:
picocolors "^1.1.1" picocolors "^1.1.1"
source-map-js "^1.2.1" source-map-js "^1.2.1"
postgres-array@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==
postgres-bytea@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35"
integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==
postgres-date@~1.0.4:
version "1.0.7"
resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8"
integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==
postgres-interval@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695"
integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==
dependencies:
xtend "^4.0.0"
preact@10: preact@10:
version "10.27.1" version "10.27.1"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.27.1.tgz#c391dcad5813b67d9e04655d844d8fdc307d4252" resolved "https://registry.yarnpkg.com/preact/-/preact-10.27.1.tgz#c391dcad5813b67d9e04655d844d8fdc307d4252"
@@ -2933,6 +3011,11 @@ socks@^2.6.2, socks@^2.8.3:
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
split2@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
sqlite3@^5.1.7: sqlite3@^5.1.7:
version "5.1.7" version "5.1.7"
resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7"
@@ -3315,6 +3398,11 @@ ws@^8.18.2:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yallist@^4.0.0: yallist@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"