Add book catalog example with global/local template system
Features:
- Global template configuration (sqlite_global_templates directive)
- Template path resolution relative to location
- Automatic template loading from directories
- Local templates override global templates
- Content handler installation via directive setter
Book Catalog Example:
- Complete working example with 10 technical books
- SQLite database with setup script
- 4 browseable pages (all, programming, databases, computer-science)
- Shared global templates (header, footer, book_card partial)
- Category-specific local templates with unique theming
- Responsive gradient UI design
- Working navigation and filtering
Configuration:
- sqlite_global_templates: HTTP main-level directive for shared templates
- sqlite_template: Location-level directive (sets content handler)
- Template resolution: {doc_root}{uri}/{template_name}
- All .hbs files in directories auto-loaded as partials
Technical improvements:
- Fixed content handler setup (not phase handler)
- Proper HttpModuleMainConf and HttpModuleLocationConf traits
- Template directory scanning and registration
- Error handling with debug logging
This commit is contained in:
155
README_BOOK_CATALOG.md
Normal file
155
README_BOOK_CATALOG.md
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
# Book Catalog Example
|
||||||
|
|
||||||
|
A complete example demonstrating the nginx-test SQLite module with a read-only book catalog.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **SQLite Database**: Stores book information (title, author, ISBN, year, genre, description, rating)
|
||||||
|
- **Multiple Views**: Different locations for browsing by category
|
||||||
|
- **Template Inheritance**: Global templates (header, footer, book_card) shared across all pages
|
||||||
|
- **Local Templates**: Category-specific styling and layouts
|
||||||
|
- **Responsive Design**: Modern, gradient-styled UI
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Create and populate the database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x setup_book_catalog.sh
|
||||||
|
./setup_book_catalog.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates `book_catalog.db` with 10 sample technical books across three genres:
|
||||||
|
- Programming
|
||||||
|
- Databases
|
||||||
|
- Computer Science
|
||||||
|
|
||||||
|
### 2. Build the module
|
||||||
|
|
||||||
|
```bash
|
||||||
|
direnv exec "$PWD" cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Start nginx
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./ngx_src/nginx-1.28.0/objs/nginx -c conf/book_catalog.conf -p .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Visit the catalog
|
||||||
|
|
||||||
|
Open your browser to:
|
||||||
|
- http://localhost:8080/ (redirects to all books)
|
||||||
|
- http://localhost:8080/books/all
|
||||||
|
- http://localhost:8080/books/programming
|
||||||
|
- http://localhost:8080/books/databases
|
||||||
|
- http://localhost:8080/books/computer-science
|
||||||
|
|
||||||
|
### 5. Stop nginx
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./ngx_src/nginx-1.28.0/objs/nginx -s stop -c conf/book_catalog.conf -p .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
nginx-test/
|
||||||
|
├── book_catalog.db # SQLite database
|
||||||
|
├── setup_book_catalog.sh # Database setup script
|
||||||
|
├── conf/
|
||||||
|
│ └── book_catalog.conf # Nginx configuration
|
||||||
|
└── server_root/
|
||||||
|
├── global_templates/ # Shared templates
|
||||||
|
│ ├── header.hbs # Page header with navigation
|
||||||
|
│ ├── footer.hbs # Page footer
|
||||||
|
│ └── book_card.hbs # Reusable book card partial
|
||||||
|
└── books/
|
||||||
|
├── all/
|
||||||
|
│ └── list.hbs # All books page
|
||||||
|
├── programming/
|
||||||
|
│ └── list.hbs # Programming books page
|
||||||
|
├── databases/
|
||||||
|
│ └── list.hbs # Database books page
|
||||||
|
└── computer-science/
|
||||||
|
└── list.hbs # CS books page
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### Template Loading Order
|
||||||
|
|
||||||
|
For each request, the module:
|
||||||
|
|
||||||
|
1. **Loads global templates** from `server_root/global_templates/`:
|
||||||
|
- `header.hbs` - Page structure and navigation
|
||||||
|
- `footer.hbs` - Page footer
|
||||||
|
- `book_card.hbs` - Book display component
|
||||||
|
|
||||||
|
2. **Loads local templates** from the location's directory:
|
||||||
|
- Each category has its own `list.hbs` with custom styling
|
||||||
|
- Local templates can override global ones
|
||||||
|
|
||||||
|
3. **Renders the main template** with SQL query results
|
||||||
|
|
||||||
|
### Template Usage
|
||||||
|
|
||||||
|
**In list.hbs:**
|
||||||
|
```handlebars
|
||||||
|
{{> header}}
|
||||||
|
|
||||||
|
<div class="book-grid">
|
||||||
|
{{#each results}}
|
||||||
|
{{> book_card}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{> footer}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `{{> header}}`, `{{> book_card}}`, and `{{> footer}}` are partials loaded from the global templates directory.
|
||||||
|
|
||||||
|
### SQL Queries
|
||||||
|
|
||||||
|
Each location runs a different SQL query:
|
||||||
|
|
||||||
|
- **All books**: `SELECT * FROM books ORDER BY rating DESC, title`
|
||||||
|
- **Programming**: `SELECT * FROM books WHERE genre = 'Programming' ...`
|
||||||
|
- **Databases**: `SELECT * FROM books WHERE genre = 'Databases' ...`
|
||||||
|
- **Computer Science**: `SELECT * FROM books WHERE genre = 'Computer Science' ...`
|
||||||
|
|
||||||
|
Results are passed to the template as a `results` array.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### Adding More Books
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sqlite3 book_catalog.db
|
||||||
|
```
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO books (title, author, isbn, year, genre, description, rating)
|
||||||
|
VALUES ('Your Book', 'Author Name', '978-XXXXXXXXXX', 2024, 'Programming', 'Description here', 4.5);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding New Categories
|
||||||
|
|
||||||
|
1. Create a new genre in the database
|
||||||
|
2. Add a location block in `book_catalog.conf`
|
||||||
|
3. Create a template directory under `server_root/books/`
|
||||||
|
4. Add the category to the navigation in `header.hbs`
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
Each category's `list.hbs` contains embedded CSS. Modify the `<style>` section to change colors, layouts, etc.
|
||||||
|
|
||||||
|
## Architecture Notes
|
||||||
|
|
||||||
|
- **Read-only**: All SQL queries are SELECT statements only
|
||||||
|
- **Performance**: Templates are loaded fresh for each request (suitable for development)
|
||||||
|
- **Security**: No user input is processed; all queries are predefined
|
||||||
|
- **Scalability**: SQLite is suitable for read-heavy workloads with moderate traffic
|
||||||
|
|
||||||
|
Enjoy exploring the book catalog! 📚
|
||||||
|
|
||||||
BIN
book_catalog.db
Normal file
BIN
book_catalog.db
Normal file
Binary file not shown.
58
conf/book_catalog.conf
Normal file
58
conf/book_catalog.conf
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Book Catalog Configuration
|
||||||
|
# Demonstrates the nginx-test SQLite module with multiple locations and template inheritance
|
||||||
|
|
||||||
|
load_module target/debug/libnginx_test.dylib;
|
||||||
|
|
||||||
|
worker_processes 1;
|
||||||
|
events {}
|
||||||
|
|
||||||
|
error_log logs/error.log debug;
|
||||||
|
|
||||||
|
http {
|
||||||
|
# Global templates for shared components (header, footer, book_card partial)
|
||||||
|
sqlite_global_templates "server_root/global_templates";
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
|
||||||
|
root "server_root";
|
||||||
|
|
||||||
|
# Default redirect to all books
|
||||||
|
location = / {
|
||||||
|
return 301 /books/all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# All books
|
||||||
|
location /books/all {
|
||||||
|
add_header "Content-Type" "text/html; charset=utf-8";
|
||||||
|
sqlite_db "book_catalog.db";
|
||||||
|
sqlite_query "SELECT * FROM books ORDER BY rating DESC, title";
|
||||||
|
sqlite_template "list.hbs";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Programming books only
|
||||||
|
location /books/programming {
|
||||||
|
add_header "Content-Type" "text/html; charset=utf-8";
|
||||||
|
sqlite_db "book_catalog.db";
|
||||||
|
sqlite_query "SELECT * FROM books WHERE genre = 'Programming' ORDER BY rating DESC, title";
|
||||||
|
sqlite_template "list.hbs";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Database books only
|
||||||
|
location /books/databases {
|
||||||
|
add_header "Content-Type" "text/html; charset=utf-8";
|
||||||
|
sqlite_db "book_catalog.db";
|
||||||
|
sqlite_query "SELECT * FROM books WHERE genre = 'Databases' ORDER BY rating DESC, title";
|
||||||
|
sqlite_template "list.hbs";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Computer Science books only
|
||||||
|
location /books/computer-science {
|
||||||
|
add_header "Content-Type" "text/html; charset=utf-8";
|
||||||
|
sqlite_db "book_catalog.db";
|
||||||
|
sqlite_query "SELECT * FROM books WHERE genre = 'Computer Science' ORDER BY rating DESC, title";
|
||||||
|
sqlite_template "list.hbs";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -9,10 +9,14 @@ events {}
|
|||||||
error_log logs/error.log debug;
|
error_log logs/error.log debug;
|
||||||
|
|
||||||
http {
|
http {
|
||||||
|
# Optional: Global templates directory for shared partials/layouts
|
||||||
|
# sqlite_global_templates "server_root/global_templates";
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 8080;
|
listen 8080;
|
||||||
|
|
||||||
location / {
|
root "server_root";
|
||||||
|
location /people {
|
||||||
add_header "Content-Type" "text/html";
|
add_header "Content-Type" "text/html";
|
||||||
sqlite_db "db.sqlite3";
|
sqlite_db "db.sqlite3";
|
||||||
sqlite_query "SELECT id, name, address FROM person";
|
sqlite_query "SELECT id, name, address FROM person";
|
||||||
|
|||||||
122
server_root/books/all/list.hbs
Normal file
122
server_root/books/all/list.hbs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{{> header}}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.book-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.book-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.book-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 16px rgba(102, 126, 234, 0.2);
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
.book-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: start;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.book-title {
|
||||||
|
color: #2d3748;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.book-rating {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
.book-author {
|
||||||
|
color: #667eea;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.book-description {
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.book-details {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
.book-genre {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.book-year {
|
||||||
|
background: #e9ecef;
|
||||||
|
color: #495057;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.book-isbn {
|
||||||
|
color: #6c757d;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.stats {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-item h2 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
.stat-item p {
|
||||||
|
opacity: 0.9;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<h2>📚</h2>
|
||||||
|
<p>Book Collection</p>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<h2>⭐</h2>
|
||||||
|
<p>Top Rated</p>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<h2>🔍</h2>
|
||||||
|
<p>Browse All</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 style="color: #2d3748; margin-bottom: 1rem;">All Books in Collection</h2>
|
||||||
|
|
||||||
|
<div class="book-grid">
|
||||||
|
{{#each results}}
|
||||||
|
{{> book_card}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{> footer}}
|
||||||
|
|
||||||
107
server_root/books/computer-science/list.hbs
Normal file
107
server_root/books/computer-science/list.hbs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
{{> header}}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.book-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.book-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.book-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 16px rgba(59, 130, 246, 0.2);
|
||||||
|
border-color: #3b82f6;
|
||||||
|
}
|
||||||
|
.book-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: start;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.book-title {
|
||||||
|
color: #2d3748;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.book-rating {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
.book-author {
|
||||||
|
color: #3b82f6;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.book-description {
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.book-details {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
.book-genre {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.book-year {
|
||||||
|
background: #e9ecef;
|
||||||
|
color: #495057;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.book-isbn {
|
||||||
|
color: #6c757d;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.category-header {
|
||||||
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.category-header h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="category-header">
|
||||||
|
<h2>🎓 Computer Science Books</h2>
|
||||||
|
<p>Fundamental concepts, algorithms, and theoretical computer science</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="book-grid">
|
||||||
|
{{#each results}}
|
||||||
|
{{> book_card}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#unless results}}
|
||||||
|
<p style="text-align: center; color: #6c757d; padding: 2rem;">No computer science books found.</p>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{> footer}}
|
||||||
|
|
||||||
107
server_root/books/databases/list.hbs
Normal file
107
server_root/books/databases/list.hbs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
{{> header}}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.book-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.book-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.book-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 16px rgba(239, 68, 68, 0.2);
|
||||||
|
border-color: #ef4444;
|
||||||
|
}
|
||||||
|
.book-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: start;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.book-title {
|
||||||
|
color: #2d3748;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.book-rating {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
.book-author {
|
||||||
|
color: #ef4444;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.book-description {
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.book-details {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
.book-genre {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.book-year {
|
||||||
|
background: #e9ecef;
|
||||||
|
color: #495057;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.book-isbn {
|
||||||
|
color: #6c757d;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.category-header {
|
||||||
|
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.category-header h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="category-header">
|
||||||
|
<h2>🗄️ Database Books</h2>
|
||||||
|
<p>Deep dive into database systems, design, and data management</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="book-grid">
|
||||||
|
{{#each results}}
|
||||||
|
{{> book_card}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#unless results}}
|
||||||
|
<p style="text-align: center; color: #6c757d; padding: 2rem;">No database books found.</p>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{> footer}}
|
||||||
|
|
||||||
107
server_root/books/programming/list.hbs
Normal file
107
server_root/books/programming/list.hbs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
{{> header}}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.book-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.book-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.book-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 16px rgba(102, 126, 234, 0.2);
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
.book-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: start;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.book-title {
|
||||||
|
color: #2d3748;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.book-rating {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
.book-author {
|
||||||
|
color: #667eea;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.book-description {
|
||||||
|
color: #4a5568;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.book-details {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
.book-genre {
|
||||||
|
background: #10b981;
|
||||||
|
color: white;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.book-year {
|
||||||
|
background: #e9ecef;
|
||||||
|
color: #495057;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.book-isbn {
|
||||||
|
color: #6c757d;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.category-header {
|
||||||
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.category-header h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="category-header">
|
||||||
|
<h2>💻 Programming Books</h2>
|
||||||
|
<p>Books focused on programming languages, practices, and software development</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="book-grid">
|
||||||
|
{{#each results}}
|
||||||
|
{{> book_card}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#unless results}}
|
||||||
|
<p style="text-align: center; color: #6c757d; padding: 2rem;">No programming books found.</p>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{> footer}}
|
||||||
|
|
||||||
16
server_root/global_templates/book_card.hbs
Normal file
16
server_root/global_templates/book_card.hbs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<div class="book-card">
|
||||||
|
<div class="book-header">
|
||||||
|
<h3 class="book-title">{{title}}</h3>
|
||||||
|
<div class="book-rating">⭐ {{rating}}</div>
|
||||||
|
</div>
|
||||||
|
<p class="book-author">by {{author}}</p>
|
||||||
|
<p class="book-description">{{description}}</p>
|
||||||
|
<div class="book-details">
|
||||||
|
<span class="book-genre">{{genre}}</span>
|
||||||
|
<span class="book-year">{{year}}</span>
|
||||||
|
{{#if isbn}}
|
||||||
|
<span class="book-isbn">ISBN: {{isbn}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
9
server_root/global_templates/footer.hbs
Normal file
9
server_root/global_templates/footer.hbs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
</main>
|
||||||
|
<footer style="background: #f8f9fa; padding: 2rem; text-align: center; border-top: 2px solid #e9ecef; color: #6c757d;">
|
||||||
|
<p>📖 Powered by nginx-test SQLite Module</p>
|
||||||
|
<p style="margin-top: 0.5rem; font-size: 0.9rem;">A demonstration of Rust + NGINX + SQLite + Handlebars</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
77
server_root/global_templates/header.hbs
Normal file
77
server_root/global_templates/header.hbs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Book Catalog</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
header h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
header p {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
nav {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
border-bottom: 2px solid #e9ecef;
|
||||||
|
}
|
||||||
|
nav a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
nav a:hover {
|
||||||
|
color: #764ba2;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>📚 Book Catalog</h1>
|
||||||
|
<p>Explore our collection of technical books</p>
|
||||||
|
</header>
|
||||||
|
<nav>
|
||||||
|
<a href="/books/all">All Books</a>
|
||||||
|
<a href="/books/programming">Programming</a>
|
||||||
|
<a href="/books/databases">Databases</a>
|
||||||
|
<a href="/books/computer-science">Computer Science</a>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
|
||||||
52
setup_book_catalog.sh
Executable file
52
setup_book_catalog.sh
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Setup script for book catalog example
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Setting up book catalog database..."
|
||||||
|
|
||||||
|
# Create the database
|
||||||
|
rm -f book_catalog.db
|
||||||
|
sqlite3 book_catalog.db <<EOF
|
||||||
|
-- Create books table
|
||||||
|
CREATE TABLE books (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
author TEXT NOT NULL,
|
||||||
|
isbn TEXT,
|
||||||
|
year INTEGER,
|
||||||
|
genre TEXT,
|
||||||
|
description TEXT,
|
||||||
|
rating REAL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Insert sample book data
|
||||||
|
INSERT INTO books (title, author, isbn, year, genre, description, rating) VALUES
|
||||||
|
('The Pragmatic Programmer', 'Andrew Hunt, David Thomas', '978-0135957059', 2019, 'Programming', 'Your journey to mastery in software craftsmanship.', 4.5),
|
||||||
|
('Clean Code', 'Robert C. Martin', '978-0132350884', 2008, 'Programming', 'A handbook of agile software craftsmanship.', 4.7),
|
||||||
|
('Design Patterns', 'Gang of Four', '978-0201633610', 1994, 'Programming', 'Elements of reusable object-oriented software.', 4.6),
|
||||||
|
('The Rust Programming Language', 'Steve Klabnik, Carol Nichols', '978-1718503106', 2023, 'Programming', 'The official book on the Rust programming language.', 4.8),
|
||||||
|
('Structure and Interpretation of Computer Programs', 'Harold Abelson, Gerald Jay Sussman', '978-0262510871', 1996, 'Computer Science', 'Classic text on programming and computer science.', 4.9),
|
||||||
|
('Introduction to Algorithms', 'Thomas H. Cormen et al.', '978-0262033848', 2009, 'Computer Science', 'Comprehensive algorithms textbook.', 4.6),
|
||||||
|
('Code Complete', 'Steve McConnell', '978-0735619678', 2004, 'Programming', 'A practical handbook of software construction.', 4.5),
|
||||||
|
('Designing Data-Intensive Applications', 'Martin Kleppmann', '978-1449373320', 2017, 'Databases', 'The big ideas behind reliable, scalable, and maintainable systems.', 4.8),
|
||||||
|
('Database System Concepts', 'Abraham Silberschatz et al.', '978-0078022159', 2019, 'Databases', 'Comprehensive introduction to database systems.', 4.4),
|
||||||
|
('The Art of Computer Programming, Vol. 1', 'Donald Knuth', '978-0201896831', 1997, 'Computer Science', 'Fundamental algorithms and analysis.', 4.7);
|
||||||
|
|
||||||
|
-- Create a view for books by genre
|
||||||
|
CREATE VIEW books_by_genre AS
|
||||||
|
SELECT genre, COUNT(*) as count, AVG(rating) as avg_rating
|
||||||
|
FROM books
|
||||||
|
GROUP BY genre
|
||||||
|
ORDER BY count DESC;
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Database created: book_catalog.db"
|
||||||
|
echo ""
|
||||||
|
echo "Sample data:"
|
||||||
|
sqlite3 book_catalog.db "SELECT COUNT(*) as total_books FROM books;"
|
||||||
|
echo ""
|
||||||
|
echo "Books by genre:"
|
||||||
|
sqlite3 book_catalog.db "SELECT * FROM books_by_genre;"
|
||||||
|
|
||||||
175
src/lib.rs
175
src/lib.rs
@ -1,10 +1,10 @@
|
|||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use ngx::core::Buffer;
|
use ngx::core::Buffer;
|
||||||
use ngx::ffi::{
|
use ngx::ffi::{
|
||||||
NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, NGX_HTTP_MODULE, NGX_HTTP_LOC_CONF_OFFSET,
|
NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, NGX_HTTP_MAIN_CONF, NGX_HTTP_MODULE,
|
||||||
NGX_RS_MODULE_SIGNATURE, nginx_version, ngx_array_push, ngx_chain_t, ngx_command_t,
|
NGX_HTTP_LOC_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE, nginx_version, ngx_chain_t,
|
||||||
ngx_conf_t, ngx_http_handler_pt, ngx_http_module_t,
|
ngx_command_t, ngx_conf_t, ngx_http_module_t, ngx_int_t, ngx_module_t, ngx_str_t,
|
||||||
ngx_http_phases_NGX_HTTP_ACCESS_PHASE, ngx_int_t, ngx_module_t, ngx_str_t, ngx_uint_t,
|
ngx_uint_t,
|
||||||
};
|
};
|
||||||
use ngx::http::{
|
use ngx::http::{
|
||||||
HttpModule, HttpModuleLocationConf, HttpModuleMainConf, MergeConfigError, NgxHttpCoreModule,
|
HttpModule, HttpModuleLocationConf, HttpModuleMainConf, MergeConfigError, NgxHttpCoreModule,
|
||||||
@ -25,30 +25,21 @@ impl http::HttpModule for Module {
|
|||||||
unsafe { &*addr_of!(ngx_http_howto_module) }
|
unsafe { &*addr_of!(ngx_http_howto_module) }
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t {
|
unsafe extern "C" fn postconfiguration(_cf: *mut ngx_conf_t) -> ngx_int_t {
|
||||||
unsafe {
|
|
||||||
let htcf =
|
|
||||||
NgxHttpCoreModule::main_conf_mut(&*cf).expect("failed to get core main conf");
|
|
||||||
|
|
||||||
let h = ngx_array_push(
|
|
||||||
&mut htcf.phases[ngx_http_phases_NGX_HTTP_ACCESS_PHASE as usize].handlers,
|
|
||||||
) as *mut ngx_http_handler_pt;
|
|
||||||
if h.is_null() {
|
|
||||||
return core::Status::NGX_ERROR.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
// set an Access phase handler
|
|
||||||
*h = Some(howto_access_handler);
|
|
||||||
core::Status::NGX_OK.into()
|
core::Status::NGX_OK.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Implement HttpModuleLocationConf to define our location-specific configuration
|
// Implement HttpModuleLocationConf to define our location-specific configuration
|
||||||
unsafe impl HttpModuleLocationConf for Module {
|
unsafe impl HttpModuleLocationConf for Module {
|
||||||
type LocationConf = ModuleConfig;
|
type LocationConf = ModuleConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement HttpModuleMainConf to define our global configuration
|
||||||
|
unsafe impl HttpModuleMainConf for Module {
|
||||||
|
type MainConf = MainConfig;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a ModuleConfig to save our configuration state.
|
// Create a ModuleConfig to save our configuration state.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct ModuleConfig {
|
struct ModuleConfig {
|
||||||
@ -57,6 +48,21 @@ struct ModuleConfig {
|
|||||||
template_path: String,
|
template_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global configuration for shared templates
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct MainConfig {
|
||||||
|
global_templates_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl http::Merge for MainConfig {
|
||||||
|
fn merge(&mut self, prev: &MainConfig) -> Result<(), MergeConfigError> {
|
||||||
|
if self.global_templates_dir.is_empty() {
|
||||||
|
self.global_templates_dir = prev.global_templates_dir.clone();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Implement our Merge trait to merge configuration with higher layers.
|
// Implement our Merge trait to merge configuration with higher layers.
|
||||||
impl http::Merge for ModuleConfig {
|
impl http::Merge for ModuleConfig {
|
||||||
fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> {
|
fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> {
|
||||||
@ -83,8 +89,8 @@ impl http::Merge for ModuleConfig {
|
|||||||
static ngx_http_howto_module_ctx: ngx_http_module_t = ngx_http_module_t {
|
static ngx_http_howto_module_ctx: ngx_http_module_t = ngx_http_module_t {
|
||||||
preconfiguration: Some(Module::preconfiguration),
|
preconfiguration: Some(Module::preconfiguration),
|
||||||
postconfiguration: Some(Module::postconfiguration),
|
postconfiguration: Some(Module::postconfiguration),
|
||||||
create_main_conf: None,
|
create_main_conf: Some(Module::create_main_conf),
|
||||||
init_main_conf: None,
|
init_main_conf: Some(Module::init_main_conf),
|
||||||
create_srv_conf: None,
|
create_srv_conf: None,
|
||||||
merge_srv_conf: None,
|
merge_srv_conf: None,
|
||||||
create_loc_conf: Some(Module::create_loc_conf),
|
create_loc_conf: Some(Module::create_loc_conf),
|
||||||
@ -133,7 +139,15 @@ pub static mut ngx_http_howto_module: ngx_module_t = ngx_module_t {
|
|||||||
// sure to terminate the array with an empty command.
|
// sure to terminate the array with an empty command.
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
static mut ngx_http_howto_commands: [ngx_command_t; 4] = [
|
static mut ngx_http_howto_commands: [ngx_command_t; 5] = [
|
||||||
|
ngx_command_t {
|
||||||
|
name: ngx_string!("sqlite_global_templates"),
|
||||||
|
type_: (NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
|
||||||
|
set: Some(ngx_http_howto_commands_set_global_templates),
|
||||||
|
conf: 0, // Main conf offset
|
||||||
|
offset: 0,
|
||||||
|
post: std::ptr::null_mut(),
|
||||||
|
},
|
||||||
ngx_command_t {
|
ngx_command_t {
|
||||||
name: ngx_string!("sqlite_db"),
|
name: ngx_string!("sqlite_db"),
|
||||||
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
|
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
|
||||||
@ -171,6 +185,21 @@ static mut ngx_http_howto_commands: [ngx_command_t; 4] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn ngx_http_howto_commands_set_global_templates(
|
||||||
|
cf: *mut ngx_conf_t,
|
||||||
|
_cmd: *mut ngx_command_t,
|
||||||
|
conf: *mut c_void,
|
||||||
|
) -> *mut c_char {
|
||||||
|
unsafe {
|
||||||
|
let conf = &mut *(conf as *mut MainConfig);
|
||||||
|
let args = (*(*cf).args).elts as *mut ngx_str_t;
|
||||||
|
conf.global_templates_dir = (*args.add(1)).to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn ngx_http_howto_commands_set_db_path(
|
extern "C" fn ngx_http_howto_commands_set_db_path(
|
||||||
cf: *mut ngx_conf_t,
|
cf: *mut ngx_conf_t,
|
||||||
@ -211,11 +240,51 @@ extern "C" fn ngx_http_howto_commands_set_template_path(
|
|||||||
let conf = &mut *(conf as *mut ModuleConfig);
|
let conf = &mut *(conf as *mut ModuleConfig);
|
||||||
let args = (*(*cf).args).elts as *mut ngx_str_t;
|
let args = (*(*cf).args).elts as *mut ngx_str_t;
|
||||||
conf.template_path = (*args.add(1)).to_string();
|
conf.template_path = (*args.add(1)).to_string();
|
||||||
|
|
||||||
|
// Set the content handler for this location
|
||||||
|
let clcf = NgxHttpCoreModule::location_conf_mut(&*cf)
|
||||||
|
.expect("failed to get core location conf");
|
||||||
|
clcf.handler = Some(howto_access_handler);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ptr::null_mut()
|
std::ptr::null_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load all .hbs templates from a directory into the Handlebars registry
|
||||||
|
fn load_templates_from_dir(reg: &mut Handlebars, dir_path: &str) -> std::io::Result<usize> {
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
let dir = Path::new(dir_path);
|
||||||
|
if !dir.exists() || !dir.is_dir() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut count = 0;
|
||||||
|
for entry in fs::read_dir(dir)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if path.is_file() {
|
||||||
|
if let Some(ext) = path.extension() {
|
||||||
|
if ext == "hbs" {
|
||||||
|
if let Some(stem) = path.file_stem() {
|
||||||
|
if let Some(name) = stem.to_str() {
|
||||||
|
if let Err(e) = reg.register_template_file(name, &path) {
|
||||||
|
eprintln!("Failed to register template {}: {}", path.display(), e);
|
||||||
|
} else {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
|
||||||
// Execute a generic SQL query and return results as JSON-compatible data
|
// Execute a generic SQL query and return results as JSON-compatible data
|
||||||
fn execute_query(db_path: &str, query: &str) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
|
fn execute_query(db_path: &str, query: &str) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
|
||||||
let conn = Connection::open(db_path)?;
|
let conn = Connection::open(db_path)?;
|
||||||
@ -271,6 +340,33 @@ http_request_handler!(howto_access_handler, |request: &mut http::Request| {
|
|||||||
|
|
||||||
ngx_log_debug_http!(request, "sqlite module handler called");
|
ngx_log_debug_http!(request, "sqlite module handler called");
|
||||||
|
|
||||||
|
// Resolve template path relative to document root and location
|
||||||
|
let core_loc_conf =
|
||||||
|
NgxHttpCoreModule::location_conf(request).expect("failed to get core location conf");
|
||||||
|
let doc_root = match (*core_loc_conf).root.to_str() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
ngx_log_debug_http!(request, "failed to decode root path: {}", e);
|
||||||
|
return http::HTTPStatus::INTERNAL_SERVER_ERROR.into();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let uri = match request.path().to_str() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
ngx_log_debug_http!(request, "failed to decode URI path: {}", e);
|
||||||
|
return http::HTTPStatus::INTERNAL_SERVER_ERROR.into();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let template_full_path = format!("{}{}/{}", doc_root, uri, co.template_path);
|
||||||
|
|
||||||
|
ngx_log_debug_http!(request, "resolved template path: {}", template_full_path);
|
||||||
|
|
||||||
|
// Get the directory containing the main template for local templates
|
||||||
|
let template_dir = std::path::Path::new(&template_full_path)
|
||||||
|
.parent()
|
||||||
|
.and_then(|p| p.to_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
// Execute the configured SQL query
|
// Execute the configured SQL query
|
||||||
let results = match execute_query(&co.db_path, &co.query) {
|
let results = match execute_query(&co.db_path, &co.query) {
|
||||||
Ok(results) => results,
|
Ok(results) => results,
|
||||||
@ -280,12 +376,39 @@ http_request_handler!(howto_access_handler, |request: &mut http::Request| {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Setup Handlebars and register the configured template
|
// Setup Handlebars and load templates
|
||||||
let mut reg = Handlebars::new();
|
let mut reg = Handlebars::new();
|
||||||
match reg.register_template_file("template", &co.template_path) {
|
|
||||||
Ok(_) => (),
|
// First, load global templates if configured
|
||||||
|
let main_conf = Module::main_conf(request).expect("main config is none");
|
||||||
|
if !main_conf.global_templates_dir.is_empty() {
|
||||||
|
match load_templates_from_dir(&mut reg, &main_conf.global_templates_dir) {
|
||||||
|
Ok(count) => {
|
||||||
|
ngx_log_debug_http!(request, "loaded {} global templates from {}", count, main_conf.global_templates_dir);
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ngx_log_debug_http!(request, "failed to load template: {}", e);
|
ngx_log_debug_http!(request, "warning: failed to load global templates: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then, load local templates (these override global ones)
|
||||||
|
match load_templates_from_dir(&mut reg, template_dir) {
|
||||||
|
Ok(count) => {
|
||||||
|
ngx_log_debug_http!(request, "loaded {} local templates from {}", count, template_dir);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
ngx_log_debug_http!(request, "warning: failed to load local templates: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, register the main template (overriding if it was loaded from directories)
|
||||||
|
match reg.register_template_file("template", &template_full_path) {
|
||||||
|
Ok(_) => {
|
||||||
|
ngx_log_debug_http!(request, "registered main template: {}", template_full_path);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
ngx_log_debug_http!(request, "failed to load main template: {}", e);
|
||||||
return http::HTTPStatus::INTERNAL_SERVER_ERROR.into();
|
return http::HTTPStatus::INTERNAL_SERVER_ERROR.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
37
start_book_catalog.sh
Executable file
37
start_book_catalog.sh
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
#!/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/libnginx_test.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 ."
|
||||||
|
|
||||||
Reference in New Issue
Block a user