added official hacktheboo2024 writeups
This commit is contained in:
parent
1f7a9b0566
commit
e3c46450f7
327 changed files with 14303 additions and 0 deletions
Binary file not shown.
|
@ -0,0 +1,14 @@
|
|||
FROM node:18
|
||||
|
||||
RUN apt-get update && apt-get install -y netcat-openbsd
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "start"]
|
|
@ -0,0 +1,19 @@
|
|||
version: '3.8'
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DB_PATH=/usr/src/app/data/shadowbrook.db
|
||||
volumes:
|
||||
- .:/usr/src/app
|
||||
- ./data:/usr/src/app/data # Ensure data is persistent
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: nouchka/sqlite3
|
||||
volumes:
|
||||
- ./data:/data
|
2418
htb/hacktheboo2024/forensics/[Very Easy] Forbidden Manuscript/dev/shadowbrook-library/package-lock.json
generated
Normal file
2418
htb/hacktheboo2024/forensics/[Very Easy] Forbidden Manuscript/dev/shadowbrook-library/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "shadowbrook-library",
|
||||
"version": "1.0.0",
|
||||
"description": "A spooky library of forbidden knowledge",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
"start": "node src/app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blakeembrey/template": "1.1.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.17.1",
|
||||
"sqlite3": "^5.0.0",
|
||||
"squirrelly": "^9.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
const express = require('express');
|
||||
const path = require('path');
|
||||
const bodyParser = require('body-parser');
|
||||
const manuscriptRoutes = require('./routes/manuscripts');
|
||||
const indexRoutes = require('./routes/index');
|
||||
const app = express();
|
||||
|
||||
// Middleware
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// View engine setup
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
|
||||
// Routes
|
||||
app.use('/', indexRoutes);
|
||||
app.use('/manuscripts', manuscriptRoutes);
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Shadowbrook Library is running on port ${PORT}`);
|
||||
});
|
|
@ -0,0 +1,81 @@
|
|||
const sqlite3 = require('sqlite3').verbose();
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Define the database path
|
||||
const dbDirectory = path.join(__dirname, '../data');
|
||||
const dbPath = process.env.DB_PATH || path.join(dbDirectory, 'shadowbrook.db');
|
||||
|
||||
// Ensure the database directory exists
|
||||
if (!fs.existsSync(dbDirectory)) {
|
||||
console.log(`Directory '${dbDirectory}' does not exist. Creating...`);
|
||||
fs.mkdirSync(dbDirectory, { recursive: true });
|
||||
}
|
||||
|
||||
// Connect to SQLite database
|
||||
const db = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error('Error connecting to SQLite database:', err.message);
|
||||
} else {
|
||||
console.log('Connected to SQLite database.');
|
||||
|
||||
// Create manuscripts table if it doesn't exist
|
||||
db.run(`CREATE TABLE IF NOT EXISTS manuscripts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
content TEXT NOT NULL
|
||||
)`, (err) => {
|
||||
if (err) {
|
||||
console.error('Error creating manuscripts table:', err.message);
|
||||
} else {
|
||||
console.log('Manuscripts table ready.');
|
||||
|
||||
// Initialize database with creepy Halloween-themed manuscripts
|
||||
const manuscripts = [
|
||||
{
|
||||
title: "The Whispering Shadows",
|
||||
author: "Unknown",
|
||||
content: "In the dead of night, the shadows in the old library seem to move, whispering secrets of those long forgotten. Some say they hear faint voices calling their name..."
|
||||
},
|
||||
{
|
||||
title: "The Cursed Pumpkin",
|
||||
author: "Edgar Fallow",
|
||||
content: "Every year, the pumpkin with a face twisted in agony reappears on the doorstep of the abandoned house. Its origins remain unknown, but those who dare carve it disappear without a trace."
|
||||
},
|
||||
{
|
||||
title: "The Haunting of Willow Lane",
|
||||
author: "Mary Blackthorn",
|
||||
content: "A house once full of life now stands as a decrepit shell. At midnight, the laughter of children echoes through the halls, though none have lived there for decades..."
|
||||
},
|
||||
{
|
||||
title: "The Witch's Grimoire",
|
||||
author: "Seraphina Nightshade",
|
||||
content: "Bound in human skin, the Witch's Grimoire contains forbidden spells. It is said those who read its pages under a blood moon shall be granted immense power — but at a terrible cost."
|
||||
},
|
||||
{
|
||||
title: "The Mask of Eternal Night",
|
||||
author: "Mortimer Graves",
|
||||
content: "A porcelain mask with hollow eyes sits locked away in a museum. Legends tell that wearing it opens a portal to a realm of endless darkness, where souls are consumed for eternity."
|
||||
}
|
||||
];
|
||||
|
||||
const insertManuscript = db.prepare(`INSERT INTO manuscripts (title, author, content) VALUES (?, ?, ?)`);
|
||||
|
||||
manuscripts.forEach((manuscript) => {
|
||||
insertManuscript.run(manuscript.title, manuscript.author, manuscript.content, (err) => {
|
||||
if (err) {
|
||||
console.error('Error inserting manuscript:', err.message);
|
||||
} else {
|
||||
console.log(`Manuscript "${manuscript.title}" added.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
insertManuscript.finalize();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = db;
|
|
@ -0,0 +1,360 @@
|
|||
/* General Styles */
|
||||
body {
|
||||
background-color: #0d0d0d; /* Darker background for deeper contrast */
|
||||
color: #e6e6e6;
|
||||
font-family: 'Merriweather', serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ff6347;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #ffa07a;
|
||||
}
|
||||
|
||||
/* Cool Form Styles */
|
||||
.add-manuscript {
|
||||
margin-top: 50px;
|
||||
padding: 40px;
|
||||
background-color: #1c1c1c;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.8), 0 0 15px rgba(255, 99, 71, 0.5);
|
||||
position: relative;
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
background-image: linear-gradient(135deg, rgba(255, 69, 0, 0.4), rgba(255, 99, 71, 0.2));
|
||||
}
|
||||
|
||||
.add-manuscript h2 {
|
||||
font-size: 2.5rem;
|
||||
color: #ff6347;
|
||||
margin-bottom: 20px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
background: linear-gradient(45deg, #ff6347, #ff4500);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
text-shadow: 0 0 10px rgba(255, 69, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Form Label Styling */
|
||||
.add-manuscript form label {
|
||||
font-size: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
color: #ff6347;
|
||||
display: block;
|
||||
text-align: left;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.add-manuscript form input[type="text"],
|
||||
.add-manuscript form textarea {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
background-color: #1a1a1a;
|
||||
border: 1px solid #444;
|
||||
border-radius: 5px;
|
||||
color: #e6e6e6;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
margin-bottom: 20px;
|
||||
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.add-manuscript form input[type="text"]:focus,
|
||||
.add-manuscript form textarea:focus {
|
||||
border-color: #ff4500;
|
||||
box-shadow: 0 0 15px rgba(255, 69, 0, 0.6);
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
/* Ghostly Glow Effects */
|
||||
.add-manuscript form input[type="text"]:focus::after,
|
||||
.add-manuscript form textarea:focus::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 15px rgba(255, 69, 0, 0.7);
|
||||
animation: pulseGlow 1.5s infinite ease-in-out;
|
||||
}
|
||||
|
||||
/* Animated Glowing Borders */
|
||||
@keyframes pulseGlow {
|
||||
0% {
|
||||
box-shadow: 0 0 5px rgba(255, 69, 0, 0.6);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 20px rgba(255, 69, 0, 0.8);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 5px rgba(255, 69, 0, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Floating Ghostly Effects */
|
||||
.container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
right: -50px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.1), transparent);
|
||||
border-radius: 50%;
|
||||
animation: float 6s ease-in-out infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0% {
|
||||
transform: translateY(0) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-30px) rotate(180deg);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
background-color: #222;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #444;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.6);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
header .logo h1 {
|
||||
color: #ff4500;
|
||||
font-size: 4rem;
|
||||
letter-spacing: 4px;
|
||||
text-transform: uppercase;
|
||||
background: linear-gradient(45deg, #ff6347, #ff4500);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
display: inline-block;
|
||||
margin: 0 15px;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
color: #e6e6e6;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nav ul li a::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
background-color: #ff4500;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
nav ul li a:hover::before {
|
||||
visibility: visible;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
margin-top: 40px;
|
||||
padding: 20px;
|
||||
background-color: #111;
|
||||
color: #777;
|
||||
font-size: 0.9rem;
|
||||
box-shadow: 0 -4px 15px rgba(0, 0, 0, 0.6);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
footer::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #777, transparent);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
/* Home Section */
|
||||
.home-section {
|
||||
margin-top: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.home-section h2 {
|
||||
font-size: 3rem;
|
||||
color: #ff6347;
|
||||
letter-spacing: 2px;
|
||||
text-shadow: 0 0 10px rgba(255, 99, 71, 0.5), 0 0 20px rgba(255, 69, 0, 0.3);
|
||||
}
|
||||
|
||||
.home-section p {
|
||||
font-size: 1.4rem;
|
||||
color: #b3b3b3;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 12px 25px;
|
||||
background-color: #ff4500;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
background: linear-gradient(135deg, rgba(255, 69, 0, 0.8), transparent);
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.button:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
box-shadow: 0 0 20px rgba(255, 69, 0, 0.7);
|
||||
}
|
||||
|
||||
/* Spooky Animations */
|
||||
.spooky-button {
|
||||
background-color: #111;
|
||||
color: #ff6347;
|
||||
border: 1px solid #ff6347;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.spooky-button:hover {
|
||||
color: #fff;
|
||||
box-shadow: 0 0 20px rgba(255, 69, 0, 0.5);
|
||||
}
|
||||
|
||||
.spooky-button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 300%;
|
||||
height: 300%;
|
||||
background: radial-gradient(circle, rgba(255, 69, 0, 0.1), transparent);
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
transition: transform 0.4s ease;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.spooky-button:hover::before {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
|
||||
/* Manuscript View */
|
||||
.manuscript-view h2 {
|
||||
font-size: 2.5rem;
|
||||
color: #ff6347;
|
||||
text-shadow: 0 0 10px rgba(255, 99, 71, 0.5);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.manuscript-view article {
|
||||
margin: 20px 0;
|
||||
padding: 30px;
|
||||
background-color: #1a1a1a;
|
||||
border: 1px solid #444;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.6);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.manuscript-view article p {
|
||||
font-size: 1.2rem;
|
||||
color: #e6e6e6;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Glowing Borders on Focus */
|
||||
input[type="text"], textarea {
|
||||
padding: 10px;
|
||||
font-size: 1rem;
|
||||
border: 1px solid #444;
|
||||
border-radius: 5px;
|
||||
background-color: #1a1a1a;
|
||||
color: #e6e6e6;
|
||||
outline: none;
|
||||
transition: border 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
input[type="text"]:focus, textarea:focus {
|
||||
border-color: #ff4500;
|
||||
box-shadow: 0 0 10px rgba(255, 69, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Ghostly Shadows on Scroll */
|
||||
@media (min-width: 768px) {
|
||||
.container::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -100px;
|
||||
left: 50%;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.1), transparent);
|
||||
border-radius: 50%;
|
||||
animation: float 7s ease-in-out infinite;
|
||||
z-index: -1;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { template } = require("@blakeembrey/template"); // Use require instead of import
|
||||
|
||||
// 1. GET: Home page
|
||||
router.get('/', (req, res) => {
|
||||
const { user } = req.query; // Optional query parameter to pass user's name
|
||||
|
||||
let additionalContent = '';
|
||||
|
||||
if (user) {
|
||||
try {
|
||||
const decodedUser = decodeURIComponent(user);
|
||||
console.log('User:', decodedUser);
|
||||
const fn = template("Welcome back, {{user}}!", decodedUser);
|
||||
const userRender = fn({ user: decodedUser });
|
||||
|
||||
additionalContent = `
|
||||
<p><strong>${userRender}</strong> Your knowledge quest continues. Browse the hidden manuscripts or leave your own wisdom behind.</p>
|
||||
<a class="button spooky-button" href="/manuscripts/add">Contribute a Manuscript</a>
|
||||
`;
|
||||
} catch (error) {
|
||||
console.error('Error decoding or rendering user:', error);
|
||||
additionalContent = ''; // Render nothing if error occurs
|
||||
}
|
||||
} else {
|
||||
additionalContent = `
|
||||
<form action="/" method="GET" class="username-form">
|
||||
<label for="username">Enter your name to personalize your journey:</label>
|
||||
<input type="text" id="username" name="user" placeholder="Your Name" required>
|
||||
<button type="submit" class="button spooky-button">Enter the Vault</button>
|
||||
</form>
|
||||
`;
|
||||
}
|
||||
|
||||
const content = `
|
||||
<section class="home-section">
|
||||
<h2>Welcome to the Forbidden Vault of Knowledge</h2>
|
||||
<p>Hello, ${user || 'Adventurer'}! Dare to enter? Search the library, or contribute your own arcane manuscript.</p>
|
||||
<a class="button spooky-button" href="/manuscripts/search">Search the Vault</a>
|
||||
${additionalContent}
|
||||
</section>
|
||||
`;
|
||||
|
||||
// Safely render the layout and pass the content to be injected
|
||||
try {
|
||||
res.render('layouts/main', { title: "Welcome to Shadowbrook Library", content });
|
||||
} catch (renderError) {
|
||||
console.error('Error rendering the page:', renderError);
|
||||
res.status(200).send(''); // Return empty response in case of error
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,122 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../config/db');
|
||||
|
||||
// 1. GET: Search Manuscripts
|
||||
router.get('/search', (req, res) => {
|
||||
const { query = '', user } = req.query; // Default query to an empty string if not provided
|
||||
|
||||
const searchQuery = `%${query}%`;
|
||||
db.all("SELECT * FROM manuscripts WHERE title LIKE ? OR content LIKE ?", [searchQuery, searchQuery], (err, rows) => {
|
||||
if (err) {
|
||||
res.status(500).send("Error querying the database.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate dynamic HTML for search input and results
|
||||
let content = `
|
||||
<section class="search-results">
|
||||
<h2>Search Manuscripts</h2>
|
||||
<form action="/manuscripts/search" method="get" class="search-form">
|
||||
<input type="text" name="query" value="${query}" placeholder="Search for manuscripts..." required />
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
<ul>
|
||||
`;
|
||||
|
||||
if (rows.length > 0) {
|
||||
rows.forEach(manuscript => {
|
||||
content += `
|
||||
<li>
|
||||
<a href="/manuscripts/view/${manuscript.id}">${manuscript.title}</a>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
} else {
|
||||
content += `
|
||||
<li>No results found for "${query}".</li>
|
||||
`;
|
||||
}
|
||||
|
||||
content += `
|
||||
</ul>
|
||||
</section>
|
||||
`;
|
||||
|
||||
// Render the layout and inject the dynamic content as a string
|
||||
res.render('layouts/main', { title: `Search Results for "${query}"`, content, user });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 2. GET: View a single manuscript by ID
|
||||
router.get('/view/:id', (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { user } = req.query;
|
||||
|
||||
db.get("SELECT * FROM manuscripts WHERE id = ?", [id], (err, manuscript) => {
|
||||
if (err || !manuscript) {
|
||||
res.status(404).send("Manuscript not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate dynamic content for the manuscript view
|
||||
const content = `
|
||||
<section class="manuscript-view">
|
||||
<h2>${manuscript.title}</h2>
|
||||
<p><strong>Author:</strong> ${manuscript.author}</p>
|
||||
<article>
|
||||
<p>${manuscript.content}</p>
|
||||
</article>
|
||||
</section>
|
||||
`;
|
||||
|
||||
// Render the layout and inject the dynamic content
|
||||
res.render('layouts/main', { title: manuscript.title, content, user });
|
||||
});
|
||||
});
|
||||
|
||||
// 3. GET: Add Manuscript form
|
||||
router.get('/add', (req, res) => {
|
||||
const { user } = req.query;
|
||||
|
||||
// Generate content for the add manuscript form
|
||||
const content = `
|
||||
<section class="add-manuscript">
|
||||
<h2>Add a New Arcane Manuscript</h2>
|
||||
<form action="/manuscripts/add" method="POST">
|
||||
<label for="title">Title:</label>
|
||||
<input type="text" id="title" name="title" required>
|
||||
|
||||
<label for="author">Author:</label>
|
||||
<input type="text" id="author" name="author" required>
|
||||
|
||||
<label for="content">Content:</label>
|
||||
<textarea id="content" name="content" rows="6" required></textarea>
|
||||
|
||||
<button type="submit" class="button spooky-button">Submit Manuscript</button>
|
||||
</form>
|
||||
</section>
|
||||
`;
|
||||
|
||||
// Render the layout and inject the form
|
||||
res.render('layouts/main', { title: "Add a New Manuscript", content, user });
|
||||
});
|
||||
|
||||
// 4. POST: Add a New Manuscript
|
||||
router.post('/add', (req, res) => {
|
||||
const { title, author, content } = req.body;
|
||||
const { user } = req.query;
|
||||
|
||||
db.run("INSERT INTO manuscripts (title, author, content) VALUES (?, ?, ?)", [title, author, content], function(err) {
|
||||
if (err) {
|
||||
res.status(500).send("Error inserting into the database.");
|
||||
return;
|
||||
}
|
||||
|
||||
// After adding, redirect to the newly added manuscript's page
|
||||
res.redirect(`/manuscripts/view/${this.lastID}?user=${user}`);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,7 @@
|
|||
<%- include('layouts/main', { title: "404 - Page Not Found" }) %>
|
||||
|
||||
<section class="error-page">
|
||||
<h2>404 - You have ventured too far...</h2>
|
||||
<p>It seems you've stumbled upon forbidden knowledge not meant to be found, <%= user %>. Return to safety.</p>
|
||||
<a class="button spooky-button" href="/">Go Back Home</a>
|
||||
</section>
|
|
@ -0,0 +1,7 @@
|
|||
<%- include('layouts/main', { title: "Welcome to Shadowbrook Library" }) %>
|
||||
|
||||
<section class="home-section">
|
||||
<h2>Welcome to the Forbidden Vault of Knowledge</h2>
|
||||
<p>Hello, <%= user %>! Dare to enter? Search the library, or contribute your own arcane manuscript.</p>
|
||||
<a class="button spooky-button" href="/manuscripts/search">Search the Vault</a>
|
||||
</section>
|
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><%= title || "Shadowbrook Library" %></title>
|
||||
<link rel="stylesheet" href="/dark-theme.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<%- include('../partials/header') %> <!-- Includes the header partial -->
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<%- content %> <!-- This will be passed explicitly from the routes -->
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<%- include('../partials/footer') %> <!-- Includes the footer partial -->
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,17 @@
|
|||
<%- include('../layouts/main', { title: "Add a New Manuscript" }) %>
|
||||
|
||||
<section class="add-manuscript">
|
||||
<h2>Add a New Arcane Manuscript</h2>
|
||||
<form action="/manuscripts/add" method="POST">
|
||||
<label for="title">Title:</label>
|
||||
<input type="text" id="title" name="title" required>
|
||||
|
||||
<label for="author">Author:</label>
|
||||
<input type="text" id="author" name="author" required>
|
||||
|
||||
<label for="content">Content:</label>
|
||||
<textarea id="content" name="content" rows="6" required></textarea>
|
||||
|
||||
<button type="submit" class="button spooky-button">Submit Manuscript</button>
|
||||
</form>
|
||||
</section>
|
|
@ -0,0 +1,13 @@
|
|||
<%- include('../layouts/main', { title: manuscript.title }) %>
|
||||
|
||||
<section class="manuscript-view">
|
||||
<h2><%= manuscript.title %></h2>
|
||||
<p><strong>Author:</strong> <%= manuscript.author %></p>
|
||||
<article>
|
||||
<p><%= manuscript.content %></p>
|
||||
</article>
|
||||
|
||||
<footer>
|
||||
<p>Submitted by: <%= user || "Anonymous" %></p>
|
||||
</footer>
|
||||
</section>
|
|
@ -0,0 +1,4 @@
|
|||
<footer>
|
||||
<p>© <%= new Date().getFullYear() %> Shadowbrook Library - Beware the forbidden knowledge...</p>
|
||||
</footer>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<header>
|
||||
<div class="logo">
|
||||
<a href="/">
|
||||
<h1>Shadowbrook Library</h1>
|
||||
</a>
|
||||
</div>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/manuscripts/search">Search Manuscripts</a></li>
|
||||
<li><a href="/manuscripts/add">Add Manuscript</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue