Consolidate examples into unified production config with static CSS

- New sqlite_serve.conf: all features on single port 8080
- New zenburn.css: hex color palette, all styles in one cacheable file
- New Zenburn templates: header/footer/card partials
- New start.sh: unified launcher with all endpoint docs
- Removed 3 separate example configs and start scripts

15 files changed, 980 insertions(+), 258 deletions(-)
This commit is contained in:
Edward Langley
2025-11-15 16:42:05 -08:00
parent f3e3b8c77b
commit a4a838ad3b
15 changed files with 980 additions and 258 deletions

114
conf/sqlite_serve.conf Normal file
View File

@ -0,0 +1,114 @@
# sqlite-serve - Unified Production Example
# Demonstrates all features: static queries, positional params, named params, templates
load_module target/debug/libsqlite_serve.dylib;
worker_processes auto;
events {
worker_connections 1024;
}
error_log logs/error.log warn;
http {
# Global templates for shared components
sqlite_global_templates "server_root/global_templates";
# MIME types
types {
text/html html htm;
text/css css;
application/javascript js;
}
default_type text/html;
# Performance optimizations
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
gzip on;
server {
listen 8080;
server_name localhost;
root "server_root";
charset utf-8;
# Serve static CSS file
location /static/ {
add_header "Content-Type" "text/css; charset=utf-8";
add_header "Cache-Control" "public, max-age=31536000";
}
# Homepage - Redirect to catalog
location = / {
return 302 /books;
}
# Browse all books
location = /books {
add_header "Content-Type" "text/html; charset=utf-8";
add_header "Cache-Control" "public, max-age=300";
sqlite_db "book_catalog.db";
sqlite_query "SELECT * FROM books ORDER BY rating DESC, title";
sqlite_template "catalog.hbs";
}
# Book detail by ID (named parameter)
location = /book {
add_header "Content-Type" "text/html; charset=utf-8";
add_header "Cache-Control" "public, max-age=300";
sqlite_db "book_catalog.db";
sqlite_query "SELECT * FROM books WHERE id = :book_id";
sqlite_param :book_id $arg_id;
sqlite_template "detail.hbs";
}
# Filter by genre (named parameter)
location = /genre {
add_header "Content-Type" "text/html; charset=utf-8";
add_header "Cache-Control" "public, max-age=300";
sqlite_db "book_catalog.db";
sqlite_query "SELECT * FROM books WHERE genre = :genre ORDER BY rating DESC, title";
sqlite_param :genre $arg_genre;
sqlite_template "catalog.hbs";
}
# Search by title (named parameter with LIKE)
location = /search {
add_header "Content-Type" "text/html; charset=utf-8";
sqlite_db "book_catalog.db";
sqlite_query "SELECT * FROM books WHERE title LIKE '%' || :q || '%' ORDER BY rating DESC";
sqlite_param :q $arg_q;
sqlite_template "catalog.hbs";
}
# Filter by minimum rating (named parameter)
location = /top {
add_header "Content-Type" "text/html; charset=utf-8";
sqlite_db "book_catalog.db";
sqlite_query "SELECT * FROM books WHERE rating >= :min_rating ORDER BY rating DESC, title";
sqlite_param :min_rating $arg_min;
sqlite_template "catalog.hbs";
}
# Year range filter (multiple named parameters)
location = /era {
add_header "Content-Type" "text/html; charset=utf-8";
sqlite_db "book_catalog.db";
sqlite_query "SELECT * FROM books WHERE year >= :from AND year <= :to ORDER BY year DESC, title";
sqlite_param :from $arg_from;
sqlite_param :to $arg_to;
sqlite_template "catalog.hbs";
}
# 404 handler
location @notfound {
add_header "Content-Type" "text/html; charset=utf-8";
return 404 "<html><body><h1>404 Not Found</h1><p><a href='/books'>Back to catalog</a></p></body></html>";
}
}
}

View File

@ -1,110 +1,20 @@
{{> header}}
{{> zenburn_header}}
<style>
.book-detail {
max-width: 800px;
margin: 2rem auto;
padding: 2rem;
}
.book-cover {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
height: 300px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 3rem;
margin-bottom: 2rem;
}
.book-title {
font-size: 2.5rem;
color: #2d3748;
margin-bottom: 0.5rem;
}
.book-author {
font-size: 1.5rem;
color: #667eea;
margin-bottom: 2rem;
}
.book-meta {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.meta-item {
background: #f8f9fa;
padding: 1rem;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.meta-label {
font-size: 0.875rem;
color: #6c757d;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
.meta-value {
font-size: 1.25rem;
color: #2d3748;
font-weight: 600;
}
.book-description {
background: #f8f9fa;
padding: 2rem;
border-radius: 8px;
line-height: 1.8;
font-size: 1.125rem;
color: #4a5568;
}
.rating {
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.stars {
color: #fbbf24;
font-size: 1.5rem;
}
.back-link {
display: inline-block;
margin-top: 2rem;
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 500;
transition: transform 0.2s;
}
.back-link:hover {
transform: translateY(-2px);
}
.not-found {
text-align: center;
padding: 4rem 2rem;
color: #6c757d;
}
</style>
<div class="detail-container">
<div class="breadcrumb">
<a href="/books">← Back to catalog</a>
</div>
{{#if results.[0]}}
{{#with results.[0]}}
<div class="book-detail">
<div class="book-cover">
📖
</div>
<div class="book-hero">
<h1 class="book-hero-title">{{title}}</h1>
<p class="book-hero-author">by {{author}}</p>
<h1 class="book-title">{{title}}</h1>
<p class="book-author">by {{author}}</p>
<div class="book-meta">
<div class="meta-grid">
<div class="meta-item">
<div class="meta-label">Rating</div>
<div class="meta-value rating">
<span class="stars">⭐</span>
{{rating}} / 5.0
</div>
<div class="meta-value rating">{{rating}}</div>
</div>
<div class="meta-item">
<div class="meta-label">Published</div>
@ -116,25 +26,25 @@
</div>
<div class="meta-item">
<div class="meta-label">ISBN</div>
<div class="meta-value" style="font-family: monospace; font-size: 1rem;">{{isbn}}</div>
<div class="meta-value" style="font-size: 1rem; font-family: monospace;">{{isbn}}</div>
</div>
</div>
</div>
<div class="book-description">
<strong>About this book:</strong><br><br>
{{description}}
<div class="description-section">
<h3>About This Book</h3>
<p class="description-text">{{description}}</p>
</div>
<a href="/books/all" class="back-link">← Back to Catalog</a>
</div>
<a href="/books" class="back-button">← Back to Catalog</a>
{{/with}}
{{else}}
<div class="not-found">
<h2>Book Not Found</h2>
<p>The requested book could not be found in our catalog.</p>
<a href="/books/all" class="back-link">← Back to Catalog</a>
<a href="/books" class="back-button">← Back to Catalog</a>
</div>
{{/if}}
</div>
{{> footer}}
{{> zenburn_footer}}

View File

@ -0,0 +1,37 @@
{{> zenburn_header}}
<div class="page-header">
<h2>Book Catalog</h2>
<p>Browse our curated collection of technical literature</p>
</div>
{{#if results}}
<div class="stats-grid">
<div class="stat-card">
<span class="stat-value">{{#each results}}{{#if @last}}{{@index}}{{/if}}{{/each}}</span>
<span class="stat-label">Books Found</span>
</div>
<div class="stat-card">
<span class="stat-value">📚</span>
<span class="stat-label">Categories</span>
</div>
<div class="stat-card">
<span class="stat-value">⭐</span>
<span class="stat-label">Curated</span>
</div>
</div>
<div class="book-grid">
{{#each results}}
{{> zenburn_card}}
{{/each}}
</div>
{{else}}
<div class="empty-state">
<h3>No Books Found</h3>
<p>Try adjusting your search or <a href="/books">browse all books</a></p>
</div>
{{/if}}
{{> zenburn_footer}}

View File

@ -0,0 +1,37 @@
{{> zenburn_header}}
<div class="page-header">
<h2>Book Catalog</h2>
<p>Browse our curated collection of technical literature</p>
</div>
{{#if results}}
<div class="stats-grid">
<div class="stat-card">
<span class="stat-value">{{#each results}}{{#if @last}}{{@index}}{{/if}}{{/each}}</span>
<span class="stat-label">Books Found</span>
</div>
<div class="stat-card">
<span class="stat-value">📚</span>
<span class="stat-label">Categories</span>
</div>
<div class="stat-card">
<span class="stat-value">⭐</span>
<span class="stat-label">Curated</span>
</div>
</div>
<div class="book-grid">
{{#each results}}
{{> zenburn_card}}
{{/each}}
</div>
{{else}}
<div class="empty-state">
<h3>No Books Found</h3>
<p>Try adjusting your search or <a href="/books">browse all books</a></p>
</div>
{{/if}}
{{> zenburn_footer}}

View File

@ -0,0 +1,37 @@
{{> zenburn_header}}
<div class="page-header">
<h2>Book Catalog</h2>
<p>Browse our curated collection of technical literature</p>
</div>
{{#if results}}
<div class="stats-grid">
<div class="stat-card">
<span class="stat-value">{{#each results}}{{#if @last}}{{@index}}{{/if}}{{/each}}</span>
<span class="stat-label">Books Found</span>
</div>
<div class="stat-card">
<span class="stat-value">📚</span>
<span class="stat-label">Categories</span>
</div>
<div class="stat-card">
<span class="stat-value">⭐</span>
<span class="stat-label">Curated</span>
</div>
</div>
<div class="book-grid">
{{#each results}}
{{> zenburn_card}}
{{/each}}
</div>
{{else}}
<div class="empty-state">
<h3>No Books Found</h3>
<p>Try adjusting your search or <a href="/books">browse all books</a></p>
</div>
{{/if}}
{{> zenburn_footer}}

View File

@ -0,0 +1,15 @@
<article class="book-card">
<div class="book-header">
<h3 class="book-title">{{title}}</h3>
<div class="book-rating">⭐ {{rating}}</div>
</div>
<p class="book-author">{{author}}</p>
<p class="book-description">{{description}}</p>
<div class="book-meta">
<span class="genre-badge">{{genre}}</span>
<span class="year-badge">{{year}}</span>
{{#if isbn}}<span class="isbn">{{isbn}}</span>{{/if}}
</div>
<a href="/book?id={{id}}" class="detail-link">View Details →</a>
</article>

View File

@ -0,0 +1,9 @@
</main>
<footer>
<p>sqlite-serve v0.1.0</p>
<p>Rust + NGINX + SQLite + Handlebars</p>
<p>45 tests | Type-safe | Read-only</p>
</footer>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>sqlite-serve | Book Catalog</title>
<link rel="stylesheet" href="/static/zenburn.css">
</head>
<body>
<header>
<div class="header-content">
<h1>📚 sqlite-serve</h1>
<p>Dynamic content from SQLite via NGINX</p>
</div>
</header>
<nav>
<div class="nav-content">
<a href="/books">All Books</a>
<a href="/genre?genre=Programming">Programming</a>
<a href="/genre?genre=Databases">Databases</a>
<a href="/genre?genre=Computer%20Science">Computer Science</a>
<a href="/top?min=4.7">Top Rated</a>
<a href="/era?from=2015&to=2024">Recent (2015-2024)</a>
<form class="search-form" action="/search" method="get">
<input type="text" name="q" placeholder="Search titles..." />
<button type="submit">Search</button>
</form>
</div>
</nav>
<main>

View File

@ -0,0 +1,37 @@
{{> zenburn_header}}
<div class="page-header">
<h2>Book Catalog</h2>
<p>Browse our curated collection of technical literature</p>
</div>
{{#if results}}
<div class="stats-grid">
<div class="stat-card">
<span class="stat-value">{{#each results}}{{#if @last}}{{@index}}{{/if}}{{/each}}</span>
<span class="stat-label">Books Found</span>
</div>
<div class="stat-card">
<span class="stat-value">📚</span>
<span class="stat-label">Categories</span>
</div>
<div class="stat-card">
<span class="stat-value">⭐</span>
<span class="stat-label">Curated</span>
</div>
</div>
<div class="book-grid">
{{#each results}}
{{> zenburn_card}}
{{/each}}
</div>
{{else}}
<div class="empty-state">
<h3>No Books Found</h3>
<p>Try adjusting your search or <a href="/books">browse all books</a></p>
</div>
{{/if}}
{{> zenburn_footer}}

View File

@ -0,0 +1,509 @@
/* Zenburn theme for sqlite-serve */
/* Generated palette via: zenburn -j -e oklab */
:root {
--fg-plus-2: #FFFFEF;
--fg-plus-1: #F5F5D6;
--fg: #DCDCCC;
--fg-1: #A6A689;
--fg-2: #656555;
--black: #000000;
--bg-2: #000000;
--bg-1: #111112;
--bg-05: #383838;
--bg: #2A2B2E;
--bg-plus-05: #494949;
--bg-plus-1: #4F4F4F;
--bg-plus-2: #5F5F5F;
--bg-plus-3: #6F6F6F;
--red-plus-2: #ECB3B3;
--red-plus-1: #DCA3A3;
--red: #CC9393;
--red-1: #BC8383;
--red-2: #AC7373;
--red-3: #9C6363;
--red-4: #8C5353;
--red-5: #7C4343;
--red-6: #6C3333;
--orange: #DFAF8F;
--yellow: #F0DFAF;
--yellow-1: #E0CF9F;
--yellow-2: #D0BF8F;
--green-5: #2F4F2F;
--green-4: #3F5F3F;
--green-3: #4F6F4F;
--green-2: #5F7F5F;
--green-1: #6F8F6F;
--green: #7F9F7F;
--green-plus-1: #8FB28F;
--green-plus-2: #9FC59F;
--green-plus-3: #AFD8AF;
--green-plus-4: #BFEBBF;
--cyan: #93E0E3;
--blue-plus-3: #BDE0F3;
--blue-plus-2: #ACE0E3;
--blue-plus-1: #94BFF3;
--blue: #8CD0D3;
--blue-1: #7CB8BB;
--blue-2: #6CA0A3;
--blue-3: #5C888B;
--blue-4: #4C7073;
--blue-5: #366060;
--magenta: #DC8CC3;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
line-height: 1.6;
color: var(--fg);
background: var(--bg);
min-height: 100vh;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
/* Header */
header {
background: var(--bg-1);
border-bottom: 2px solid var(--blue-1);
padding: 1.5rem 2rem;
box-shadow: 0 2px 8px oklab(0.000% 0.000 0.000 / 0.5);
}
.header-content {
max-width: 1400px;
margin: 0 auto;
}
header h1 {
color: var(--yellow);
font-size: 1.8rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
header p {
color: var(--fg-1);
font-size: 0.95rem;
}
/* Navigation */
nav {
background: var(--bg-plus-1);
padding: 0.75rem 2rem;
border-bottom: 1px solid var(--bg-1);
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 4px oklab(0.000% 0.000 0.000 / 0.3);
}
.nav-content {
max-width: 1400px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
align-items: center;
}
nav a {
color: var(--blue-1);
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
font-size: 0.95rem;
}
nav a:hover {
color: var(--green-plus-1);
}
nav a.active {
color: var(--yellow);
}
.search-form {
margin-left: auto;
display: flex;
gap: 0.5rem;
}
.search-form input {
background: var(--bg);
border: 1px solid var(--fg-2);
color: var(--fg);
padding: 0.4rem 0.8rem;
border-radius: 4px;
font-family: inherit;
font-size: 0.9rem;
}
.search-form input:focus {
outline: none;
border-color: var(--blue-1);
}
.search-form button {
background: var(--blue-1);
color: var(--bg-1);
border: none;
padding: 0.4rem 1rem;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
font-size: 0.9rem;
transition: background 0.2s;
}
.search-form button:hover {
background: var(--green-plus-1);
}
/* Main content */
main {
padding: 2rem;
max-width: 1400px;
margin: 0 auto;
}
/* Footer */
footer {
background: var(--bg-1);
padding: 2rem;
text-align: center;
border-top: 2px solid var(--blue-1);
margin-top: 3rem;
}
footer p {
margin-top: 0.5rem;
}
footer p:first-child {
color: var(--yellow);
font-weight: 600;
margin-top: 0;
}
footer p:not(:first-child) {
color: var(--fg-1);
font-size: 0.9rem;
}
/* Page header */
.page-header {
background: var(--bg-plus-1);
border: 1px solid var(--bg-1);
border-left: 3px solid var(--green-plus-1);
border-radius: 6px;
padding: 2rem;
margin-bottom: 2rem;
}
.page-header h2 {
color: var(--yellow);
font-size: 2rem;
margin-bottom: 0.5rem;
}
.page-header p {
color: var(--fg-1);
font-size: 1.05rem;
}
/* Stats grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
background: var(--bg-plus-1);
border: 1px solid var(--bg-1);
border-radius: 6px;
padding: 1.5rem;
text-align: center;
}
.stat-value {
color: var(--orange);
font-size: 2.5rem;
font-weight: 700;
display: block;
margin-bottom: 0.5rem;
}
.stat-label {
color: var(--fg-1);
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Book grid and cards */
.book-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 1.5rem;
}
.book-card {
background: var(--bg-plus-1);
border: 1px solid var(--bg-1);
border-left: 3px solid var(--blue-1);
border-radius: 6px;
padding: 1.5rem;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.book-card:hover {
border-left-color: var(--green-plus-1);
box-shadow: 0 4px 12px oklab(0.000% 0.000 0.000 / 0.5);
transform: translateY(-2px);
}
.book-header {
display: flex;
justify-content: space-between;
align-items: start;
gap: 1rem;
}
.book-title {
color: var(--yellow);
font-size: 1.15rem;
font-weight: 600;
line-height: 1.3;
}
.book-rating {
background: var(--bg-1);
color: var(--orange);
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 700;
white-space: nowrap;
flex-shrink: 0;
}
.book-author {
color: var(--blue-1);
font-weight: 500;
font-size: 0.95rem;
}
.book-description {
color: var(--fg-1);
line-height: 1.5;
font-size: 0.9rem;
}
.book-meta {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
font-size: 0.85rem;
}
.genre-badge {
background: var(--green);
color: var(--bg-1);
padding: 0.25rem 0.75rem;
border-radius: 4px;
font-weight: 600;
}
.year-badge {
background: var(--bg-1);
color: var(--fg-1);
padding: 0.25rem 0.75rem;
border-radius: 4px;
}
.isbn {
color: var(--fg-1);
font-family: monospace;
font-size: 0.8rem;
}
.detail-link {
color: var(--blue-1);
text-decoration: none;
font-weight: 500;
font-size: 0.9rem;
margin-top: 0.5rem;
align-self: flex-start;
transition: color 0.2s;
}
.detail-link:hover {
color: var(--green-plus-1);
}
/* Empty state */
.empty-state {
text-align: center;
padding: 4rem 2rem;
color: var(--fg-1);
}
.empty-state h3 {
color: var(--orange);
font-size: 1.5rem;
margin-bottom: 1rem;
}
.empty-state a {
color: var(--blue-1);
text-decoration: none;
font-weight: 600;
}
.empty-state a:hover {
color: var(--green-plus-1);
}
/* Book detail page */
.detail-container {
max-width: 900px;
margin: 0 auto;
}
.breadcrumb {
color: var(--fg-1);
margin-bottom: 1.5rem;
font-size: 0.9rem;
}
.breadcrumb a {
color: var(--blue-1);
text-decoration: none;
}
.breadcrumb a:hover {
color: var(--green-plus-1);
}
.book-hero {
background: var(--bg-plus-1);
border: 1px solid var(--bg-1);
border-left: 4px solid var(--orange);
border-radius: 6px;
padding: 2rem;
margin-bottom: 2rem;
}
.book-hero-title {
color: var(--yellow);
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.75rem;
line-height: 1.2;
}
.book-hero-author {
color: var(--blue-1);
font-size: 1.3rem;
font-weight: 500;
margin-bottom: 1.5rem;
}
.meta-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.meta-item {
background: var(--bg-1);
padding: 1.25rem;
border-radius: 6px;
border-left: 3px solid var(--blue-1);
}
.meta-label {
color: var(--fg-1);
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}
.meta-value {
color: var(--yellow);
font-size: 1.4rem;
font-weight: 700;
}
.meta-value.rating {
color: var(--orange);
}
.description-section {
background: var(--bg-plus-1);
border: 1px solid var(--bg-1);
border-radius: 6px;
padding: 2rem;
margin-bottom: 2rem;
}
.description-section h3 {
color: var(--green-plus-1);
font-size: 1.3rem;
margin-bottom: 1rem;
}
.description-text {
color: var(--fg);
line-height: 1.8;
font-size: 1.05rem;
}
.back-button {
display: inline-block;
background: var(--blue-1);
color: var(--bg-1);
padding: 0.75rem 1.5rem;
border-radius: 6px;
text-decoration: none;
font-weight: 600;
transition: all 0.2s;
}
.back-button:hover {
background: var(--green-plus-1);
transform: translateX(-4px);
}
.not-found {
text-align: center;
padding: 4rem 2rem;
}
.not-found h2 {
color: var(--red);
font-size: 2rem;
margin-bottom: 1rem;
}
.not-found p {
color: var(--fg-1);
margin-bottom: 2rem;
}

View File

@ -0,0 +1,37 @@
{{> zenburn_header}}
<div class="page-header">
<h2>Book Catalog</h2>
<p>Browse our curated collection of technical literature</p>
</div>
{{#if results}}
<div class="stats-grid">
<div class="stat-card">
<span class="stat-value">{{#each results}}{{#if @last}}{{@index}}{{/if}}{{/each}}</span>
<span class="stat-label">Books Found</span>
</div>
<div class="stat-card">
<span class="stat-value">📚</span>
<span class="stat-label">Categories</span>
</div>
<div class="stat-card">
<span class="stat-value">⭐</span>
<span class="stat-label">Curated</span>
</div>
</div>
<div class="book-grid">
{{#each results}}
{{> zenburn_card}}
{{/each}}
</div>
{{else}}
<div class="empty-state">
<h3>No Books Found</h3>
<p>Try adjusting your search or <a href="/books">browse all books</a></p>
</div>
{{/if}}
{{> zenburn_footer}}

75
start.sh Executable file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env bash
# Production start script for sqlite-serve
set -e
echo "🚀 Starting sqlite-serve..."
echo ""
# Check if database exists
if [ ! -f "book_catalog.db" ]; then
echo "📊 Database not found. Running setup..."
./setup_book_catalog.sh
echo ""
fi
# Build if needed
if [ ! -f "target/debug/libsqlite_serve.dylib" ]; then
echo "🔨 Building module..."
direnv exec "$PWD" cargo build
echo ""
fi
# Stop any existing instance
./ngx_src/nginx-1.28.0/objs/nginx -s stop -c conf/sqlite_serve.conf -p . 2>/dev/null || true
sleep 1
# Start nginx
echo "▶️ Starting NGINX on http://localhost:8080"
./ngx_src/nginx-1.28.0/objs/nginx -c conf/sqlite_serve.conf -p .
echo ""
echo "✅ sqlite-serve is running!"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Endpoints:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo " 📚 Browse"
echo " http://localhost:8080/books"
echo " → All books in catalog"
echo ""
echo " 🔍 Search"
echo " http://localhost:8080/search?q=Rust"
echo " → Search by title"
echo ""
echo " 📖 Book Detail"
echo " http://localhost:8080/book?id=1"
echo " → View individual book"
echo ""
echo " 🏷️ Filter by Genre"
echo " http://localhost:8080/genre?genre=Programming"
echo " → Programming books"
echo ""
echo " ⭐ Top Rated"
echo " http://localhost:8080/top?min=4.7"
echo " → Books rated 4.7 or higher"
echo ""
echo " 📅 By Era"
echo " http://localhost:8080/era?from=2015&to=2024"
echo " → Books from 2015-2024"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Features Demonstrated:"
echo " ✓ SQLite integration with parameterized queries"
echo " ✓ Named & positional SQL parameters"
echo " ✓ Handlebars templates with inheritance"
echo " ✓ Global & local template overrides"
echo " ✓ Type-safe configuration (Parse, Don't Validate)"
echo " ✓ Dependency injection architecture"
echo " ✓ 45 unit tests"
echo ""
echo "To stop: ./ngx_src/nginx-1.28.0/objs/nginx -s stop -c conf/sqlite_serve.conf -p ."
echo ""

View File

@ -1,37 +0,0 @@
#!/usr/bin/env bash
# Quick start script for the book catalog example
set -e
echo "📚 Starting Book Catalog..."
echo ""
# Check if database exists
if [ ! -f "book_catalog.db" ]; then
echo "Database not found. Running setup..."
./setup_book_catalog.sh
echo ""
fi
# Check if module is built
if [ ! -f "target/debug/libsqlite_serve.dylib" ]; then
echo "Module not built. Building..."
direnv exec "$PWD" cargo build
echo ""
fi
# Start nginx
echo "Starting nginx on http://localhost:8080"
./ngx_src/nginx-1.28.0/objs/nginx -c conf/book_catalog.conf -p .
echo ""
echo "✅ Book Catalog is running!"
echo ""
echo "Visit:"
echo " • http://localhost:8080/books/all - All books"
echo " • http://localhost:8080/books/programming - Programming books"
echo " • http://localhost:8080/books/databases - Database books"
echo " • http://localhost:8080/books/computer-science - CS books"
echo ""
echo "To stop: ./ngx_src/nginx-1.28.0/objs/nginx -s stop -c conf/book_catalog.conf -p ."

View File

@ -1,39 +0,0 @@
#!/usr/bin/env bash
# Start script for the book detail example with path parameters
set -e
echo "📚 Starting Book Detail Example with Path Parameters..."
echo ""
# Check if database exists
if [ ! -f "book_catalog.db" ]; then
echo "Database not found. Running setup..."
./setup_book_catalog.sh
echo ""
fi
# Check if module is built
if [ ! -f "target/debug/libsqlite_serve.dylib" ]; then
echo "Module not built. Building..."
direnv exec "$PWD" cargo build
echo ""
fi
# Start nginx
echo "Starting nginx on http://localhost:8081"
./ngx_src/nginx-1.28.0/objs/nginx -c conf/book_detail.conf -p .
echo ""
echo "✅ Book Detail Example is running!"
echo ""
echo "Try these URLs:"
echo " • http://localhost:8081/book?id=1 - View book #1"
echo " • http://localhost:8081/book?id=5 - View book #5"
echo " • http://localhost:8081/genre?genre=Programming - Programming books"
echo " • http://localhost:8081/genre?genre=Databases - Database books"
echo " • http://localhost:8081/years?min=2000&max=2010 - Books from 2000-2010"
echo " • http://localhost:8081/years?min=2015&max=2024 - Books from 2015-2024"
echo ""
echo "To stop: ./ngx_src/nginx-1.28.0/objs/nginx -s stop -c conf/book_detail.conf -p ."

View File

@ -1,50 +0,0 @@
#!/usr/bin/env bash
# Start script for named parameters example
set -e
echo "📚 Starting Named Parameters Example..."
echo ""
# Check if database exists
if [ ! -f "book_catalog.db" ]; then
echo "Database not found. Running setup..."
./setup_book_catalog.sh
echo ""
fi
# Check if module is built
if [ ! -f "target/debug/libsqlite_serve.dylib" ]; then
echo "Module not built. Building..."
direnv exec "$PWD" cargo build
echo ""
fi
# Start nginx
echo "Starting nginx on http://localhost:8082"
./ngx_src/nginx-1.28.0/objs/nginx -c conf/book_named_params.conf -p .
echo ""
echo "✅ Named Parameters Example is running!"
echo ""
echo "Named Parameter Examples:"
echo " • http://localhost:8082/book?id=1"
echo " Query: SELECT * FROM books WHERE id = :book_id"
echo " Param: :book_id = \$arg_id"
echo ""
echo " • http://localhost:8082/genre?genre=Programming"
echo " Query: ... WHERE genre = :genre_name"
echo " Param: :genre_name = \$arg_genre"
echo ""
echo " • http://localhost:8082/years?min=2015&max=2024"
echo " Query: ... WHERE year >= :min_year AND year <= :max_year"
echo " Params: :min_year = \$arg_min, :max_year = \$arg_max"
echo ""
echo " • http://localhost:8082/search?q=Rust"
echo " Search by title with named parameter"
echo ""
echo " • http://localhost:8082/top-rated?rating=4.7"
echo " Filter by minimum rating"
echo ""
echo "To stop: ./ngx_src/nginx-1.28.0/objs/nginx -s stop -c conf/book_named_params.conf -p ."