Spaces:
Running
Running
Yogesh commited on
Commit Β·
f1c8e7a
1
Parent(s): b0baf04
Configure Space: migrate catalog and autopilot database adapter to Supabase
Browse files- package-lock.json +100 -0
- package.json +1 -0
- server.js +79 -71
package-lock.json
CHANGED
|
@@ -9,6 +9,7 @@
|
|
| 9 |
"version": "1.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
"@huggingface/inference": "^2.8.1",
|
|
|
|
| 12 |
"cors": "^2.8.5",
|
| 13 |
"dotenv": "^16.4.5",
|
| 14 |
"express": "^4.19.2",
|
|
@@ -45,6 +46,90 @@
|
|
| 45 |
"sparse-bitfield": "^3.0.3"
|
| 46 |
}
|
| 47 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
"node_modules/@types/webidl-conversions": {
|
| 49 |
"version": "7.0.3",
|
| 50 |
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
|
@@ -640,6 +725,15 @@
|
|
| 640 |
"url": "https://opencollective.com/express"
|
| 641 |
}
|
| 642 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
"node_modules/iconv-lite": {
|
| 644 |
"version": "0.4.24",
|
| 645 |
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
|
@@ -1401,6 +1495,12 @@
|
|
| 1401 |
"node": ">=18"
|
| 1402 |
}
|
| 1403 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1404 |
"node_modules/type-is": {
|
| 1405 |
"version": "1.6.18",
|
| 1406 |
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
|
|
|
| 9 |
"version": "1.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
"@huggingface/inference": "^2.8.1",
|
| 12 |
+
"@supabase/supabase-js": "^2.106.2",
|
| 13 |
"cors": "^2.8.5",
|
| 14 |
"dotenv": "^16.4.5",
|
| 15 |
"express": "^4.19.2",
|
|
|
|
| 46 |
"sparse-bitfield": "^3.0.3"
|
| 47 |
}
|
| 48 |
},
|
| 49 |
+
"node_modules/@supabase/auth-js": {
|
| 50 |
+
"version": "2.106.2",
|
| 51 |
+
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.106.2.tgz",
|
| 52 |
+
"integrity": "sha512-VcAjUErkHkhC5Jaf+g/G1qbkQrFh8edaCdHa7pxJmHUjkWKjT7UnYCtPA89XV0N0GIYRkEqJZw5V62CtOxTmBQ==",
|
| 53 |
+
"license": "MIT",
|
| 54 |
+
"dependencies": {
|
| 55 |
+
"tslib": "2.8.1"
|
| 56 |
+
},
|
| 57 |
+
"engines": {
|
| 58 |
+
"node": ">=20.0.0"
|
| 59 |
+
}
|
| 60 |
+
},
|
| 61 |
+
"node_modules/@supabase/functions-js": {
|
| 62 |
+
"version": "2.106.2",
|
| 63 |
+
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.106.2.tgz",
|
| 64 |
+
"integrity": "sha512-oRnr0QrL8H+zTO1YyQ1QjiHZU/957jvubbxSJTUm2XLAgzoGGV9Tahfyd+uvLsBLRVmXLtpU3oyCjdQIvkGMOA==",
|
| 65 |
+
"license": "MIT",
|
| 66 |
+
"dependencies": {
|
| 67 |
+
"tslib": "2.8.1"
|
| 68 |
+
},
|
| 69 |
+
"engines": {
|
| 70 |
+
"node": ">=20.0.0"
|
| 71 |
+
}
|
| 72 |
+
},
|
| 73 |
+
"node_modules/@supabase/phoenix": {
|
| 74 |
+
"version": "0.4.2",
|
| 75 |
+
"resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz",
|
| 76 |
+
"integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==",
|
| 77 |
+
"license": "MIT"
|
| 78 |
+
},
|
| 79 |
+
"node_modules/@supabase/postgrest-js": {
|
| 80 |
+
"version": "2.106.2",
|
| 81 |
+
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.106.2.tgz",
|
| 82 |
+
"integrity": "sha512-tDOzyPgp9pIRMR2x6C9+uDSJrnXSzxLtt3d7nC+Lrsy3jnJDHYfdQC/xcRyhJE/TOBJ0heSqRKR3UmejDjZxsw==",
|
| 83 |
+
"license": "MIT",
|
| 84 |
+
"dependencies": {
|
| 85 |
+
"tslib": "2.8.1"
|
| 86 |
+
},
|
| 87 |
+
"engines": {
|
| 88 |
+
"node": ">=20.0.0"
|
| 89 |
+
}
|
| 90 |
+
},
|
| 91 |
+
"node_modules/@supabase/realtime-js": {
|
| 92 |
+
"version": "2.106.2",
|
| 93 |
+
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.106.2.tgz",
|
| 94 |
+
"integrity": "sha512-LdRGT7DNhyZkPjubUv5bSdAZ0jSEX8wTHvx7htj7+K59TOZRvz4TuQK7tL2RWxyIZVeFMRluL04SzWS61rKnUA==",
|
| 95 |
+
"license": "MIT",
|
| 96 |
+
"dependencies": {
|
| 97 |
+
"@supabase/phoenix": "^0.4.2",
|
| 98 |
+
"tslib": "2.8.1"
|
| 99 |
+
},
|
| 100 |
+
"engines": {
|
| 101 |
+
"node": ">=20.0.0"
|
| 102 |
+
}
|
| 103 |
+
},
|
| 104 |
+
"node_modules/@supabase/storage-js": {
|
| 105 |
+
"version": "2.106.2",
|
| 106 |
+
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.106.2.tgz",
|
| 107 |
+
"integrity": "sha512-xgKCSYuev1YarV+iVqr+zlfgSyremnJtn8T0NCT8L4XmMv1CLtESc0Q6kNp8+mKWdX/8ND0nzm7OMKx08kwNAw==",
|
| 108 |
+
"license": "MIT",
|
| 109 |
+
"dependencies": {
|
| 110 |
+
"iceberg-js": "^0.8.1",
|
| 111 |
+
"tslib": "2.8.1"
|
| 112 |
+
},
|
| 113 |
+
"engines": {
|
| 114 |
+
"node": ">=20.0.0"
|
| 115 |
+
}
|
| 116 |
+
},
|
| 117 |
+
"node_modules/@supabase/supabase-js": {
|
| 118 |
+
"version": "2.106.2",
|
| 119 |
+
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.106.2.tgz",
|
| 120 |
+
"integrity": "sha512-2/RZ/1fmJx/MRSEDG2Xk8+J4JVk5clM9V0uSI6kUTrcS32KA89DtqI5RUOC9r6mzY3WBC9qexLjssIHjbLyVJA==",
|
| 121 |
+
"license": "MIT",
|
| 122 |
+
"dependencies": {
|
| 123 |
+
"@supabase/auth-js": "2.106.2",
|
| 124 |
+
"@supabase/functions-js": "2.106.2",
|
| 125 |
+
"@supabase/postgrest-js": "2.106.2",
|
| 126 |
+
"@supabase/realtime-js": "2.106.2",
|
| 127 |
+
"@supabase/storage-js": "2.106.2"
|
| 128 |
+
},
|
| 129 |
+
"engines": {
|
| 130 |
+
"node": ">=20.0.0"
|
| 131 |
+
}
|
| 132 |
+
},
|
| 133 |
"node_modules/@types/webidl-conversions": {
|
| 134 |
"version": "7.0.3",
|
| 135 |
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
|
|
|
| 725 |
"url": "https://opencollective.com/express"
|
| 726 |
}
|
| 727 |
},
|
| 728 |
+
"node_modules/iceberg-js": {
|
| 729 |
+
"version": "0.8.1",
|
| 730 |
+
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
|
| 731 |
+
"integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
|
| 732 |
+
"license": "MIT",
|
| 733 |
+
"engines": {
|
| 734 |
+
"node": ">=20.0.0"
|
| 735 |
+
}
|
| 736 |
+
},
|
| 737 |
"node_modules/iconv-lite": {
|
| 738 |
"version": "0.4.24",
|
| 739 |
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
|
|
|
| 1495 |
"node": ">=18"
|
| 1496 |
}
|
| 1497 |
},
|
| 1498 |
+
"node_modules/tslib": {
|
| 1499 |
+
"version": "2.8.1",
|
| 1500 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 1501 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 1502 |
+
"license": "0BSD"
|
| 1503 |
+
},
|
| 1504 |
"node_modules/type-is": {
|
| 1505 |
"version": "1.6.18",
|
| 1506 |
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
package.json
CHANGED
|
@@ -10,6 +10,7 @@
|
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
"@huggingface/inference": "^2.8.1",
|
|
|
|
| 13 |
"cors": "^2.8.5",
|
| 14 |
"dotenv": "^16.4.5",
|
| 15 |
"express": "^4.19.2",
|
|
|
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
"@huggingface/inference": "^2.8.1",
|
| 13 |
+
"@supabase/supabase-js": "^2.106.2",
|
| 14 |
"cors": "^2.8.5",
|
| 15 |
"dotenv": "^16.4.5",
|
| 16 |
"express": "^4.19.2",
|
server.js
CHANGED
|
@@ -4,7 +4,7 @@ import dotenv from 'dotenv';
|
|
| 4 |
import { HfInference } from '@huggingface/inference';
|
| 5 |
import fs from 'fs';
|
| 6 |
import path from 'path';
|
| 7 |
-
import
|
| 8 |
|
| 9 |
// Load environment variables
|
| 10 |
dotenv.config();
|
|
@@ -17,36 +17,25 @@ app.use(cors());
|
|
| 17 |
app.use(express.json());
|
| 18 |
|
| 19 |
// ----------------------------------------------------
|
| 20 |
-
// DATABASE CONFIGURATION:
|
| 21 |
// ----------------------------------------------------
|
| 22 |
|
| 23 |
-
let
|
|
|
|
| 24 |
|
| 25 |
-
//
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
})
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
// Attempt MongoDB Connection if MONGODB_URI is provided
|
| 37 |
-
if (process.env.MONGODB_URI && !process.env.MONGODB_URI.includes('cluster0.xxxx')) {
|
| 38 |
-
console.log('π Connecting to MongoDB Atlas Cloud...');
|
| 39 |
-
mongoose.connect(process.env.MONGODB_URI)
|
| 40 |
-
.then(() => {
|
| 41 |
-
isMongoConnected = true;
|
| 42 |
-
console.log('β
Connected to MongoDB Atlas Cloud Database!');
|
| 43 |
-
})
|
| 44 |
-
.catch((err) => {
|
| 45 |
-
console.error('β MongoDB Atlas Connection Error:', err.message);
|
| 46 |
-
console.log('β οΈ Falling back to Local JSON Database.');
|
| 47 |
-
});
|
| 48 |
} else {
|
| 49 |
-
console.log('βΉοΈ
|
| 50 |
}
|
| 51 |
|
| 52 |
// JSON File Database Fallback Configuration
|
|
@@ -94,7 +83,7 @@ app.get('/api/health', (req, res) => {
|
|
| 94 |
res.json({
|
| 95 |
status: 'ok',
|
| 96 |
hfConfigured: !!process.env.HF_API_KEY,
|
| 97 |
-
dbType:
|
| 98 |
dbConnected: true
|
| 99 |
});
|
| 100 |
});
|
|
@@ -104,21 +93,17 @@ app.get('/api/health', (req, res) => {
|
|
| 104 |
// ----------------------------------------------------
|
| 105 |
|
| 106 |
app.get('/api/catalog', async (req, res) => {
|
| 107 |
-
if (
|
| 108 |
try {
|
| 109 |
-
const
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
id:
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
price: book.price,
|
| 117 |
-
date: book.date
|
| 118 |
-
}));
|
| 119 |
-
return res.json(mappedCatalog);
|
| 120 |
} catch (err) {
|
| 121 |
-
console.error('Error fetching from
|
| 122 |
// Fallback
|
| 123 |
}
|
| 124 |
}
|
|
@@ -134,24 +119,24 @@ app.post('/api/catalog', async (req, res) => {
|
|
| 134 |
return res.status(400).json({ error: 'Book title is required' });
|
| 135 |
}
|
| 136 |
|
| 137 |
-
if (
|
| 138 |
try {
|
| 139 |
-
const
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
}
|
| 153 |
} catch (err) {
|
| 154 |
-
console.error('Error saving to
|
| 155 |
// Fallback
|
| 156 |
}
|
| 157 |
}
|
|
@@ -175,15 +160,17 @@ app.post('/api/catalog', async (req, res) => {
|
|
| 175 |
app.delete('/api/catalog/:id', async (req, res) => {
|
| 176 |
const { id } = req.params;
|
| 177 |
|
| 178 |
-
if (
|
| 179 |
try {
|
| 180 |
-
const
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
|
|
|
|
|
|
| 184 |
return res.json({ success: true });
|
| 185 |
} catch (err) {
|
| 186 |
-
console.error('Error deleting from
|
| 187 |
// Fallback
|
| 188 |
}
|
| 189 |
}
|
|
@@ -330,14 +317,35 @@ Start writing now:`;
|
|
| 330 |
|
| 331 |
autopilotThoughts.unshift(`[${timestamp}] π° Printing Cost Calculations: pages=120. Maximizing net margin for price $${recommendedPrice}...`);
|
| 332 |
|
| 333 |
-
if (
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
} else {
|
| 342 |
const catalog = readDb();
|
| 343 |
catalog.unshift({
|
|
|
|
| 4 |
import { HfInference } from '@huggingface/inference';
|
| 5 |
import fs from 'fs';
|
| 6 |
import path from 'path';
|
| 7 |
+
import { createClient } from '@supabase/supabase-js';
|
| 8 |
|
| 9 |
// Load environment variables
|
| 10 |
dotenv.config();
|
|
|
|
| 17 |
app.use(express.json());
|
| 18 |
|
| 19 |
// ----------------------------------------------------
|
| 20 |
+
// DATABASE CONFIGURATION: SUPABASE WITH JSON FALLBACK
|
| 21 |
// ----------------------------------------------------
|
| 22 |
|
| 23 |
+
let isSupabaseConnected = false;
|
| 24 |
+
let supabase = null;
|
| 25 |
|
| 26 |
+
// Attempt Supabase Connection if credentials are provided
|
| 27 |
+
if (process.env.SUPABASE_URL && process.env.SUPABASE_KEY && !process.env.SUPABASE_URL.includes('your-project-id')) {
|
| 28 |
+
console.log('π Connecting to Supabase Cloud Database...');
|
| 29 |
+
try {
|
| 30 |
+
supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY);
|
| 31 |
+
isSupabaseConnected = true;
|
| 32 |
+
console.log('β
Connected to Supabase Cloud Database!');
|
| 33 |
+
} catch (err) {
|
| 34 |
+
console.error('β Supabase Connection Error:', err.message);
|
| 35 |
+
console.log('β οΈ Falling back to Local JSON Database.');
|
| 36 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
} else {
|
| 38 |
+
console.log('βΉοΈ Supabase credentials not set or using template. Using Local JSON Database.');
|
| 39 |
}
|
| 40 |
|
| 41 |
// JSON File Database Fallback Configuration
|
|
|
|
| 83 |
res.json({
|
| 84 |
status: 'ok',
|
| 85 |
hfConfigured: !!process.env.HF_API_KEY,
|
| 86 |
+
dbType: isSupabaseConnected ? 'supabase' : 'local_json',
|
| 87 |
dbConnected: true
|
| 88 |
});
|
| 89 |
});
|
|
|
|
| 93 |
// ----------------------------------------------------
|
| 94 |
|
| 95 |
app.get('/api/catalog', async (req, res) => {
|
| 96 |
+
if (isSupabaseConnected) {
|
| 97 |
try {
|
| 98 |
+
const { data, error } = await supabase
|
| 99 |
+
.from('catalog')
|
| 100 |
+
.select('*')
|
| 101 |
+
.order('id', { ascending: false });
|
| 102 |
+
|
| 103 |
+
if (error) throw error;
|
| 104 |
+
return res.json(data || []);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
} catch (err) {
|
| 106 |
+
console.error('Error fetching from Supabase:', err.message);
|
| 107 |
// Fallback
|
| 108 |
}
|
| 109 |
}
|
|
|
|
| 119 |
return res.status(400).json({ error: 'Book title is required' });
|
| 120 |
}
|
| 121 |
|
| 122 |
+
if (isSupabaseConnected) {
|
| 123 |
try {
|
| 124 |
+
const { data, error } = await supabase
|
| 125 |
+
.from('catalog')
|
| 126 |
+
.insert([{
|
| 127 |
+
title,
|
| 128 |
+
subtitle: subtitle || '',
|
| 129 |
+
type: type || 'other',
|
| 130 |
+
price: price || 9.99
|
| 131 |
+
}])
|
| 132 |
+
.select();
|
| 133 |
+
|
| 134 |
+
if (error) throw error;
|
| 135 |
+
if (data && data.length > 0) {
|
| 136 |
+
return res.status(201).json(data[0]);
|
| 137 |
+
}
|
| 138 |
} catch (err) {
|
| 139 |
+
console.error('Error saving to Supabase:', err.message);
|
| 140 |
// Fallback
|
| 141 |
}
|
| 142 |
}
|
|
|
|
| 160 |
app.delete('/api/catalog/:id', async (req, res) => {
|
| 161 |
const { id } = req.params;
|
| 162 |
|
| 163 |
+
if (isSupabaseConnected) {
|
| 164 |
try {
|
| 165 |
+
const { error } = await supabase
|
| 166 |
+
.from('catalog')
|
| 167 |
+
.delete()
|
| 168 |
+
.eq('id', id);
|
| 169 |
+
|
| 170 |
+
if (error) throw error;
|
| 171 |
return res.json({ success: true });
|
| 172 |
} catch (err) {
|
| 173 |
+
console.error('Error deleting from Supabase:', err.message);
|
| 174 |
// Fallback
|
| 175 |
}
|
| 176 |
}
|
|
|
|
| 317 |
|
| 318 |
autopilotThoughts.unshift(`[${timestamp}] π° Printing Cost Calculations: pages=120. Maximizing net margin for price $${recommendedPrice}...`);
|
| 319 |
|
| 320 |
+
if (isSupabaseConnected) {
|
| 321 |
+
try {
|
| 322 |
+
const { error } = await supabase
|
| 323 |
+
.from('catalog')
|
| 324 |
+
.insert([{
|
| 325 |
+
title: parsedTitle,
|
| 326 |
+
subtitle: parsedSubtitle,
|
| 327 |
+
type: bookType,
|
| 328 |
+
price: recommendedPrice
|
| 329 |
+
}]);
|
| 330 |
+
|
| 331 |
+
if (error) throw error;
|
| 332 |
+
autopilotThoughts.unshift(`[${timestamp}] π¦ Cloud Save: Stored "${parsedTitle.slice(0, 30)}..." to Supabase!`);
|
| 333 |
+
} catch (err) {
|
| 334 |
+
console.error('Error saving to Supabase in Autopilot:', err.message);
|
| 335 |
+
autopilotThoughts.unshift(`[${timestamp}] β οΈ Cloud Save Failed. Storing to local fallback...`);
|
| 336 |
+
// Fallback
|
| 337 |
+
const catalog = readDb();
|
| 338 |
+
catalog.unshift({
|
| 339 |
+
id: Date.now().toString(),
|
| 340 |
+
title: parsedTitle,
|
| 341 |
+
subtitle: parsedSubtitle,
|
| 342 |
+
type: bookType,
|
| 343 |
+
price: recommendedPrice,
|
| 344 |
+
date: new Date().toLocaleDateString()
|
| 345 |
+
});
|
| 346 |
+
writeDb(catalog);
|
| 347 |
+
autopilotThoughts.unshift(`[${timestamp}] π¦ Database Save: Stored "${parsedTitle.slice(0, 30)}..." to Local JSON Fallback!`);
|
| 348 |
+
}
|
| 349 |
} else {
|
| 350 |
const catalog = readDb();
|
| 351 |
catalog.unshift({
|