v2.0: Laravel + Inertia + Vue foundation complete

- Replaced Next.js + Express + React stack with Laravel + Inertia + Vue
- Created database migrations for docket_entries, documents, subscriptions, admin_users
- Built Eloquent models with relationships (DocketEntry, Document, Subscription, AdminUser)
- Implemented HomeController with Inertia.js integration
- Created Vue home page component matching v1.0 design exactly
- Installed Laravel Breeze for authentication scaffolding
- Configured Vite 7 for Vue 3 + TypeScript compilation
- Updated .gitignore for Laravel project structure
- Tested locally - website rendering correctly with empty database
- All v1.0 Next.js/Express files removed, replaced with Laravel structure

Technology Stack:
- Backend: Laravel 12.43.1, PHP 8.3.28, Eloquent ORM
- Frontend: Vue 3, TypeScript, Inertia.js, Tailwind CSS 3.x
- Build: Vite 7.x, Composer 2.9.2
- Database: SQLite (dev), PostgreSQL (production)

Next steps: Email subscription API, document downloads, admin dashboard
This commit is contained in:
TheMaddax 2025-12-17 15:44:26 -07:00
parent 4e7c4252f8
commit 80657cc209
4881 changed files with 6317 additions and 408368 deletions

View file

@ -1,15 +1,18 @@
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[{package.json,*.yml}]
end_of_line = lf
indent_size = 4
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[compose.yaml]
indent_size = 4

69
.env
View file

@ -1,10 +1,65 @@
# Production Environment Configuration
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:FabRZZvfMqwKRSPj2KYehBzYNrYI+hX247oLw58LRVs=
APP_DEBUG=true
APP_URL=http://localhost
# Database Configuration
DB_PASSWORD=h3LvCItcZKcyRdsmeH1X+nVams7RAf89
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
# JWT Configuration
JWT_SECRET=ut02ymAdoHdrWAPgPsX25PMuhgqO54/Tow5JvgRdWeM=
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# Email Configuration (Keep existing working values)
GOOGLE_APP_PASSWORD=ojvlysraxwjriwzy
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

65
.env.example Normal file
View file

@ -0,0 +1,65 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

View file

@ -1,16 +0,0 @@
# Production Environment Configuration
# Copy this file and set your actual production values
# Database Configuration
DB_PASSWORD=your_secure_database_password_here
# JWT Configuration (Generate a secure random string)
JWT_SECRET=your_secure_jwt_secret_here_minimum_32_characters
# Email Configuration (Keep existing working values)
GOOGLE_APP_PASSWORD=ojvlysraxwjriwzy
# Optional: Override other settings if needed
# SMTP_HOST=smtp.gmail.com
# SMTP_PORT=587
# GOOGLE_EMAIL=system@deafgain.org

11
.gitattributes vendored Normal file
View file

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

91
.gitignore vendored
View file

@ -1,47 +1,70 @@
# macOS
# Laravel & PHP
*.log
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.fleet
/.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/vendor
Homestead.json
Homestead.yaml
Thumbs.db
# Node modules
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Node & NPM
/node_modules
/public/build
/public/hot
# Environment variables
.env.local
.env.development.local
.env.test.local
.env.production.local
# Storage & Uploads
/public/storage
/storage/*.key
/storage/pail
/storage/app/public/*
!/storage/app/public/.gitkeep
# Logs
logs
*.log
# Database
/database/*.sqlite
/database/*.sqlite-journal
*.db
*.db-shm
*.db-wal
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# Build outputs
dist/
build/
.next/
# Build artifacts
/public/hot
/public/storage
/storage/*.key
/bootstrap/cache/*
!/bootstrap/cache/.gitkeep
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS generated files
*~
# OS
.DS_Store
Thumbs.db
# Composer
/vendor/
composer.lock
# NPM/Yarn
node_modules/
npm-debug.log
yarn-error.log
package-lock.json
pnpm-lock.yaml
# TypeScript
*.tsbuildinfo

View file

@ -1,143 +0,0 @@
# Caddyfile for Eliza Kragh v. MAD Court Docket Website
# This file should be placed in the Caddy configuration directory on the remote server
# Replace with your actual domain
docket.example.com {
# Enable automatic HTTPS
tls {
# Use Let's Encrypt for SSL certificates
# email admin@example.com
}
# Security headers
header {
# Security headers for legal document website
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
X-XSS-Protection "1; mode=block"
Referrer-Policy "strict-origin-when-cross-origin"
# Remove server information
-Server
-X-Powered-By
}
# Rate limiting for API endpoints
rate_limit {
zone api {
key {remote_host}
events 100
window 1m
}
zone auth {
key {remote_host}
events 10
window 1m
}
}
# API routes with rate limiting
@api path /api/*
rate_limit @api api
@auth path /api/auth/*
rate_limit @auth auth
# Reverse proxy to frontend container
reverse_proxy frontend:3000 {
# Health check
health_uri /
health_interval 30s
health_timeout 10s
# Load balancing (if multiple instances)
lb_policy round_robin
# Headers for proper forwarding
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
}
# Logging for legal compliance
log {
output file /var/log/caddy/docket-access.log {
roll_size 100mb
roll_keep 10
roll_keep_for 720h
}
format json
level INFO
}
# Error handling
handle_errors {
@5xx expression {http.error.status_code} >= 500
respond @5xx "Service temporarily unavailable" 503
@4xx expression {http.error.status_code} >= 400 && {http.error.status_code} < 500
respond @4xx "Page not found" 404
}
}
# Admin subdomain (optional - for enhanced security)
admin.docket.example.com {
tls {
# email admin@example.com
}
# Additional security for admin interface
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'"
}
# Stricter rate limiting for admin
rate_limit {
zone admin {
key {remote_host}
events 20
window 1m
}
}
rate_limit admin
# Only allow admin routes
@admin path /admin*
handle @admin {
reverse_proxy frontend:3000 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
# Redirect non-admin requests to main site
redir https://docket.example.com{uri} permanent
log {
output file /var/log/caddy/admin-access.log {
roll_size 50mb
roll_keep 20
roll_keep_for 2160h # 90 days
}
format json
level INFO
}
}
# HTTP to HTTPS redirect
http://docket.example.com {
redir https://docket.example.com{uri} permanent
}
http://admin.docket.example.com {
redir https://admin.docket.example.com{uri} permanent
}

View file

@ -1,510 +0,0 @@
# Eliza Kragh v. MAD Court Docket Website - Design Specification
## Project Overview
A public-facing website to display court documents and filings for the ongoing lawsuit: **Eliza Kragh v. Montana Association of the Deaf**. The system provides public transparency for court proceedings while allowing administrative management of documents and user notifications.
## Technical Architecture
### System Components
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Next.js 15.4 │ │ Express.js 6 │ │ PostgreSQL 17.5 │
│ Frontend │◄──►│ Backend API │◄──►│ Database │
│ (Port 3000) │ │ (Port 3001) │ │ (Port 5432) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ ┌─────────────────┐ │
│ │ Redis │ │
└──────────────►│ Cache/Session │◄─────────────┘
│ (Port 6379) │
└─────────────────┘
┌─────────────────┐
│ File System │
│ /uploads/ │
└─────────────────┘
```
**Technology Stack (June 2025 Latest Versions):**
- **Frontend**: Next.js 15.4.0 with React 19, TypeScript 6.0, Tailwind CSS
- **Backend**: Node.js 22 LTS, Express.js 6.0, TypeScript 6.0, Prisma 5.0
- **Database**: PostgreSQL 17.5 with Redis caching
- **Infrastructure**: Docker Engine 28.1.1, Nginx reverse proxy
## Database Schema
### Tables Structure
```sql
-- Docket Entries (main court filings)
CREATE TABLE docket_entries (
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
document_type VARCHAR(100) NOT NULL,
summary TEXT NOT NULL,
court_notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Documents (PDFs associated with entries)
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
docket_entry_id INTEGER REFERENCES docket_entries(id) ON DELETE CASCADE,
original_filename VARCHAR(255) NOT NULL,
stored_filename VARCHAR(255) NOT NULL UNIQUE,
file_path VARCHAR(500) NOT NULL,
title VARCHAR(255) NOT NULL,
summary TEXT,
notes TEXT,
is_primary BOOLEAN DEFAULT FALSE,
file_size INTEGER NOT NULL,
display_order INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Email Subscriptions
CREATE TABLE subscriptions (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
unsubscribe_token VARCHAR(255) UNIQUE
);
-- Admin Users
CREATE TABLE admin_users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP
);
-- Indexes for performance
CREATE INDEX idx_docket_entries_date ON docket_entries(date DESC);
CREATE INDEX idx_documents_docket_entry ON documents(docket_entry_id);
CREATE INDEX idx_documents_display_order ON documents(docket_entry_id, display_order);
CREATE INDEX idx_subscriptions_active ON subscriptions(is_active);
```
## API Endpoints
### Public Endpoints
```
GET /api/docket-entries
Response: Array of docket entries with associated documents
{
"entries": [
{
"id": 1,
"date": "2024-12-15",
"document_type": "Motion to Dismiss",
"summary": "Defendant's motion...",
"court_notes": "Motion filed within deadline...",
"documents": [
{
"id": 1,
"title": "Main Document",
"summary": "Primary motion document",
"notes": "Filed by defendant's counsel",
"is_primary": true,
"display_order": 0
}
]
}
]
}
GET /api/documents/:id/download
Response: PDF file stream
Headers: Content-Type: application/pdf
POST /api/subscribe
Body: { "email": "user@example.com" }
Response: { "success": true, "message": "Subscribed successfully" }
GET /api/unsubscribe/:token
Response: Unsubscribe confirmation page
```
### Admin Endpoints (Protected)
```
POST /api/auth/login
Body: { "username": "admin", "password": "password" }
Response: { "token": "jwt-token", "expires": "timestamp" }
POST /api/docket-entries
Body: {
"date": "2024-12-15",
"document_type": "Motion",
"summary": "Entry summary",
"court_notes": "Court notes",
"documents": [
{
"title": "Main Document",
"summary": "Document summary",
"notes": "Document notes",
"is_primary": true,
"file": "base64-encoded-pdf"
}
]
}
PUT /api/docket-entries/:id
Body: Updated entry data
Response: Updated entry object
DELETE /api/docket-entries/:id
Response: { "success": true }
POST /api/documents
Body: FormData with file and metadata
Response: Created document object
PUT /api/documents/:id
Body: Updated document metadata
Response: Updated document object
PUT /api/documents/:id/replace
Body: FormData with new file
Response: Updated document object
DELETE /api/documents/:id
Response: { "success": true }
```
## File Management System
### UUID-Based Naming Scheme
```
Format: {entry-id}_{document-type}_{uuid}.pdf
Examples:
001_main_a7b3c9d2-4e5f-6789-abcd-ef0123456789.pdf
001_exhibit_b8c4d0e3-5f60-789a-bcde-f01234567890.pdf
002_main_c9d5e1f4-6071-890b-cdef-012345678901.pdf
```
### Directory Structure
```
uploads/
├── 2024/
│ ├── 12/
│ │ ├── 001_main_a7b3c9d2-4e5f-6789-abcd-ef0123456789.pdf
│ │ ├── 001_exhibit_b8c4d0e3-5f60-789a-bcde-f01234567890.pdf
│ │ └── 002_main_c9d5e1f4-6071-890b-cdef-012345678901.pdf
│ └── 11/
│ └── [previous month files]
└── 2025/
└── [future files]
```
### File Upload Process
1. Validate file type (PDF only) and size (max 10MB)
2. Generate UUID for filename
3. Create year/month directory structure
4. Save file with UUID-based name
5. Store metadata in database
6. Return success response with document ID
## Frontend Components
### Public Interface Components
```
components/
├── Layout/
│ ├── Header.tsx # Site header with title and subscribe button
│ ├── Footer.tsx # Site footer
│ └── Layout.tsx # Main layout wrapper
├── Docket/
│ ├── DocketList.tsx # List of all docket entries
│ ├── DocketEntry.tsx # Individual entry display
│ ├── DocumentList.tsx # Documents within an entry
│ └── DocumentViewer.tsx # PDF viewing modal
├── Subscription/
│ ├── SubscribeForm.tsx # Email subscription form
│ └── SubscribeModal.tsx # Subscription confirmation modal
└── Common/
├── LoadingSpinner.tsx # Loading states
├── ErrorMessage.tsx # Error display
└── Button.tsx # Reusable button component
```
### Admin Interface Components
```
components/admin/
├── Auth/
│ └── LoginForm.tsx # Admin login form
├── Dashboard/
│ ├── Dashboard.tsx # Main admin dashboard
│ ├── EntryList.tsx # Manage existing entries
│ └── Stats.tsx # Basic statistics
├── Entry/
│ ├── CreateEntry.tsx # New docket entry form
│ ├── EditEntry.tsx # Edit existing entry
│ └── DeleteEntry.tsx # Delete confirmation
├── Document/
│ ├── DocumentUpload.tsx # Multi-file upload component
│ ├── DocumentEdit.tsx # Edit document metadata
│ └── DocumentReplace.tsx # Replace PDF file
└── Layout/
├── AdminLayout.tsx # Admin layout wrapper
└── AdminNav.tsx # Admin navigation
```
## User Interface Layouts
### Public Docket Page
```
┌─────────────────────────────────────────────────────────────┐
│ ELIZA KRAGH v. MAD DOCKET │
│ [Subscribe] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Case Information │
│ ───────────────── │
│ Case: Eliza Kragh v. Montana Association of the Deaf │
│ Status: Ongoing │
│ Last Updated: December 15, 2024 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Docket Entries │
│ ───────────────── │
│ [Date] | [Document Type] | [Summary] | [View] │
│ ──────────────────────────────────────────────────────── │
│ 12/15/24 | Motion to Dismiss | Defendant's motion... |[+] │
│ └─ 📄 Main Document │
│ └─ 📄 Exhibit A - Financial Records │
│ └─ 📄 Exhibit B - Email Correspondence │
│ ──────────────────────────────────────────────────────── │
│ 12/10/24 | Complaint | Plaintiff's initial filing... |[+] │
│ └─ 📄 Main Document │
│ └─ 📄 Exhibit 1 - Contract Agreement │
│ ──────────────────────────────────────────────────────── │
│ [More entries...] │
└─────────────────────────────────────────────────────────────┘
```
### Admin Dashboard
```
┌─────────────────────────────────────────────────────────────┐
│ ADMIN DASHBOARD [Logout] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ [+ New Docket Entry] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Manage Existing Entries │
│ ────────────────────── │
│ 12/15/24 | Motion to Dismiss | [Edit] [Delete] │
│ └─ 📄 Main Document [Edit] [Replace] [Del] │
│ └─ 📄 Exhibit A [Edit] [Replace] [Del] │
│ └─ 📄 Exhibit B [Edit] [Replace] [Del] │
│ └─ [+ Add Document to Entry] │
│ ──────────────────────────────────────────────────────── │
│ 12/10/24 | Complaint | [Edit] [Delete] │
│ └─ 📄 Main Document [Edit] [Replace] [Del] │
│ └─ 📄 Exhibit 1 [Edit] [Replace] [Del] │
│ └─ [+ Add Document to Entry] │
└─────────────────────────────────────────────────────────────┘
```
## Docker Configuration
### docker-compose.yml
```yaml
version: '3.8'
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_API_URL=http://backend:3001/api
depends_on:
- backend
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "3001:3001"
environment:
- DATABASE_URL=postgresql://docket_user:docket_pass@postgres:5432/docket_db
- REDIS_URL=redis://redis:6379
- JWT_SECRET=your-secret-key
- UPLOAD_DIR=/app/uploads
volumes:
- uploads:/app/uploads
depends_on:
- postgres
- redis
postgres:
image: postgres:15
environment:
- POSTGRES_DB=docket_db
- POSTGRES_USER=docket_user
- POSTGRES_PASSWORD=docket_pass
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
depends_on:
- frontend
- backend
volumes:
postgres_data:
uploads:
```
## Security Implementation
### Authentication & Authorization
- JWT tokens for admin authentication
- Token expiration and refresh mechanism
- Protected routes middleware
- Role-based access control
### File Security
- Files served through API endpoints only
- No direct file system access
- File type validation (PDF only)
- File size limits (10MB max)
- UUID-based naming prevents enumeration
### Input Validation
- All API inputs validated with Joi/Zod
- SQL injection prevention with Prisma ORM
- XSS protection with input sanitization
- CSRF protection for admin forms
### Rate Limiting
- API endpoint rate limiting
- File upload rate limiting
- Login attempt limiting
- Email subscription rate limiting
## Email Notification System
### Subscription Process
1. User enters email on public site
2. Email validated and stored in database
3. Confirmation email sent with unsubscribe token
4. User added to active subscribers list
### Notification Trigger
1. Admin uploads new docket entry
2. System checks for active subscribers
3. Email notification queued for each subscriber
4. Background job processes email queue
5. Emails sent with entry summary and link
### Email Template
```
Subject: New Filing in Eliza Kragh v. Montana Association of the Deaf
Dear Subscriber,
A new document has been filed in the case of Eliza Kragh v. Montana Association of the Deaf:
Date: December 15, 2024
Document Type: Motion to Dismiss
Summary: Defendant's motion to dismiss based on lack of jurisdiction...
View the complete filing and all documents at:
https://docket-site.com
To unsubscribe from these notifications, click here:
https://docket-site.com/unsubscribe/[token]
Best regards,
Court Docket Notification System
```
## Performance Optimization
### Frontend Optimization
- Next.js static generation for public pages
- Image optimization for any graphics
- Code splitting and lazy loading
- PDF.js worker for document rendering
- Caching strategies for API responses
### Backend Optimization
- Database query optimization with indexes
- Redis caching for frequently accessed data
- File streaming for PDF downloads
- Connection pooling for database
- Compression middleware for API responses
### Infrastructure Optimization
- Nginx reverse proxy with caching
- Gzip compression for static assets
- CDN integration for file serving
- Database connection pooling
- Redis session storage
## Deployment Strategy
### Development Environment
- Docker Compose for local development
- Hot reloading for both frontend and backend
- Development database with sample data
- Email testing with local SMTP server
### Production Environment
- Docker Swarm or Kubernetes deployment
- SSL/TLS certificates with Let's Encrypt
- Production database with backups
- Real SMTP service for email notifications
- Monitoring and logging with ELK stack
### CI/CD Pipeline
- GitHub Actions for automated testing
- Docker image building and pushing
- Automated deployment to staging
- Manual approval for production deployment
- Database migration automation
## Testing Strategy
### Unit Testing
- Backend API endpoints with Jest
- Frontend components with React Testing Library
- Database operations with test database
- File upload functionality testing
### Integration Testing
- End-to-end user workflows with Cypress
- API integration testing
- Email notification testing
- File serving and download testing
### Security Testing
- Authentication and authorization testing
- Input validation testing
- File upload security testing
- SQL injection prevention testing
This comprehensive design specification serves as the complete technical reference for implementing the Eliza Kragh v. MAD court docket website.

297
README.md
View file

@ -1,278 +1,59 @@
# Elizabeth Kragh v. Montana Association of the Deaf - Court Docket Website
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
A public-facing website to display court documents and filings for the ongoing lawsuit: **Elizabeth Kragh v. Montana Association of the Deaf**. The system provides public transparency for court proceedings while allowing administrative management of documents and user notifications.
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## 🌐 Live Website
## About Laravel
- **Public Access**: https://mad-lawsuit.org
- **Admin Panel**: https://mad-lawsuit.org/admin
- **Status**: LIVE AND OPERATIONAL ✅
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
## 🏗️ Architecture
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
- **Frontend**: Next.js 15.4.0 with React 19, TypeScript 6.0, Tailwind CSS
- **Backend**: Node.js 22 LTS, Express.js 6.0, TypeScript 6.0, Prisma 5.0
- **Database**: PostgreSQL 17.5 with Redis caching
- **Infrastructure**: Docker Engine 28.1.1, Nginx reverse proxy
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## 🚀 Quick Start
## Learning Laravel
### Prerequisites
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application.
- Node.js 22 LTS
- Docker Engine 28.1.1 & Docker Compose
- pnpm (recommended package manager)
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
### Development Setup
## Laravel Sponsors
1. **Clone the repository**
```bash
git clone <repository-url>
cd eliza-mad-docket-website
```
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
2. **Start the development environment**
```bash
docker-compose up -d
```
### Premium Partners
3. **Install dependencies**
```bash
# Backend
cd backend
pnpm install
# Frontend
cd ../frontend
pnpm install
```
- **[Vehikl](https://vehikl.com)**
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Redberry](https://redberry.international/laravel-development)**
- **[Active Logic](https://activelogic.com)**
4. **Set up the database**
```bash
cd backend
pnpm prisma migrate dev
pnpm prisma generate
```
## Contributing
5. **Start development servers**
```bash
# Backend (in backend directory)
pnpm dev
# Frontend (in frontend directory)
pnpm dev
```
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
6. **Access the application**
- Frontend: http://localhost:3000
- Backend API: http://localhost:3001
- Database: localhost:5432
- Redis: localhost:6379
## Code of Conduct
## 📁 Project Structure
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
```
eliza-mad-docket/
├── backend/ # Express.js API
│ ├── src/
│ │ ├── routes/ # API routes
│ │ ├── middleware/ # Express middleware
│ │ ├── utils/ # Utility functions
│ │ └── index.ts # Main server file
│ ├── prisma/ # Database schema & migrations
│ ├── Dockerfile # Backend container
│ └── package.json
├── frontend/ # Next.js application
│ ├── src/
│ │ ├── app/ # Next.js 13+ app directory
│ │ ├── components/ # React components
│ │ └── lib/ # Utility libraries
│ ├── Dockerfile # Frontend container
│ └── package.json
├── cline_docs/ # Memory Bank documentation
├── docker-compose.yml # Container orchestration
└── DESIGN_SPECIFICATION.md # Complete technical spec
```
## Security Vulnerabilities
## 🔧 Environment Variables
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
### Backend (.env)
```env
DATABASE_URL=postgresql://docket_user:docket_pass@localhost:5432/docket_db
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-secret-key-change-in-production
JWT_EXPIRES_IN=24h
UPLOAD_DIR=/app/uploads
MAX_FILE_SIZE=10485760
NODE_ENV=development
```
## License
### Frontend (.env.local)
```env
NEXT_PUBLIC_API_URL=http://localhost:3001/api
```
## 🗄️ Database Schema
The application uses PostgreSQL with the following main tables:
- **docket_entries**: Main court filings
- **documents**: PDF files associated with entries
- **subscriptions**: Email subscriptions for notifications
- **admin_users**: Administrative users
## 🔐 Security Features
- JWT authentication for admin access
- File access control through API endpoints
- UUID-based file naming for security
- Input validation and sanitization
- Rate limiting on API endpoints
- CORS protection
## 📄 File Management
- **Storage**: Local filesystem with Docker volumes
- **Naming**: UUID-based scheme: `{entry-id}_{document-type}_{uuid}.pdf`
- **Structure**: Organized by year/month directories
- **Security**: Files served only through API endpoints
- **Limits**: 10MB maximum file size, PDF only
## 🚀 Deployment
### Production Deployment
For production deployment with Portainer and Caddy, see the complete [DEPLOYMENT.md](./DEPLOYMENT.md) guide.
**Quick Production Setup:**
1. **Prepare environment**
```bash
cp .env.prod .env
# Edit .env with secure values
```
2. **Deploy with production compose**
```bash
docker-compose -f docker-compose.prod.yml up -d
```
3. **Set up admin user for Elizabeth**
```bash
docker exec -it backend_container node setup-admin-fixed.js
```
## 🔐 Admin Access
### Current Production Credentials
- **URL**: https://mad-lawsuit.org/admin
- **Username**: `eliza`
- **Password**: `AXEoZWk2AMAD0naw`
- **Status**: Active and functional ✅
**⚠️ Security Note**: These credentials were generated during production deployment. Change them immediately after first login for enhanced security.
### Development Setup
1. **Build containers**
```bash
docker-compose build
```
2. **Run development stack**
```bash
docker-compose up -d
```
3. **Run database migrations**
```bash
docker-compose exec backend pnpm prisma migrate deploy
```
### Environment-specific configurations
- **Development**: Hot reloading, detailed logging, demo credentials
- **Production**: Optimized builds, security headers, SSL/TLS, secure credentials
## 🧪 Testing
```bash
# Backend tests
cd backend
pnpm test
# Frontend tests
cd frontend
pnpm test
# Type checking
pnpm type-check
```
## 📊 API Endpoints
### Public Endpoints
- `GET /api/docket-entries` - Get all docket entries with documents
- `GET /api/documents/:id/download` - Download PDF file
- `POST /api/subscribe` - Subscribe to email notifications
- `GET /api/unsubscribe/:token` - Unsubscribe from notifications
### Admin Endpoints (Protected)
- `POST /api/auth/login` - Admin authentication
- `POST /api/docket-entries` - Create new docket entry
- `PUT /api/docket-entries/:id` - Update docket entry
- `DELETE /api/docket-entries/:id` - Delete docket entry
- `POST /api/documents` - Upload document
- `PUT /api/documents/:id` - Update document metadata
- `DELETE /api/documents/:id` - Delete document
## 🎨 UI Features
### Public Interface
- Clean, responsive design
- Document viewing with PDF.js
- Email subscription system
- Mobile-friendly layout
- Accessibility compliant
### Admin Interface
- Secure login system
- Multi-file upload support
- Document management (CRUD operations)
- File replacement capability
- Metadata editing
## 📧 Email Notifications
- Automatic notifications for new filings
- Unsubscribe functionality
- HTML email templates
- Background job processing
## 🔍 Monitoring & Logging
- Structured logging with Winston
- Request/response logging
- Error tracking and reporting
- Health check endpoints
- Performance monitoring
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request
## 📝 License
This project is licensed under the MIT License - see the LICENSE file for details.
## 📞 Support
For questions or issues, please contact the development team or create an issue in the repository.
---
**Note**: This is a public transparency tool for court proceedings. All uploaded documents become publicly accessible through this website.
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View file

@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use Inertia\Response;
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*/
public function create(): Response
{
return Inertia::render('Auth/Login', [
'canResetPassword' => Route::has('password.request'),
'status' => session('status'),
]);
}
/**
* Handle an incoming authentication request.
*/
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(route('dashboard', absolute: false));
}
/**
* Destroy an authenticated session.
*/
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
class ConfirmablePasswordController extends Controller
{
/**
* Show the confirm password view.
*/
public function show(): Response
{
return Inertia::render('Auth/ConfirmPassword');
}
/**
* Confirm the user's password.
*/
public function store(Request $request): RedirectResponse
{
if (! Auth::guard('web')->validate([
'email' => $request->user()->email,
'password' => $request->password,
])) {
throw ValidationException::withMessages([
'password' => __('auth.password'),
]);
}
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended(route('dashboard', absolute: false));
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class EmailVerificationNotificationController extends Controller
{
/**
* Send a new email verification notification.
*/
public function store(Request $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false));
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class EmailVerificationPromptController extends Controller
{
/**
* Display the email verification prompt.
*/
public function __invoke(Request $request): RedirectResponse|Response
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(route('dashboard', absolute: false))
: Inertia::render('Auth/VerifyEmail', ['status' => session('status')]);
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
class NewPasswordController extends Controller
{
/**
* Display the password reset view.
*/
public function create(Request $request): Response
{
return Inertia::render('Auth/ResetPassword', [
'email' => $request->email,
'token' => $request->route('token'),
]);
}
/**
* Handle an incoming new password request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'token' => 'required',
'email' => 'required|email',
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
if ($status == Password::PASSWORD_RESET) {
return redirect()->route('login')->with('status', __($status));
}
throw ValidationException::withMessages([
'email' => [trans($status)],
]);
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
class PasswordController extends Controller
{
/**
* Update the user's password.
*/
public function update(Request $request): RedirectResponse
{
$validated = $request->validate([
'current_password' => ['required', 'current_password'],
'password' => ['required', Password::defaults(), 'confirmed'],
]);
$request->user()->update([
'password' => Hash::make($validated['password']),
]);
return back();
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Inertia\Response;
class PasswordResetLinkController extends Controller
{
/**
* Display the password reset link request view.
*/
public function create(): Response
{
return Inertia::render('Auth/ForgotPassword', [
'status' => session('status'),
]);
}
/**
* Handle an incoming password reset link request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'email' => 'required|email',
]);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$status = Password::sendResetLink(
$request->only('email')
);
if ($status == Password::RESET_LINK_SENT) {
return back()->with('status', __($status));
}
throw ValidationException::withMessages([
'email' => [trans($status)],
]);
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use Inertia\Inertia;
use Inertia\Response;
class RegisteredUserController extends Controller
{
/**
* Display the registration view.
*/
public function create(): Response
{
return Inertia::render('Auth/Register');
}
/**
* Handle an incoming registration request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
event(new Registered($user));
Auth::login($user);
return redirect(route('dashboard', absolute: false));
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Http\RedirectResponse;
class VerifyEmailController extends Controller
{
/**
* Mark the authenticated user's email address as verified.
*/
public function __invoke(EmailVerificationRequest $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers;
use App\Models\DocketEntry;
use Inertia\Inertia;
use Inertia\Response;
class HomeController extends Controller
{
/**
* Display the home page with all docket entries.
*/
public function index(): Response
{
$docketEntries = DocketEntry::with('documents')
->orderBy('date', 'desc')
->get();
return Inertia::render('Home', [
'docketEntries' => $docketEntries,
'caseInfo' => [
'title' => 'Elizabeth Kragh v. Montana Association of the Deaf',
'status' => 'Active Litigation',
'lastUpdated' => $docketEntries->first()?->date?->format('F j, Y') ?? 'No entries yet',
],
]);
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ProfileUpdateRequest;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Inertia\Inertia;
use Inertia\Response;
class ProfileController extends Controller
{
/**
* Display the user's profile form.
*/
public function edit(Request $request): Response
{
return Inertia::render('Profile/Edit', [
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
'status' => session('status'),
]);
}
/**
* Update the user's profile information.
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
return Redirect::route('profile.edit');
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validate([
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return Redirect::to('/');
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
/**
* The root template that is loaded on the first page visit.
*
* @var string
*/
protected $rootView = 'app';
/**
* Determine the current asset version.
*/
public function version(Request $request): ?string
{
return parent::version($request);
}
/**
* Define the props that are shared by default.
*
* @return array<string, mixed>
*/
public function share(Request $request): array
{
return [
...parent::share($request),
'auth' => [
'user' => $request->user(),
],
];
}
}

View file

@ -0,0 +1,85 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
/**
* Attempt to authenticate the request's credentials.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
/**
* Ensure the login request is not rate limited.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
/**
* Get the rate limiting throttle key for the request.
*/
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ProfileUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'lowercase',
'email',
'max:255',
Rule::unique(User::class)->ignore($this->user()->id),
],
];
}
}

34
app/Models/AdminUser.php Normal file
View file

@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
class AdminUser extends Model
{
protected $fillable = [
'username',
'password',
];
protected $hidden = [
'password',
];
/**
* Automatically hash password when setting.
*/
public function setPasswordAttribute($value)
{
$this->attributes['password'] = Hash::make($value);
}
/**
* Verify password.
*/
public function verifyPassword($password): bool
{
return Hash::check($password, $this->password);
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class DocketEntry extends Model
{
protected $fillable = [
'date',
'title',
'summary',
'notes',
];
protected $casts = [
'date' => 'date',
];
/**
* Get the documents for the docket entry.
*/
public function documents(): HasMany
{
return $this->hasMany(Document::class)->orderBy('display_order');
}
}

35
app/Models/Document.php Normal file
View file

@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Document extends Model
{
protected $fillable = [
'docket_entry_id',
'title',
'original_filename',
'stored_filename',
'file_path',
'file_size',
'mime_type',
'summary',
'notes',
'display_order',
];
protected $casts = [
'file_size' => 'integer',
'display_order' => 'integer',
];
/**
* Get the docket entry that owns the document.
*/
public function docketEntry(): BelongsTo
{
return $this->belongsTo(DocketEntry::class);
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Subscription extends Model
{
protected $fillable = [
'email',
'is_active',
'unsubscribe_token',
];
protected $casts = [
'is_active' => 'boolean',
];
/**
* Generate a unique unsubscribe token.
*/
protected static function boot()
{
parent::boot();
static::creating(function ($subscription) {
if (empty($subscription->unsubscribe_token)) {
$subscription->unsubscribe_token = Str::random(64);
}
});
}
}

48
app/Models/User.php Normal file
View file

@ -0,0 +1,48 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Vite::prefetch(concurrency: 3);
}
}

18
artisan Executable file
View file

@ -0,0 +1,18 @@
#!/usr/bin/env php
<?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
exit($status);

View file

@ -1,28 +0,0 @@
# Database
DATABASE_URL=postgresql://docket_user:docket_pass@localhost:5432/docket_db
REDIS_URL=redis://localhost:6379
# Email Configuration (Gmail Smuggler)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=2525
SMTP_SECURE=false
SMTP_USER=system@deafgain.org
SMTP_PASS=ojvlysraxwjriwzy
FROM_EMAIL=system@deafgain.org
RECIPIENT_EMAIL=eliza@deafgain.org
GOOGLE_EMAIL=system@deafgain.org
GOOGLE_APP_PASSWORD=ojvlysraxwjriwzy
# Authentication
JWT_SECRET=your-secret-key-change-in-production
JWT_EXPIRES_IN=24h
# File Upload
UPLOAD_DIR=./uploads
MAX_FILE_SIZE=10485760
# Environment
NODE_ENV=development
PORT=3001
LOG_LEVEL=info
FRONTEND_URL=https://mad-lawsuit.org

View file

@ -1,50 +0,0 @@
# Use Node.js 22 Alpine image to match package.json engines requirement
FROM node:22-alpine
WORKDIR /app
# Install system dependencies
RUN apk add --no-cache openssl curl python3 make g++
# Install pnpm
RUN npm install -g pnpm
# Copy package files
COPY package.json pnpm-lock.yaml* ./
# Install all dependencies
RUN pnpm install --frozen-lockfile
# Copy Prisma schema
COPY prisma ./prisma/
# Generate Prisma client
RUN pnpm prisma generate
# Copy source code and build
COPY . .
RUN pnpm build
# Remove dev dependencies to reduce image size
ENV CI=true
RUN pnpm prune --prod
# Generate Prisma client after prune (prisma CLI now available in production deps)
RUN pnpm prisma generate
# Create uploads directory
RUN mkdir -p /app/uploads
# Set environment variables
ENV NODE_ENV=production
ENV PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1
# Expose port
EXPOSE 3001
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3001/api/health || exit 1
# Start the application
CMD ["pnpm", "start"]

130
backend/dist/index.js vendored
View file

@ -1,130 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.redis = exports.prisma = void 0;
const express_1 = __importDefault(require("express"));
const cors_1 = __importDefault(require("cors"));
const helmet_1 = __importDefault(require("helmet"));
const compression_1 = __importDefault(require("compression"));
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
const dotenv_1 = __importDefault(require("dotenv"));
const client_1 = require("@prisma/client");
const adapter_pg_1 = require("@prisma/adapter-pg");
const pg_1 = __importDefault(require("pg"));
const redis_1 = require("redis");
const logger_1 = require("./utils/logger");
const errorHandler_1 = require("./middleware/errorHandler");
const requestLogger_1 = require("./middleware/requestLogger");
const auth_1 = __importDefault(require("./routes/auth"));
const docket_1 = __importDefault(require("./routes/docket"));
const documents_1 = __importDefault(require("./routes/documents"));
const subscriptions_1 = __importDefault(require("./routes/subscriptions"));
const health_1 = __importDefault(require("./routes/health"));
const notifications_1 = __importDefault(require("./routes/notifications"));
dotenv_1.default.config();
const app = (0, express_1.default)();
const PORT = process.env.PORT || 3001;
app.set('trust proxy', true);
const connectionString = process.env.DATABASE_URL;
const pool = new pg_1.default.Pool({ connectionString });
const adapter = new adapter_pg_1.PrismaPg(pool);
exports.prisma = new client_1.PrismaClient({
adapter,
log: ['query', 'info', 'warn', 'error'],
});
exports.redis = (0, redis_1.createClient)({
url: process.env.REDIS_URL || 'redis://localhost:6379',
});
exports.redis.connect().catch((error) => {
logger_1.logger.error('Redis connection failed:', error);
});
exports.redis.on('error', (error) => {
logger_1.logger.error('Redis error:', error);
});
exports.redis.on('connect', () => {
logger_1.logger.info('Connected to Redis');
});
app.use((0, helmet_1.default)({
crossOriginResourcePolicy: { policy: "cross-origin" }
}));
app.use((0, cors_1.default)({
origin: process.env.NODE_ENV === 'production'
? [
process.env.FRONTEND_URL,
'https://mad-lawsuit.org',
'https://files.mad-lawsuit.org',
'http://frontend:806',
'http://localhost:806'
]
: [
'http://localhost:3000',
'http://localhost:806',
'http://frontend:806',
'http://frontend:3000',
'https://mad-lawsuit.org',
'https://files.mad-lawsuit.org'
],
credentials: true,
}));
const limiter = (0, express_rate_limit_1.default)({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests from this IP, please try again later.',
standardHeaders: true,
legacyHeaders: false,
skip: (req) => {
return false;
},
keyGenerator: (req) => {
return req.ip || 'unknown';
},
});
app.use('/api', limiter);
const authLimiter = (0, express_rate_limit_1.default)({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many authentication attempts, please try again later.',
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => {
return req.ip || 'unknown';
},
});
app.use('/api/auth', authLimiter);
app.use((0, compression_1.default)());
app.use(express_1.default.json({ limit: '10mb' }));
app.use(express_1.default.urlencoded({ extended: true, limit: '10mb' }));
app.use(requestLogger_1.requestLogger);
app.use('/api/health', health_1.default);
app.use('/api/auth', auth_1.default);
app.use('/api/docket-entries', docket_1.default);
app.use('/api/documents', documents_1.default);
app.use('/api/subscriptions', subscriptions_1.default);
app.use('/api/notifications', notifications_1.default);
app.use('*', (req, res) => {
res.status(404).json({
success: false,
message: 'Route not found',
});
});
app.use(errorHandler_1.errorHandler);
process.on('SIGTERM', async () => {
logger_1.logger.info('SIGTERM received, shutting down gracefully');
await exports.prisma.$disconnect();
await exports.redis.quit();
process.exit(0);
});
process.on('SIGINT', async () => {
logger_1.logger.info('SIGINT received, shutting down gracefully');
await exports.prisma.$disconnect();
await exports.redis.quit();
process.exit(0);
});
app.listen(PORT, () => {
logger_1.logger.info(`Server running on port ${PORT}`);
logger_1.logger.info(`Environment: ${process.env.NODE_ENV || 'development'}`);
});
exports.default = app;
//# sourceMappingURL=index.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA8B;AAC9B,gDAAwB;AACxB,oDAA4B;AAC5B,8DAAsC;AACtC,4EAA2C;AAC3C,oDAA4B;AAC5B,2CAA8C;AAC9C,mDAA8C;AAC9C,4CAAoB;AACpB,iCAAqC;AACrC,2CAAwC;AACxC,4DAAyD;AACzD,8DAA2D;AAG3D,yDAAuC;AACvC,6DAA2C;AAC3C,mEAAgD;AAChD,2EAAwD;AACxD,6DAA2C;AAC3C,2EAAwD;AAGxD,gBAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;AACtB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AAGtC,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;AAG7B,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;AAClD,MAAM,IAAI,GAAG,IAAI,YAAE,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;AAC/C,MAAM,OAAO,GAAG,IAAI,qBAAQ,CAAC,IAAI,CAAC,CAAC;AAEtB,QAAA,MAAM,GAAG,IAAI,qBAAY,CAAC;IACrC,OAAO;IACP,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC;CACxC,CAAC,CAAC;AAGU,QAAA,KAAK,GAAG,IAAA,oBAAY,EAAC;IAChC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,wBAAwB;CACvD,CAAC,CAAC;AAGH,aAAK,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IAC9B,eAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEH,aAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;IAC1B,eAAM,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC;AAEH,aAAK,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACvB,eAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAGH,GAAG,CAAC,GAAG,CAAC,IAAA,gBAAM,EAAC;IACb,yBAAyB,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE;CACtD,CAAC,CAAC,CAAC;AAGJ,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,EAAC;IACX,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;QAC3C,CAAC,CAAC;YACE,OAAO,CAAC,GAAG,CAAC,YAAY;YACxB,yBAAyB;YACzB,+BAA+B;YAC/B,qBAAqB;YACrB,sBAAsB;SACvB;QACH,CAAC,CAAC;YACE,uBAAuB;YACvB,sBAAsB;YACtB,qBAAqB;YACrB,sBAAsB;YACtB,yBAAyB;YACzB,+BAA+B;SAChC;IACL,WAAW,EAAE,IAAI;CAClB,CAAC,CAAC,CAAC;AAGJ,MAAM,OAAO,GAAG,IAAA,4BAAS,EAAC;IACxB,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACxB,GAAG,EAAE,GAAG;IACR,OAAO,EAAE,yDAAyD;IAClE,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,KAAK;IAEpB,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;QAEZ,OAAO,KAAK,CAAC;IACf,CAAC;IACD,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE;QAEpB,OAAO,GAAG,CAAC,EAAE,IAAI,SAAS,CAAC;IAC7B,CAAC;CACF,CAAC,CAAC;AAEH,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAGzB,MAAM,WAAW,GAAG,IAAA,4BAAS,EAAC;IAC5B,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACxB,GAAG,EAAE,CAAC;IACN,OAAO,EAAE,2DAA2D;IACpE,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,KAAK;IACpB,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE;QACpB,OAAO,GAAG,CAAC,EAAE,IAAI,SAAS,CAAC;IAC7B,CAAC;CACF,CAAC,CAAC;AAEH,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAGlC,GAAG,CAAC,GAAG,CAAC,IAAA,qBAAW,GAAE,CAAC,CAAC;AACvB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AACzC,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AAG/D,GAAG,CAAC,GAAG,CAAC,6BAAa,CAAC,CAAC;AAGvB,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,gBAAY,CAAC,CAAC;AACrC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,cAAU,CAAC,CAAC;AACjC,GAAG,CAAC,GAAG,CAAC,qBAAqB,EAAE,gBAAY,CAAC,CAAC;AAC7C,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,mBAAc,CAAC,CAAC;AAC1C,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,uBAAkB,CAAC,CAAC;AAClD,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,uBAAkB,CAAC,CAAC;AAGlD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACxB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QACnB,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,iBAAiB;KAC3B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAGH,GAAG,CAAC,GAAG,CAAC,2BAAY,CAAC,CAAC;AAGtB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;IAC/B,eAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAG1D,MAAM,cAAM,CAAC,WAAW,EAAE,CAAC;IAG3B,MAAM,aAAK,CAAC,IAAI,EAAE,CAAC;IAEnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;IAC9B,eAAM,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IAGzD,MAAM,cAAM,CAAC,WAAW,EAAE,CAAC;IAG3B,MAAM,aAAK,CAAC,IAAI,EAAE,CAAC;IAEnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAGH,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACpB,eAAM,CAAC,IAAI,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;IAC9C,eAAM,CAAC,IAAI,CAAC,gBAAgB,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,aAAa,EAAE,CAAC,CAAC;AACvE,CAAC,CAAC,CAAC;AAEH,kBAAe,GAAG,CAAC"}

View file

@ -1,50 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.authenticateToken = void 0;
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const index_1 = require("../index");
const authenticateToken = async (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({
success: false,
message: 'Access token required',
});
}
try {
const jwtSecret = process.env['JWT_SECRET'];
if (!jwtSecret) {
return res.status(500).json({
success: false,
message: 'Server configuration error',
});
}
const decoded = jsonwebtoken_1.default.verify(token, jwtSecret);
const user = await index_1.prisma.adminUser.findUnique({
where: { id: decoded.userId },
});
if (!user) {
return res.status(401).json({
success: false,
message: 'Invalid token - user not found',
});
}
req.user = {
userId: decoded.userId,
username: decoded.username,
};
next();
}
catch (error) {
return res.status(403).json({
success: false,
message: 'Invalid or expired token',
});
}
};
exports.authenticateToken = authenticateToken;
//# sourceMappingURL=auth.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":";;;;;;AACA,gEAA+B;AAC/B,oCAAkC;AAS3B,MAAM,iBAAiB,GAAG,KAAK,EACpC,GAAyB,EACzB,GAAa,EACb,IAAkB,EAClB,EAAE;IACF,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,UAAU,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAErD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,uBAAuB;SACjC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC1B,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,4BAA4B;aACtC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,CAAQ,CAAC;QAGpD,MAAM,IAAI,GAAG,MAAM,cAAM,CAAC,SAAS,CAAC,UAAU,CAAC;YAC7C,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE;SAC9B,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC1B,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,gCAAgC;aAC1C,CAAC,CAAC;QACL,CAAC;QAED,GAAG,CAAC,IAAI,GAAG;YACT,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAAC;QAEF,IAAI,EAAE,CAAC;IACT,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,0BAA0B;SACpC,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC;AAlDW,QAAA,iBAAiB,qBAkD5B"}

View file

@ -1,37 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.asyncHandler = exports.createError = exports.errorHandler = void 0;
const logger_1 = require("../utils/logger");
const errorHandler = (error, req, res, next) => {
const statusCode = error.statusCode || 500;
const message = error.message || 'Internal Server Error';
logger_1.logger.error('Error occurred:', {
error: error.message,
stack: error.stack,
url: req.url,
method: req.method,
ip: req.ip,
userAgent: req.get('User-Agent'),
});
const isDevelopment = process.env.NODE_ENV === 'development';
res.status(statusCode).json({
success: false,
message: isDevelopment ? message : 'Something went wrong',
...(isDevelopment && { stack: error.stack }),
});
};
exports.errorHandler = errorHandler;
const createError = (message, statusCode = 500) => {
const error = new Error(message);
error.statusCode = statusCode;
error.isOperational = true;
return error;
};
exports.createError = createError;
const asyncHandler = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
exports.asyncHandler = asyncHandler;
//# sourceMappingURL=errorHandler.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/middleware/errorHandler.ts"],"names":[],"mappings":";;;AACA,4CAAyC;AAOlC,MAAM,YAAY,GAAG,CAC1B,KAAe,EACf,GAAY,EACZ,GAAa,EACb,IAAkB,EACZ,EAAE;IACR,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,GAAG,CAAC;IAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,uBAAuB,CAAC;IAGzD,eAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE;QAC9B,KAAK,EAAE,KAAK,CAAC,OAAO;QACpB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;KACjC,CAAC,CAAC;IAGH,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,CAAC;IAE7D,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;QAC1B,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB;QACzD,GAAG,CAAC,aAAa,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;KAC7C,CAAC,CAAC;AACL,CAAC,CAAC;AA3BW,QAAA,YAAY,gBA2BvB;AAEK,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,aAAqB,GAAG,EAAY,EAAE;IACjF,MAAM,KAAK,GAAa,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3C,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC;IAC9B,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC;IAC3B,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AALW,QAAA,WAAW,eAKtB;AAEK,MAAM,YAAY,GAAG,CAAC,EAAY,EAAE,EAAE;IAC3C,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC,CAAC;AAJW,QAAA,YAAY,gBAIvB"}

View file

@ -1,28 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.requestLogger = void 0;
const logger_1 = require("../utils/logger");
const requestLogger = (req, res, next) => {
const start = Date.now();
logger_1.logger.info('Incoming request', {
method: req.method,
url: req.url,
ip: req.ip,
userAgent: req.get('User-Agent'),
});
const originalEnd = res.end;
res.end = function (chunk, encoding) {
const duration = Date.now() - start;
logger_1.logger.info('Request completed', {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`,
ip: req.ip,
});
return originalEnd.call(this, chunk, encoding);
};
next();
};
exports.requestLogger = requestLogger;
//# sourceMappingURL=requestLogger.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"requestLogger.js","sourceRoot":"","sources":["../../src/middleware/requestLogger.ts"],"names":[],"mappings":";;;AACA,4CAAyC;AAElC,MAAM,aAAa,GAAG,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;IACrF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAGzB,eAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE;QAC9B,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;KACjC,CAAC,CAAC;IAGH,MAAM,WAAW,GAAG,GAAG,CAAC,GAAG,CAAC;IAC5B,GAAG,CAAC,GAAG,GAAG,UAAS,KAAW,EAAE,QAAc;QAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QAEpC,eAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE;YAC/B,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,QAAQ,EAAE,GAAG,QAAQ,IAAI;YACzB,EAAE,EAAE,GAAG,CAAC,EAAE;SACX,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IACjD,CAAQ,CAAC;IAET,IAAI,EAAE,CAAC;AACT,CAAC,CAAC;AA5BW,QAAA,aAAa,iBA4BxB"}

View file

@ -1,85 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const bcryptjs_1 = __importDefault(require("bcryptjs"));
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const index_1 = require("../index");
const errorHandler_1 = require("../middleware/errorHandler");
const auth_1 = require("../middleware/auth");
const joi_1 = __importDefault(require("joi"));
const router = (0, express_1.Router)();
const loginSchema = joi_1.default.object({
username: joi_1.default.string().required(),
password: joi_1.default.string().required(),
});
router.post('/login', (0, errorHandler_1.asyncHandler)(async (req, res) => {
const { error, value } = loginSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
const { username, password } = value;
const admin = await index_1.prisma.adminUser.findUnique({
where: { username },
});
if (!admin) {
return res.status(401).json({
success: false,
message: 'Invalid credentials',
});
}
const isValidPassword = await bcryptjs_1.default.compare(password, admin.passwordHash);
if (!isValidPassword) {
return res.status(401).json({
success: false,
message: 'Invalid credentials',
});
}
await index_1.prisma.adminUser.update({
where: { id: admin.id },
data: { lastLogin: new Date() },
});
const jwtSecret = process.env['JWT_SECRET'];
const jwtExpiresIn = process.env['JWT_EXPIRES_IN'] || '24h';
if (!jwtSecret) {
return res.status(500).json({
success: false,
message: 'Server configuration error',
});
}
const token = jsonwebtoken_1.default.sign({
userId: admin.id,
username: admin.username
}, jwtSecret, {
expiresIn: jwtExpiresIn
});
res.json({
success: true,
message: 'Login successful',
token,
user: {
id: admin.id,
username: admin.username,
},
});
}));
router.get('/verify', auth_1.authenticateToken, (0, errorHandler_1.asyncHandler)(async (req, res) => {
res.json({
success: true,
message: 'Token is valid',
user: req.user,
});
}));
router.post('/logout', auth_1.authenticateToken, (0, errorHandler_1.asyncHandler)(async (req, res) => {
res.json({
success: true,
message: 'Logged out successfully',
});
}));
exports.default = router;
//# sourceMappingURL=auth.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":";;;;;AAAA,qCAAoD;AACpD,wDAA8B;AAC9B,gEAA+B;AAC/B,oCAAkC;AAClC,6DAA0D;AAC1D,6CAA6E;AAC7E,8CAAsB;AAEtB,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AAGxB,MAAM,WAAW,GAAG,aAAG,CAAC,MAAM,CAAC;IAC7B,QAAQ,EAAE,aAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,QAAQ,EAAE,aAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAGH,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAA,2BAAY,EAAC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IACvE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAExD,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO;SAClC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAGrC,MAAM,KAAK,GAAG,MAAM,cAAM,CAAC,SAAS,CAAC,UAAU,CAAC;QAC9C,KAAK,EAAE,EAAE,QAAQ,EAAE;KACpB,CAAC,CAAC;IAEH,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,qBAAqB;SAC/B,CAAC,CAAC;IACL,CAAC;IAGD,MAAM,eAAe,GAAG,MAAM,kBAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAE3E,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,qBAAqB;SAC/B,CAAC,CAAC;IACL,CAAC;IAGD,MAAM,cAAM,CAAC,SAAS,CAAC,MAAM,CAAC;QAC5B,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE;QACvB,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE;KAChC,CAAC,CAAC;IAGH,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,KAAK,CAAC;IAE5D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,4BAA4B;SACtC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,KAAK,GAAG,sBAAG,CAAC,IAAI,CACpB;QACE,MAAM,EAAE,KAAK,CAAC,EAAE;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,EACD,SAAS,EACT;QACE,SAAS,EAAE,YAAY;KACL,CACrB,CAAC;IAEF,GAAG,CAAC,IAAI,CAAC;QACP,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,kBAAkB;QAC3B,KAAK;QACL,IAAI,EAAE;YACJ,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB;KACF,CAAC,CAAC;AACL,CAAC,CAAC,CAAC,CAAC;AAGJ,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,wBAAiB,EAAE,IAAA,2BAAY,EAAC,KAAK,EAAE,GAAyB,EAAE,GAAa,EAAE,EAAE;IACvG,GAAG,CAAC,IAAI,CAAC;QACP,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,gBAAgB;QACzB,IAAI,EAAE,GAAG,CAAC,IAAI;KACf,CAAC,CAAC;AACL,CAAC,CAAC,CAAC,CAAC;AAGJ,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAiB,EAAE,IAAA,2BAAY,EAAC,KAAK,EAAE,GAAyB,EAAE,GAAa,EAAE,EAAE;IACxG,GAAG,CAAC,IAAI,CAAC;QACP,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,yBAAyB;KACnC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC,CAAC;AAEJ,kBAAe,MAAM,CAAC"}

View file

@ -1,190 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const index_1 = require("../index");
const errorHandler_1 = require("../middleware/errorHandler");
const auth_1 = require("../middleware/auth");
const joi_1 = __importDefault(require("joi"));
const router = (0, express_1.Router)();
function normalizeDate(dateInput) {
if (typeof dateInput === 'string') {
const [year, month, day] = dateInput.split('-');
return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
}
if (dateInput instanceof Date) {
const year = dateInput.getFullYear();
const month = dateInput.getMonth();
const day = dateInput.getDate();
return new Date(year, month, day);
}
return dateInput;
}
const createDocketEntrySchema = joi_1.default.object({
date: joi_1.default.date().required(),
title: joi_1.default.string().required().max(500),
summary: joi_1.default.string().required(),
notes: joi_1.default.string().optional().allow(''),
});
const updateDocketEntrySchema = joi_1.default.object({
date: joi_1.default.date().optional(),
title: joi_1.default.string().optional().max(500),
summary: joi_1.default.string().optional(),
notes: joi_1.default.string().optional().allow(''),
});
router.get('/', (0, errorHandler_1.asyncHandler)(async (req, res) => {
const entries = await index_1.prisma.docketEntry.findMany({
include: {
documents: {
orderBy: {
displayOrder: 'asc',
},
},
},
orderBy: {
date: 'asc',
},
});
res.json({
success: true,
entries,
});
}));
router.get('/:id', auth_1.authenticateToken, (0, errorHandler_1.asyncHandler)(async (req, res) => {
const entryId = parseInt(req.params.id);
if (isNaN(entryId)) {
return res.status(400).json({
success: false,
message: 'Invalid entry ID',
});
}
const entry = await index_1.prisma.docketEntry.findUnique({
where: { id: entryId },
include: {
documents: {
orderBy: {
displayOrder: 'asc',
},
},
},
});
if (!entry) {
return res.status(404).json({
success: false,
message: 'Docket entry not found',
});
}
res.json({
success: true,
entry,
});
}));
router.post('/', auth_1.authenticateToken, (0, errorHandler_1.asyncHandler)(async (req, res) => {
const { error, value } = createDocketEntrySchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
const normalizedData = { ...value };
if (value.date) {
normalizedData.date = normalizeDate(value.date);
}
const entry = await index_1.prisma.docketEntry.create({
data: normalizedData,
include: {
documents: {
orderBy: {
displayOrder: 'asc',
},
},
},
});
res.status(201).json({
success: true,
message: 'Docket entry created successfully',
entry,
});
}));
router.put('/:id', auth_1.authenticateToken, (0, errorHandler_1.asyncHandler)(async (req, res) => {
const entryId = parseInt(req.params.id);
if (isNaN(entryId)) {
return res.status(400).json({
success: false,
message: 'Invalid entry ID',
});
}
const { error, value } = updateDocketEntrySchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
const normalizedData = { ...value };
if (value.date) {
normalizedData.date = normalizeDate(value.date);
}
try {
const entry = await index_1.prisma.docketEntry.update({
where: { id: entryId },
data: normalizedData,
include: {
documents: {
orderBy: {
displayOrder: 'asc',
},
},
},
});
res.json({
success: true,
message: 'Docket entry updated successfully',
entry,
});
}
catch (error) {
if (error.code === 'P2025') {
return res.status(404).json({
success: false,
message: 'Docket entry not found',
});
}
throw error;
}
}));
router.delete('/:id', auth_1.authenticateToken, (0, errorHandler_1.asyncHandler)(async (req, res) => {
const entryId = parseInt(req.params.id);
if (isNaN(entryId)) {
return res.status(400).json({
success: false,
message: 'Invalid entry ID',
});
}
try {
await index_1.prisma.document.deleteMany({
where: { docketEntryId: entryId },
});
await index_1.prisma.docketEntry.delete({
where: { id: entryId },
});
res.json({
success: true,
message: 'Docket entry deleted successfully',
});
}
catch (error) {
if (error.code === 'P2025') {
return res.status(404).json({
success: false,
message: 'Docket entry not found',
});
}
throw error;
}
}));
exports.default = router;
//# sourceMappingURL=docket.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,231 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const index_1 = require("../index");
const errorHandler_1 = require("../middleware/errorHandler");
const auth_1 = require("../middleware/auth");
const multer_1 = __importDefault(require("multer"));
const uuid_1 = require("uuid");
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const joi_1 = __importDefault(require("joi"));
const router = (0, express_1.Router)();
const storage = multer_1.default.diskStorage({
destination: (req, file, cb) => {
const uploadDir = process.env['UPLOAD_DIR'] || './uploads';
if (!fs_1.default.existsSync(uploadDir)) {
fs_1.default.mkdirSync(uploadDir, { recursive: true });
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const uuid = (0, uuid_1.v4)();
const extension = path_1.default.extname(file.originalname);
cb(null, `${uuid}${extension}`);
},
});
const upload = (0, multer_1.default)({
storage,
limits: {
fileSize: parseInt(process.env['MAX_FILE_SIZE'] || '262144000'),
},
fileFilter: (req, file, cb) => {
const allowedMimeTypes = [
'application/pdf',
'application/x-pdf',
'application/acrobat',
'applications/vnd.pdf',
'text/pdf',
'text/x-pdf'
];
const isValidPDF = allowedMimeTypes.includes(file.mimetype) ||
file.originalname.toLowerCase().endsWith('.pdf');
console.log('Upload attempt:', {
mimetype: file.mimetype,
filename: file.originalname,
isValid: isValidPDF
});
if (isValidPDF) {
cb(null, true);
}
else {
cb(new Error(`Only PDF files are allowed. Received: ${file.mimetype} for file: ${file.originalname}`));
}
},
});
const createDocumentSchema = joi_1.default.object({
docketEntryId: joi_1.default.number().integer().required(),
title: joi_1.default.string().required().max(500),
summary: joi_1.default.string().optional().allow(''),
notes: joi_1.default.string().optional().allow(''),
displayOrder: joi_1.default.number().integer().min(0).optional(),
});
const updateDocumentSchema = joi_1.default.object({
title: joi_1.default.string().optional().max(500),
summary: joi_1.default.string().optional().allow(''),
notes: joi_1.default.string().optional().allow(''),
displayOrder: joi_1.default.number().integer().min(0).optional(),
});
router.get('/:id/download', (0, errorHandler_1.asyncHandler)(async (req, res) => {
const documentId = parseInt(req.params['id'] || '');
if (isNaN(documentId)) {
return res.status(400).json({
success: false,
message: 'Invalid document ID',
});
}
const document = await index_1.prisma.document.findUnique({
where: { id: documentId },
});
if (!document) {
return res.status(404).json({
success: false,
message: 'Document not found',
});
}
const filePath = document.filePath;
if (!fs_1.default.existsSync(filePath)) {
return res.status(404).json({
success: false,
message: 'File not found on server',
});
}
res.setHeader('Content-Type', document.mimeType);
res.setHeader('Content-Disposition', `inline; filename="${document.originalFilename}"`);
const fileStream = fs_1.default.createReadStream(filePath);
fileStream.pipe(res);
}));
router.post('/upload', auth_1.authenticateToken, upload.single('file'), (0, errorHandler_1.asyncHandler)(async (req, res) => {
if (!req.file) {
return res.status(400).json({
success: false,
message: 'No file uploaded',
});
}
const { error, value } = createDocumentSchema.validate(req.body);
if (error) {
fs_1.default.unlinkSync(req.file.path);
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
const { docketEntryId, title, summary, notes, displayOrder } = value;
const docketEntry = await index_1.prisma.docketEntry.findUnique({
where: { id: docketEntryId },
});
if (!docketEntry) {
fs_1.default.unlinkSync(req.file.path);
return res.status(404).json({
success: false,
message: 'Docket entry not found',
});
}
let finalDisplayOrder = displayOrder;
if (finalDisplayOrder === undefined) {
const lastDocument = await index_1.prisma.document.findFirst({
where: { docketEntryId },
orderBy: { displayOrder: 'desc' },
});
finalDisplayOrder = lastDocument ? lastDocument.displayOrder + 1 : 0;
}
const document = await index_1.prisma.document.create({
data: {
docketEntryId,
title,
originalFilename: req.file.originalname,
storedFilename: req.file.filename,
filePath: req.file.path,
fileSize: req.file.size,
mimeType: req.file.mimetype,
summary: summary || '',
notes: notes || '',
displayOrder: finalDisplayOrder,
},
});
res.status(201).json({
success: true,
message: 'Document uploaded successfully',
document,
});
}));
router.put('/:id', auth_1.authenticateToken, (0, errorHandler_1.asyncHandler)(async (req, res) => {
const documentId = parseInt(req.params['id'] || '');
if (isNaN(documentId)) {
return res.status(400).json({
success: false,
message: 'Invalid document ID',
});
}
const { error, value } = updateDocumentSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
try {
const document = await index_1.prisma.document.update({
where: { id: documentId },
data: value,
});
res.json({
success: true,
message: 'Document updated successfully',
document,
});
}
catch (error) {
if (error.code === 'P2025') {
return res.status(404).json({
success: false,
message: 'Document not found',
});
}
throw error;
}
}));
router.delete('/:id', auth_1.authenticateToken, (0, errorHandler_1.asyncHandler)(async (req, res) => {
const documentId = parseInt(req.params['id'] || '');
if (isNaN(documentId)) {
return res.status(400).json({
success: false,
message: 'Invalid document ID',
});
}
try {
const document = await index_1.prisma.document.findUnique({
where: { id: documentId },
});
if (!document) {
return res.status(404).json({
success: false,
message: 'Document not found',
});
}
if (fs_1.default.existsSync(document.filePath)) {
fs_1.default.unlinkSync(document.filePath);
}
await index_1.prisma.document.delete({
where: { id: documentId },
});
res.json({
success: true,
message: 'Document deleted successfully',
});
}
catch (error) {
if (error.code === 'P2025') {
return res.status(404).json({
success: false,
message: 'Document not found',
});
}
throw error;
}
}));
exports.default = router;
//# sourceMappingURL=documents.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,34 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const index_1 = require("../index");
const index_2 = require("../index");
const router = (0, express_1.Router)();
router.get('/', async (req, res) => {
try {
await index_1.prisma.$queryRaw `SELECT 1`;
await index_2.redis.ping();
res.json({
success: true,
message: 'API is healthy',
timestamp: new Date().toISOString(),
services: {
database: 'connected',
redis: 'connected',
},
});
}
catch (error) {
res.status(503).json({
success: false,
message: 'Service unavailable',
timestamp: new Date().toISOString(),
services: {
database: 'disconnected',
redis: 'disconnected',
},
});
}
});
exports.default = router;
//# sourceMappingURL=health.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":";;AAAA,qCAAoD;AACpD,oCAAkC;AAClC,oCAAiC;AAEjC,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AAExB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IACpD,IAAI,CAAC;QAEH,MAAM,cAAM,CAAC,SAAS,CAAA,UAAU,CAAC;QAGjC,MAAM,aAAK,CAAC,IAAI,EAAE,CAAC;QAEnB,GAAG,CAAC,IAAI,CAAC;YACP,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,gBAAgB;YACzB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,QAAQ,EAAE;gBACR,QAAQ,EAAE,WAAW;gBACrB,KAAK,EAAE,WAAW;aACnB;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,qBAAqB;YAC9B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,QAAQ,EAAE;gBACR,QAAQ,EAAE,cAAc;gBACxB,KAAK,EAAE,cAAc;aACtB;SACF,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,kBAAe,MAAM,CAAC"}

View file

@ -1,104 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const index_1 = require("../index");
const errorHandler_1 = require("../middleware/errorHandler");
const auth_1 = require("../middleware/auth");
const joi_1 = __importDefault(require("joi"));
const uuid_1 = require("uuid");
const router = (0, express_1.Router)();
const subscribeSchema = joi_1.default.object({
email: joi_1.default.string().email().required(),
});
router.post('/subscribe', (0, errorHandler_1.asyncHandler)(async (req, res) => {
const { error, value } = subscribeSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
const { email } = value;
const existing = await index_1.prisma.subscription.findUnique({
where: { email },
});
if (existing) {
if (existing.isActive) {
return res.status(400).json({
success: false,
message: 'Email is already subscribed',
});
}
else {
await index_1.prisma.subscription.update({
where: { email },
data: { isActive: true },
});
}
}
else {
await index_1.prisma.subscription.create({
data: {
email,
unsubscribeToken: (0, uuid_1.v4)(),
},
});
}
res.json({
success: true,
message: 'Successfully subscribed to notifications',
});
}));
router.get('/', auth_1.authenticateToken, (0, errorHandler_1.asyncHandler)(async (req, res) => {
const subscribers = await index_1.prisma.subscription.findMany({
orderBy: { createdAt: 'desc' },
select: {
id: true,
email: true,
isActive: true,
createdAt: true,
},
});
const stats = {
total: subscribers.length,
active: subscribers.filter(sub => sub.isActive).length,
inactive: subscribers.filter(sub => !sub.isActive).length,
};
res.json({
success: true,
subscribers,
stats,
});
}));
router.put('/:id/toggle', auth_1.authenticateToken, (0, errorHandler_1.asyncHandler)(async (req, res) => {
const subscriberId = parseInt(req.params.id);
if (isNaN(subscriberId)) {
return res.status(400).json({
success: false,
message: 'Invalid subscriber ID',
});
}
const subscriber = await index_1.prisma.subscription.findUnique({
where: { id: subscriberId },
});
if (!subscriber) {
return res.status(404).json({
success: false,
message: 'Subscriber not found',
});
}
const updated = await index_1.prisma.subscription.update({
where: { id: subscriberId },
data: { isActive: !subscriber.isActive },
});
res.json({
success: true,
message: `Subscriber ${updated.isActive ? 'activated' : 'deactivated'} successfully`,
subscriber: updated,
});
}));
exports.default = router;
//# sourceMappingURL=subscriptions.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"subscriptions.js","sourceRoot":"","sources":["../../src/routes/subscriptions.ts"],"names":[],"mappings":";;;;;AAAA,qCAAoD;AACpD,oCAAkC;AAClC,6DAA0D;AAC1D,6CAA6E;AAC7E,8CAAsB;AACtB,+BAAoC;AAEpC,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AAGxB,MAAM,eAAe,GAAG,aAAG,CAAC,MAAM,CAAC;IACjC,KAAK,EAAE,aAAG,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC;AAGH,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,IAAA,2BAAY,EAAC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IAC3E,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,eAAe,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE5D,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO;SAClC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAGxB,MAAM,QAAQ,GAAG,MAAM,cAAM,CAAC,YAAY,CAAC,UAAU,CAAC;QACpD,KAAK,EAAE,EAAE,KAAK,EAAE;KACjB,CAAC,CAAC;IAEH,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACtB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC1B,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,6BAA6B;aACvC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YAEN,MAAM,cAAM,CAAC,YAAY,CAAC,MAAM,CAAC;gBAC/B,KAAK,EAAE,EAAE,KAAK,EAAE;gBAChB,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;aACzB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;SAAM,CAAC;QAEN,MAAM,cAAM,CAAC,YAAY,CAAC,MAAM,CAAC;YAC/B,IAAI,EAAE;gBACJ,KAAK;gBACL,gBAAgB,EAAE,IAAA,SAAM,GAAE;aAC3B;SACF,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,IAAI,CAAC;QACP,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,0CAA0C;KACpD,CAAC,CAAC;AACL,CAAC,CAAC,CAAC,CAAC;AAGJ,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,wBAAiB,EAAE,IAAA,2BAAY,EAAC,KAAK,EAAE,GAAyB,EAAE,GAAa,EAAE,EAAE;IACjG,MAAM,WAAW,GAAG,MAAM,cAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;QACrD,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;QAC9B,MAAM,EAAE;YACN,EAAE,EAAE,IAAI;YACR,KAAK,EAAE,IAAI;YACX,QAAQ,EAAE,IAAI;YACd,SAAS,EAAE,IAAI;SAChB;KACF,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG;QACZ,KAAK,EAAE,WAAW,CAAC,MAAM;QACzB,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM;QACtD,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM;KAC1D,CAAC;IAEF,GAAG,CAAC,IAAI,CAAC;QACP,OAAO,EAAE,IAAI;QACb,WAAW;QACX,KAAK;KACN,CAAC,CAAC;AACL,CAAC,CAAC,CAAC,CAAC;AAGJ,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,wBAAiB,EAAE,IAAA,2BAAY,EAAC,KAAK,EAAE,GAAyB,EAAE,GAAa,EAAE,EAAE;IAC3G,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAE7C,IAAI,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;QACxB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,uBAAuB;SACjC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,cAAM,CAAC,YAAY,CAAC,UAAU,CAAC;QACtD,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE;KAC5B,CAAC,CAAC;IAEH,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,sBAAsB;SAChC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,cAAM,CAAC,YAAY,CAAC,MAAM,CAAC;QAC/C,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE;QAC3B,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE;KACzC,CAAC,CAAC;IAEH,GAAG,CAAC,IAAI,CAAC;QACP,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,cAAc,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,eAAe;QACpF,UAAU,EAAE,OAAO;KACpB,CAAC,CAAC;AACL,CAAC,CAAC,CAAC,CAAC;AAEJ,kBAAe,MAAM,CAAC"}

View file

@ -1,240 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.emailService = exports.EmailService = void 0;
const logger_1 = require("../utils/logger");
const nodemailer_1 = __importDefault(require("nodemailer"));
class EmailService {
static instance;
transporter;
constructor() {
this.transporter = nodemailer_1.default.createTransport({
host: process.env['SMTP_HOST'] || 'smtp.gmail.com',
port: parseInt(process.env['SMTP_PORT'] || '587'),
secure: process.env['SMTP_SECURE'] === 'true',
auth: {
user: process.env['GOOGLE_EMAIL'],
pass: process.env['GOOGLE_APP_PASSWORD'],
},
});
this.verifyConnection();
}
static getInstance() {
if (!EmailService.instance) {
EmailService.instance = new EmailService();
}
return EmailService.instance;
}
async verifyConnection() {
try {
await this.transporter.verify();
logger_1.logger.info('SMTP connection verified successfully');
}
catch (error) {
logger_1.logger.error('SMTP connection failed:', error);
}
}
async sendEmail(options) {
try {
const mailOptions = {
from: {
name: 'Court Docket System',
address: process.env['GOOGLE_EMAIL'] || 'system@deafgain.org',
},
to: options.to,
subject: options.subject,
html: options.html,
};
if (options.bcc && options.bcc.length > 0) {
mailOptions.bcc = options.bcc;
}
const result = await this.transporter.sendMail(mailOptions);
logger_1.logger.info('Email sent successfully:', {
to: options.to,
bcc: options.bcc ? `${options.bcc.length} recipients` : 'none',
subject: options.subject,
messageId: result.messageId,
});
return true;
}
catch (error) {
logger_1.logger.error('Failed to send email:', error);
return false;
}
}
async notifySubscribersOfNewEntry(entryTitle, entryDate, entrySummary) {
try {
const { prisma } = await Promise.resolve().then(() => __importStar(require('../index')));
const subscribers = await prisma.subscription.findMany({
where: { isActive: true },
});
if (subscribers.length === 0) {
logger_1.logger.info('No active subscribers to notify');
return;
}
const subject = `New Court Filing: ${entryTitle}`;
const html = this.generateNotificationEmail(entryTitle, entryDate, entrySummary);
const subscriberEmails = subscribers.map((subscriber) => subscriber.email);
const success = await this.sendEmail({
to: process.env['GOOGLE_EMAIL'] || 'system@deafgain.org',
bcc: subscriberEmails,
subject,
html,
});
if (success) {
logger_1.logger.info(`Email notification sent successfully to ${subscriberEmails.length} subscribers via BCC`);
}
else {
logger_1.logger.error(`Failed to send BCC email notification to ${subscriberEmails.length} subscribers`);
}
}
catch (error) {
logger_1.logger.error('Failed to notify subscribers:', error);
}
}
generateNotificationEmail(title, date, summary) {
const formattedDate = new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>New Court Filing Notification</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
background-color: #1f2937;
color: white;
padding: 20px;
text-align: center;
border-radius: 8px 8px 0 0;
}
.content {
background-color: #f9fafb;
padding: 30px;
border-radius: 0 0 8px 8px;
border: 1px solid #e5e7eb;
}
.filing-info {
background-color: white;
padding: 20px;
border-radius: 6px;
margin: 20px 0;
border-left: 4px solid #3b82f6;
}
.date {
color: #6b7280;
font-size: 14px;
margin-bottom: 10px;
}
.title {
font-size: 18px;
font-weight: bold;
color: #1f2937;
margin-bottom: 15px;
}
.summary {
color: #4b5563;
line-height: 1.5;
}
.footer {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
font-size: 12px;
color: #6b7280;
text-align: center;
}
.button {
display: inline-block;
background-color: #3b82f6;
color: white;
padding: 12px 24px;
text-decoration: none;
border-radius: 6px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="header">
<h1>Eliza Kragh v. Montana Association of the Deaf</h1>
<p>New Court Filing Notification</p>
</div>
<div class="content">
<p>A new filing has been added to the court docket:</p>
<div class="filing-info">
<div class="date">${formattedDate}</div>
<div class="title">${title}</div>
<div class="summary">${summary}</div>
</div>
<p>You can view the complete docket and any associated documents by visiting the <a href="https://mad-lawsuit.org/" style="color: #3b82f6; text-decoration: none;">website</a>:</p>
<a href="https://mad-lawsuit.org/" class="button">
View Court Docket
</a>
<div class="footer">
<p>You are receiving this email because you subscribed to notifications for this case.</p>
<p>This is an automated notification from the court docket system.</p>
<p>To unsubscribe, please send an email to eliza.kragh@gmail.com</p>
</div>
</div>
</body>
</html>
`;
}
}
exports.EmailService = EmailService;
exports.emailService = EmailService.getInstance();
//# sourceMappingURL=emailService.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"emailService.js","sourceRoot":"","sources":["../../src/services/emailService.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4CAAyC;AACzC,4DAAoC;AASpC,MAAa,YAAY;IACf,MAAM,CAAC,QAAQ,CAAe;IAC9B,WAAW,CAAyB;IAE5C;QAEE,IAAI,CAAC,WAAW,GAAG,oBAAU,CAAC,eAAe,CAAC;YAC5C,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,gBAAgB;YAClD,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC;YACjD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,MAAM;YAC7C,IAAI,EAAE;gBACJ,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;gBACjC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;aACzC;SACF,CAAC,CAAC;QAGH,IAAI,CAAC,gBAAgB,EAAE,CAAC;IAC1B,CAAC;IAEM,MAAM,CAAC,WAAW;QACvB,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;YAC3B,YAAY,CAAC,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;QAC7C,CAAC;QACD,OAAO,YAAY,CAAC,QAAQ,CAAC;IAC/B,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;YAChC,eAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,eAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,OAAqB;QACnC,IAAI,CAAC;YACH,MAAM,WAAW,GAAQ;gBACvB,IAAI,EAAE;oBACJ,IAAI,EAAE,qBAAqB;oBAC3B,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,qBAAqB;iBAC9D;gBACD,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,IAAI,EAAE,OAAO,CAAC,IAAI;aACnB,CAAC;YAGF,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1C,WAAW,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;YAChC,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAE5D,eAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE;gBACtC,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,aAAa,CAAC,CAAC,CAAC,MAAM;gBAC9D,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAC,CAAC;YAEH,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,eAAM,CAAC,KAAK,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAAC;YAC7C,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,KAAK,CAAC,2BAA2B,CAAC,UAAkB,EAAE,SAAiB,EAAE,YAAoB;QAC3F,IAAI,CAAC;YAEH,MAAM,EAAE,MAAM,EAAE,GAAG,wDAAa,UAAU,GAAC,CAAC;YAC5C,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;gBACrD,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;aAC1B,CAAC,CAAC;YAEH,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC7B,eAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;gBAC/C,OAAO;YACT,CAAC;YAED,MAAM,OAAO,GAAG,qBAAqB,UAAU,EAAE,CAAC;YAClD,MAAM,IAAI,GAAG,IAAI,CAAC,yBAAyB,CAAC,UAAU,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;YACjF,MAAM,gBAAgB,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,UAAe,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAGhF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC;gBACnC,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,qBAAqB;gBACxD,GAAG,EAAE,gBAAgB;gBACrB,OAAO;gBACP,IAAI;aACL,CAAC,CAAC;YAEH,IAAI,OAAO,EAAE,CAAC;gBACZ,eAAM,CAAC,IAAI,CAAC,2CAA2C,gBAAgB,CAAC,MAAM,sBAAsB,CAAC,CAAC;YACxG,CAAC;iBAAM,CAAC;gBACN,eAAM,CAAC,KAAK,CAAC,4CAA4C,gBAAgB,CAAC,MAAM,cAAc,CAAC,CAAC;YAClG,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,eAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAEO,yBAAyB,CAAC,KAAa,EAAE,IAAY,EAAE,OAAe;QAC5E,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE;YAC/D,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,MAAM;YACb,GAAG,EAAE,SAAS;SACf,CAAC,CAAC;QAEH,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAgFqB,aAAa;iCACZ,KAAK;mCACH,OAAO;;;;;;;;;;;;;;;;;KAiBrC,CAAC;IACJ,CAAC;CACF;AApND,oCAoNC;AAEY,QAAA,YAAY,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC"}

View file

@ -1,33 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = void 0;
const winston_1 = __importDefault(require("winston"));
const logFormat = winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.errors({ stack: true }), winston_1.default.format.json());
exports.logger = winston_1.default.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: logFormat,
defaultMeta: { service: 'docket-api' },
transports: [
new winston_1.default.transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 5242880,
maxFiles: 5,
}),
new winston_1.default.transports.File({
filename: 'logs/combined.log',
maxsize: 5242880,
maxFiles: 5,
}),
],
});
if (process.env.NODE_ENV !== 'production') {
exports.logger.add(new winston_1.default.transports.Console({
format: winston_1.default.format.combine(winston_1.default.format.colorize(), winston_1.default.format.simple())
}));
}
exports.default = exports.logger;
//# sourceMappingURL=logger.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA8B;AAE9B,MAAM,SAAS,GAAG,iBAAO,CAAC,MAAM,CAAC,OAAO,CACtC,iBAAO,CAAC,MAAM,CAAC,SAAS,EAAE,EAC1B,iBAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EACtC,iBAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CACtB,CAAC;AAEW,QAAA,MAAM,GAAG,iBAAO,CAAC,YAAY,CAAC;IACzC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM;IACtC,MAAM,EAAE,SAAS;IACjB,WAAW,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE;IACtC,UAAU,EAAE;QACV,IAAI,iBAAO,CAAC,UAAU,CAAC,IAAI,CAAC;YAC1B,QAAQ,EAAE,gBAAgB;YAC1B,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,CAAC;SACZ,CAAC;QACF,IAAI,iBAAO,CAAC,UAAU,CAAC,IAAI,CAAC;YAC1B,QAAQ,EAAE,mBAAmB;YAC7B,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,CAAC;SACZ,CAAC;KACH;CACF,CAAC,CAAC;AAGH,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;IAC1C,cAAM,CAAC,GAAG,CAAC,IAAI,iBAAO,CAAC,UAAU,CAAC,OAAO,CAAC;QACxC,MAAM,EAAE,iBAAO,CAAC,MAAM,CAAC,OAAO,CAC5B,iBAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,EACzB,iBAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CACxB;KACF,CAAC,CAAC,CAAC;AACN,CAAC;AAED,kBAAe,cAAM,CAAC"}

View file

@ -1,809 +0,0 @@
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-24T21:13:09.977Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-24T21:13:09.980Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-24T21:13:09.984Z"}
{"ip":"::ffff:127.0.0.1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:13:57.178Z","url":"/","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0"}
{"duration":"2ms","ip":"::ffff:127.0.0.1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":404,"timestamp":"2025-06-24T21:13:57.180Z","url":"/"}
{"ip":"::ffff:127.0.0.1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:13:57.204Z","url":"/favicon.ico","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0"}
{"duration":"1ms","ip":"::ffff:127.0.0.1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":404,"timestamp":"2025-06-24T21:13:57.205Z","url":"/"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-24T21:14:07.942Z"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-24T21:14:07.944Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-24T21:15:56.009Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-24T21:15:56.010Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-24T21:15:56.012Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-24T21:20:19.862Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-24T21:20:19.863Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-24T21:20:19.866Z"}
{"ip":"::ffff:127.0.0.1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:21:02.794Z","url":"/","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::ffff:127.0.0.1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":404,"timestamp":"2025-06-24T21:21:02.796Z","url":"/"}
{"ip":"::ffff:127.0.0.1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:21:02.815Z","url":"/favicon.ico","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"1ms","ip":"::ffff:127.0.0.1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":404,"timestamp":"2025-06-24T21:21:02.816Z","url":"/"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-24T21:21:14.796Z"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-24T21:21:14.799Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-24T21:21:25.211Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-24T21:21:25.212Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-24T21:21:25.214Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:21:37.049Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"18ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T21:21:37.066Z","url":"/"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-24T21:24:18.294Z"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-24T21:24:18.295Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-24T21:28:23.634Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-24T21:28:23.636Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-24T21:28:23.637Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-24T21:30:45.172Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-24T21:30:45.174Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-24T21:30:45.175Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:31:04.950Z","url":"/api/docket","userAgent":"curl/8.7.1"}
{"duration":"0ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":404,"timestamp":"2025-06-24T21:31:04.950Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:31:07.099Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"19ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T21:31:07.119Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:31:14.621Z","url":"/api/docket-entries","userAgent":"curl/8.7.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T21:31:14.625Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T21:31:23.756Z","url":"/api/auth/login","userAgent":"curl/8.7.1"}
{"duration":"249ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T21:31:24.004Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T21:31:35.716Z","url":"/api/subscriptions/subscribe","userAgent":"curl/8.7.1"}
{"duration":"0ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":404,"timestamp":"2025-06-24T21:31:35.716Z","url":"/"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-24T21:31:54.377Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-24T21:31:54.799Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-24T21:31:54.801Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-24T21:31:54.802Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T21:32:04.251Z","url":"/api/subscriptions/subscribe","userAgent":"curl/8.7.1"}
{"duration":"21ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T21:32:04.272Z","url":"/subscribe"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:36:37.203Z","url":"/api/docket-entries","userAgent":"curl/8.7.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T21:36:37.209Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:36:44.997Z","url":"/api/subscriptions/subscribe","userAgent":"curl/8.7.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":404,"timestamp":"2025-06-24T21:36:44.997Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T21:36:52.430Z","url":"/api/subscriptions/subscribe","userAgent":"curl/8.7.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":400,"timestamp":"2025-06-24T21:36:52.434Z","url":"/subscribe"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:43:55.707Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"18ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-24T21:43:55.725Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T21:44:26.328Z","url":"/api/auth/login","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"266ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T21:44:26.593Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:44:26.803Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-24T21:44:26.806Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:44:26.809Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-24T21:44:26.812Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T21:45:50.519Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-24T21:45:50.523Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:41:20.733Z","url":"/api/auth/login","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"268ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:41:21.001Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:41:21.120Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-24T22:41:21.127Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:41:21.130Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:41:21.132Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:43:07.980Z","url":"/api/documents/upload","userAgent":"curl/8.7.1"}
{"error":"Only PDF files are allowed","ip":"::1","level":"error","message":"Error occurred:","method":"POST","service":"docket-api","stack":"Error: Only PDF files are allowed\n at fileFilter (/Users/chaulmark/Eliza-MAD-docket-website/backend/src/routes/documents.ts:39:10)\n at wrappedFileFilter (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/multer@1.4.5-lts.2/node_modules/multer/index.js:44:7)\n at Multipart.<anonymous> (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/multer@1.4.5-lts.2/node_modules/multer/lib/make-middleware.js:109:7)\n at Multipart.emit (node:events:507:28)\n at HeaderParser.cb (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:358:14)\n at HeaderParser.push (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:162:20)\n at SBMH.ssCb [as _cb] (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:394:37)\n at feed (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/streamsearch@1.1.0/node_modules/streamsearch/lib/sbmh.js:219:14)\n at SBMH.push (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/streamsearch@1.1.0/node_modules/streamsearch/lib/sbmh.js:104:16)\n at Multipart._write (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:567:19)","timestamp":"2025-06-24T22:43:07.987Z","url":"/api/documents/upload","userAgent":"curl/8.7.1"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":500,"timestamp":"2025-06-24T22:43:07.987Z","url":"/api/documents/upload"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-24T22:44:10.109Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-24T22:44:10.520Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-24T22:44:10.522Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-24T22:44:10.524Z"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-24T22:44:23.926Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-24T22:44:24.304Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-24T22:44:24.306Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-24T22:44:24.308Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:44:33.165Z","url":"/api/auth/login","userAgent":"curl/8.7.1"}
{"duration":"250ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:44:33.415Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:44:33.425Z","url":"/api/docket-entries","userAgent":"curl/8.7.1"}
{"html":"\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"...","level":"info","message":"Email would be sent:","service":"docket-api","subject":"New Court Filing: Discovery Motion Filed","timestamp":"2025-06-24T22:44:33.447Z","to":"test@example.com"}
{"level":"info","message":"Email notifications sent: 1 successful, 0 failed","service":"docket-api","timestamp":"2025-06-24T22:44:33.548Z"}
{"duration":"123ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":201,"timestamp":"2025-06-24T22:44:33.548Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:45:35.068Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:45:35.075Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:45:38.010Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-24T22:45:38.014Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:47:42.071Z","url":"/api/docket-entries","userAgent":"curl/8.7.1"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:47:42.076Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:47:42.084Z","url":"/api/docket-entries","userAgent":"curl/8.7.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:47:42.087Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:47:42.099Z","url":"/api/subscriptions/subscribe","userAgent":"curl/8.7.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:47:42.105Z","url":"/subscribe"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:47:42.112Z","url":"/api/subscriptions/subscribe","userAgent":"curl/8.7.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":400,"timestamp":"2025-06-24T22:47:42.114Z","url":"/subscribe"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:47:42.122Z","url":"/api/auth/login","userAgent":"curl/8.7.1"}
{"duration":"238ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:47:42.360Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:47:42.374Z","url":"/api/auth/verify","userAgent":"curl/8.7.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:47:42.376Z","url":"/verify"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:47:42.385Z","url":"/api/docket-entries/1","userAgent":"curl/8.7.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:47:42.391Z","url":"/1"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:47:42.402Z","url":"/api/docket-entries","userAgent":"curl/8.7.1"}
{"html":"\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"...","level":"info","message":"Email would be sent:","service":"docket-api","subject":"New Court Filing: Automated Test Entry - 17:47:42","timestamp":"2025-06-24T22:47:42.412Z","to":"test@example.com"}
{"html":"\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"...","level":"info","message":"Email would be sent:","service":"docket-api","subject":"New Court Filing: Automated Test Entry - 17:47:42","timestamp":"2025-06-24T22:47:42.412Z","to":"test1750805262@example.com"}
{"level":"info","message":"Email notifications sent: 2 successful, 0 failed","service":"docket-api","timestamp":"2025-06-24T22:47:42.513Z"}
{"duration":"112ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":201,"timestamp":"2025-06-24T22:47:42.514Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:47:43.537Z","url":"/api/documents/upload","userAgent":"curl/8.7.1"}
{"duration":"0ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":401,"timestamp":"2025-06-24T22:47:43.537Z","url":"/upload"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:47:43.550Z","url":"/api/documents/upload","userAgent":"curl/8.7.1"}
{"error":"Only PDF files are allowed","ip":"::1","level":"error","message":"Error occurred:","method":"POST","service":"docket-api","stack":"Error: Only PDF files are allowed\n at fileFilter (/Users/chaulmark/Eliza-MAD-docket-website/backend/src/routes/documents.ts:39:10)\n at wrappedFileFilter (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/multer@1.4.5-lts.2/node_modules/multer/index.js:44:7)\n at Multipart.<anonymous> (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/multer@1.4.5-lts.2/node_modules/multer/lib/make-middleware.js:109:7)\n at Multipart.emit (node:events:507:28)\n at HeaderParser.cb (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:358:14)\n at HeaderParser.push (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:162:20)\n at SBMH.ssCb [as _cb] (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:394:37)\n at feed (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/streamsearch@1.1.0/node_modules/streamsearch/lib/sbmh.js:219:14)\n at SBMH.push (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/streamsearch@1.1.0/node_modules/streamsearch/lib/sbmh.js:104:16)\n at Multipart._write (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:567:19)","timestamp":"2025-06-24T22:47:43.556Z","url":"/api/documents/upload","userAgent":"curl/8.7.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":500,"timestamp":"2025-06-24T22:47:43.556Z","url":"/api/documents/upload"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:47:43.570Z","url":"/api/auth/login","userAgent":"curl/8.7.1"}
{"duration":"231ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":401,"timestamp":"2025-06-24T22:47:43.801Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:47:43.809Z","url":"/api/auth/verify","userAgent":"curl/8.7.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":403,"timestamp":"2025-06-24T22:47:43.810Z","url":"/verify"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:47:43.817Z","url":"/api/health","userAgent":"curl/8.7.1"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:47:43.822Z","url":"/"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-24T22:49:20.164Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-24T22:49:32.273Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-24T22:49:32.275Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-24T22:49:32.278Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-24T22:49:32.777Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:49:42.212Z","url":"/api/auth/login","userAgent":"curl/8.7.1"}
{"duration":"248ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:49:42.460Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:49:42.470Z","url":"/api/docket-entries","userAgent":"curl/8.7.1"}
{"level":"info","message":"Email sent successfully:","messageId":"<ae446ddd-3cdd-afc2-3b7e-5cd4819c08ae@deafgain.org>","service":"docket-api","subject":"New Court Filing: Real Email Test - Settlement Conference Scheduled","timestamp":"2025-06-24T22:49:43.978Z","to":"test1750805262@example.com"}
{"level":"info","message":"Email sent successfully:","messageId":"<e570d07d-b26e-2726-8924-e383604cf0b4@deafgain.org>","service":"docket-api","subject":"New Court Filing: Real Email Test - Settlement Conference Scheduled","timestamp":"2025-06-24T22:49:44.022Z","to":"test@example.com"}
{"level":"info","message":"Email notifications sent: 2 successful, 0 failed","service":"docket-api","timestamp":"2025-06-24T22:49:44.024Z"}
{"duration":"1555ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":201,"timestamp":"2025-06-24T22:49:44.025Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:51:06.241Z","url":"/api/docket-entries","userAgent":"curl/8.7.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:51:06.247Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:51:50.975Z","url":"/api/subscriptions/subscribe","userAgent":"curl/8.7.1"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:51:50.982Z","url":"/subscribe"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:52:01.178Z","url":"/api/auth/login","userAgent":"curl/8.7.1"}
{"duration":"229ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:52:01.407Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-24T22:52:01.415Z","url":"/api/docket-entries","userAgent":"curl/8.7.1"}
{"level":"info","message":"Email sent successfully:","messageId":"<2d07ac41-147a-1249-5fde-395dd03b18f5@deafgain.org>","service":"docket-api","subject":"New Court Filing: Email Test for Chris - Motion for Summary Judgment Filed","timestamp":"2025-06-24T22:52:02.630Z","to":"test@example.com"}
{"level":"info","message":"Email sent successfully:","messageId":"<87a974f7-0735-ef00-8f83-bae865916422@deafgain.org>","service":"docket-api","subject":"New Court Filing: Email Test for Chris - Motion for Summary Judgment Filed","timestamp":"2025-06-24T22:52:02.631Z","to":"test1750805262@example.com"}
{"level":"info","message":"Email sent successfully:","messageId":"<3e6a094c-9cb0-0e95-b5cd-9de97df11e33@deafgain.org>","service":"docket-api","subject":"New Court Filing: Email Test for Chris - Motion for Summary Judgment Filed","timestamp":"2025-06-24T22:52:04.474Z","to":"chris@sigd.net"}
{"level":"info","message":"Email notifications sent: 3 successful, 0 failed","service":"docket-api","timestamp":"2025-06-24T22:52:04.475Z"}
{"duration":"3061ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":201,"timestamp":"2025-06-24T22:52:04.476Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T22:58:48.528Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"19ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-24T22:58:48.547Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-24T23:01:13.428Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-24T23:01:13.432Z","url":"/"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T13:38:31.483Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T13:38:31.485Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T13:38:31.489Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T13:38:31.965Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T13:38:50.881Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"21ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T13:38:50.902Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:00:26.370Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"12ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:00:26.382Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:01:04.654Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:01:04.658Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:06:46.367Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"17ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:06:46.384Z","url":"/"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T14:06:52.974Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T14:06:53.436Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T14:06:53.437Z"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T14:06:53.439Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T14:06:53.440Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T14:07:10.448Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T14:07:10.449Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T14:07:10.452Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T14:07:10.927Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:07:14.281Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"19ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:07:14.300Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:09:11.796Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:09:11.801Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:16:36.010Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"17ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:16:36.027Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:19:59.753Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:19:59.756Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:20:26.607Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:20:26.612Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:21:36.853Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"17ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:21:36.870Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:29:05.803Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"18ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:29:05.821Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:29:22.453Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:29:22.458Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:32:10.797Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:32:10.801Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T14:38:43.507Z","url":"/api/auth/login","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"266ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T14:38:43.773Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:38:44.159Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:38:44.165Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:38:44.167Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T14:38:44.170Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:39:39.010Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:39:39.014Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:40:55.724Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:40:55.729Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:41:42.699Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:41:42.702Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:42:29.563Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:42:29.567Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:42:42.320Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:42:42.323Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:42:42.326Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:42:42.328Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:42:49.326Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:42:49.330Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:42:49.333Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T14:42:49.336Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:44:45.354Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:44:45.362Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"24ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:44:45.378Z","url":"/"}
{"duration":"18ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T14:44:45.380Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:49:49.872Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"16ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:49:49.888Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:50:39.092Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:50:39.096Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:50:39.101Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T14:50:39.103Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T14:56:27.090Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"17ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T14:56:27.107Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:00:01.999Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:00:02.003Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:00:17.453Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:00:17.457Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:00:27.417Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:00:27.420Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:00:27.422Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:00:27.425Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:00:29.988Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:00:29.991Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:00:29.994Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:00:29.997Z","url":"/"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T15:00:53.837Z"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T15:00:53.837Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T15:01:10.606Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T15:01:10.607Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T15:01:10.609Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T15:01:11.462Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:01:15.919Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"19ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:01:15.938Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:01:19.275Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:01:19.279Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:01:19.284Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T15:01:19.288Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:01:20.890Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:01:20.893Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:01:20.897Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:01:20.900Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:01:22.797Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:01:22.800Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:01:22.805Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:01:22.807Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:01:24.249Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:01:24.253Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:01:24.258Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:01:24.261Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:55:43.733Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"18ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:55:43.751Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:55:54.282Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:55:54.285Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:55:54.289Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:55:54.291Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:55:56.034Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:55:56.037Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:55:56.040Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:55:56.043Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"PUT","service":"docket-api","timestamp":"2025-06-25T15:56:15.070Z","url":"/api/docket-entries/8","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"14ms","ip":"::1","level":"info","message":"Request completed","method":"PUT","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T15:56:15.084Z","url":"/8"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:56:15.089Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T15:56:15.091Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:56:32.552Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:56:32.554Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"PUT","service":"docket-api","timestamp":"2025-06-25T15:57:01.882Z","url":"/api/docket-entries/8","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"15ms","ip":"::1","level":"info","message":"Request completed","method":"PUT","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T15:57:01.897Z","url":"/8"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:57:01.906Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T15:57:01.909Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:57:06.435Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:57:06.438Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"PUT","service":"docket-api","timestamp":"2025-06-25T15:57:14.812Z","url":"/api/docket-entries/8","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"10ms","ip":"::1","level":"info","message":"Request completed","method":"PUT","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T15:57:14.822Z","url":"/8"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:57:14.828Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T15:57:14.831Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:57:18.727Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:57:18.731Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:57:26.740Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:57:26.743Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T15:57:26.747Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T15:57:26.749Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T15:58:15.130Z","url":"/api/documents/upload","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T15:58:52.409Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T15:58:52.410Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T15:58:52.413Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T15:58:52.891Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T15:59:55.718Z","url":"/api/documents/upload","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T16:00:54.943Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T16:00:54.945Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T16:00:54.947Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T16:00:55.507Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T16:01:00.397Z","url":"/api/documents/upload","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"42ms","ip":"::1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":201,"timestamp":"2025-06-25T16:01:00.439Z","url":"/upload"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:01:07.043Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:01:07.049Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:01:16.880Z","url":"/api/documents/1/download","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"12ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:01:16.892Z","url":"/1/download"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T16:01:45.528Z"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T16:01:45.532Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T16:10:56.757Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T16:10:56.758Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T16:10:56.760Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T16:10:57.292Z"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T16:11:12.710Z"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T16:11:12.713Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T16:12:30.150Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T16:12:30.151Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T16:12:30.153Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T16:12:30.611Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:12:44.316Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"}
{"duration":"19ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:12:44.335Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:12:50.408Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:12:50.412Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:12:54.677Z","url":"/api/documents/1/download","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"10ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:12:54.687Z","url":"/1/download"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:13:14.780Z","url":"/api/documents/1/download","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:13:14.785Z","url":"/1/download"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:13:34.059Z","url":"/api/documents/1/download","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:13:34.065Z","url":"/1/download"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:14:03.596Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:14:03.600Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:14:03.604Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:14:03.607Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"PUT","service":"docket-api","timestamp":"2025-06-25T16:14:19.020Z","url":"/api/docket-entries/8","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"18ms","ip":"::1","level":"info","message":"Request completed","method":"PUT","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:14:19.038Z","url":"/8"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:14:19.047Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:14:19.050Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:14:22.721Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:14:22.724Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"PUT","service":"docket-api","timestamp":"2025-06-25T16:14:38.542Z","url":"/api/docket-entries/8","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"12ms","ip":"::1","level":"info","message":"Request completed","method":"PUT","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:14:38.554Z","url":"/8"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:14:38.562Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:14:38.565Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:14:41.134Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:14:41.137Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:18:12.936Z","url":"/api/documents/1/download","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"23ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:18:12.959Z","url":"/1/download"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:18:25.293Z","url":"/api/documents/1/download","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:18:25.296Z","url":"/1/download"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:18:41.211Z","url":"/api/documents/1/download","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:18:41.216Z","url":"/1/download"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:25:03.885Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"18ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:25:03.903Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:25:07.631Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:25:07.635Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:25:35.042Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:25:35.046Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"PUT","service":"docket-api","timestamp":"2025-06-25T16:27:04.052Z","url":"/api/docket-entries/8","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"12ms","ip":"::1","level":"info","message":"Request completed","method":"PUT","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:27:04.064Z","url":"/8"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:27:04.071Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:27:04.074Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:27:07.884Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:27:07.887Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:30:38.837Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"17ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:30:38.854Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:32:16.783Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:32:16.788Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:34:58.611Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:34:58.614Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:35:48.523Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:35:48.524Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"22ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:35:48.545Z","url":"/"}
{"duration":"21ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:35:48.545Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:36:40.857Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:36:40.861Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:36:40.864Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:36:40.866Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:37:58.921Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:37:58.924Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:37:58.928Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:37:58.931Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:38:04.598Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:38:04.602Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:38:04.605Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:38:04.607Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:38:06.105Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:38:06.108Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:38:06.113Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:38:06.115Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:39:38.668Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:39:38.675Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:39:38.677Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:39:38.680Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:39:40.218Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:39:40.223Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:39:41.858Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:39:41.861Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:39:41.868Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:39:41.870Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:08.199Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:40:08.204Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:08.207Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:40:08.210Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:38.268Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:40:38.272Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:39.922Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:39.923Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:40:39.926Z","url":"/"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:40:39.927Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:44.249Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:40:44.252Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:44.255Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:40:44.257Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:48.683Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:48.693Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"29ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:40:48.713Z","url":"/"}
{"duration":"20ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:40:48.713Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:50.184Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:40:50.188Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:40:50.191Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:40:50.194Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:41:00.830Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:41:00.833Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:44:17.842Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:44:17.846Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:44:19.428Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:44:19.432Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:44:19.435Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:44:19.437Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:44:22.130Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:44:22.134Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:44:22.138Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:44:22.141Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:45:18.485Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:45:18.489Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:45:18.497Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:45:18.500Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:45:21.632Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:45:21.635Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:45:21.639Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:45:21.641Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:51:08.564Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"16ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:51:08.580Z","url":"/"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T16:51:30.090Z"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T16:51:30.092Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T16:51:31.610Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T16:51:31.612Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T16:51:31.614Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T16:51:32.064Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:51:40.787Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"19ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:51:40.806Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:52:11.625Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:52:11.630Z","url":"/"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T16:53:15.847Z"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T16:53:15.849Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T16:53:24.138Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T16:53:24.139Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T16:53:24.141Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T16:53:24.600Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:53:27.326Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"18ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:53:27.345Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:53:36.575Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T16:53:36.579Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:56:11.828Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:56:11.832Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:56:27.948Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:56:27.952Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T16:57:40.062Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T16:57:40.066Z","url":"/"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T16:58:15.851Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T16:58:16.348Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T16:58:16.349Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T16:58:16.352Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T16:58:16.926Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T17:00:36.490Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T17:00:36.491Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T17:00:36.493Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T17:00:38.052Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:01:08.516Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"18ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T17:01:08.534Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:01:59.122Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:01:59.126Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:02:23.225Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:02:23.230Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:02:23.236Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T17:02:23.239Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:02:26.892Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:02:26.896Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:02:26.902Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T17:02:26.905Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:09:39.693Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"14ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:09:39.707Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:11:13.511Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:11:13.516Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:11:25.325Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:11:25.329Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:11:25.332Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:11:25.335Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:11:28.796Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:11:28.801Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:11:28.804Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:11:28.808Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:15:43.200Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"17ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:15:43.217Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:16:08.337Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:16:08.341Z","url":"/"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T17:17:58.237Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T17:17:58.639Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T17:17:58.640Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T17:17:58.643Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T17:17:59.241Z"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T17:18:52.571Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T17:18:52.961Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T17:18:52.963Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T17:18:52.965Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T17:18:53.437Z"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T17:19:09.018Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T17:19:09.401Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T17:19:09.402Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T17:19:09.405Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T17:19:09.897Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:20:39.310Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"22ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:20:39.331Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:20:41.966Z","url":"/api/subscriptions","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"8ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T17:20:41.974Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:20:41.977Z","url":"/api/subscriptions","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T17:20:41.981Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:21:15.553Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T17:21:15.553Z","url":"/api/docket-entries","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":304,"timestamp":"2025-06-25T17:21:15.557Z","url":"/"}
{"duration":"16ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T17:21:15.569Z","url":"/"}
{"level":"info","message":"SIGINT received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T17:31:17.501Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T17:45:56.359Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T17:45:56.360Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T17:45:56.366Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T17:47:42.726Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T17:47:42.727Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T17:47:42.734Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T18:04:15.731Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T18:04:15.734Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T18:04:15.751Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T18:04:16.426Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:04:18.874Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"121ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:04:18.995Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:04:49.082Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:04:49.089Z","url":"/"}
{"ip":"::ffff:172.18.0.1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:05:01.969Z","url":"/api/health","userAgent":"curl/8.7.1"}
{"duration":"10ms","ip":"::ffff:172.18.0.1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:05:01.978Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:05:19.158Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:05:19.163Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:05:49.240Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:05:49.245Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:06:19.334Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:06:19.341Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:06:49.431Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"8ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:06:49.439Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:07:19.528Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:07:19.535Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:07:49.628Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"8ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:07:49.637Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:08:19.720Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:08:19.726Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:08:49.821Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:08:49.825Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:09:19.913Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"41ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:09:19.954Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:09:50.020Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:09:50.024Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:10:20.087Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:10:20.092Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:10:50.143Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:10:50.148Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:11:20.225Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:11:20.231Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:11:50.322Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:11:50.328Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:12:20.401Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:12:20.408Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:12:50.466Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:12:50.471Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:13:20.568Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:13:20.574Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:13:50.669Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:13:50.675Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:14:20.753Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"46ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:14:20.799Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:14:50.887Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:14:50.891Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:15:20.997Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:15:21.001Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:15:51.087Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:15:51.089Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:16:21.182Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:16:21.189Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:16:51.279Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:16:51.282Z","url":"/"}
{"ip":"::ffff:172.18.0.1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T18:16:53.682Z","url":"/api/auth/login","userAgent":"curl/8.7.1"}
{"duration":"3943ms","ip":"::ffff:172.18.0.1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:16:57.626Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:17:21.368Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:17:21.372Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:17:51.429Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:17:51.431Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:18:21.494Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:18:21.496Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:18:51.589Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:18:51.591Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:19:21.684Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"39ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:19:21.723Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:19:51.814Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:19:51.817Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:20:21.896Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:20:21.897Z","url":"/"}
{"ip":"::ffff:172.18.0.1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T18:20:36.608Z","url":"/api/auth/login","userAgent":"curl/8.7.1"}
{"duration":"3889ms","ip":"::ffff:172.18.0.1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:20:40.497Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:20:51.979Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:20:51.981Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:21:22.051Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:21:22.053Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:21:52.115Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:21:52.117Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:22:22.202Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:22:22.204Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:22:52.320Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:22:52.322Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:23:22.390Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:23:22.392Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:23:52.474Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:23:52.475Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:24:22.566Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"40ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:24:22.606Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:24:52.696Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:24:52.699Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:25:22.789Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:25:22.792Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:25:52.888Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:25:52.890Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:26:22.971Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:26:22.973Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:26:53.064Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:26:53.066Z","url":"/"}
{"ip":"::ffff:172.18.0.1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T18:27:17.986Z","url":"/api/auth/login","userAgent":"curl/8.7.1"}
{"duration":"3908ms","ip":"::ffff:172.18.0.1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:27:21.894Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:27:23.157Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:27:23.159Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:27:53.254Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:27:53.257Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:28:23.322Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:28:23.323Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:28:53.429Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:28:53.431Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:29:23.501Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"37ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:29:23.538Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:29:53.622Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:29:53.625Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:30:23.709Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:30:23.711Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:30:53.799Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:30:53.801Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:31:23.884Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:31:23.886Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:31:53.967Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:31:53.969Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:32:24.053Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:32:24.055Z","url":"/"}
{"ip":"::ffff:172.18.0.5","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T18:32:41.817Z","url":"/api/auth/login"}
{"duration":"3933ms","ip":"::ffff:172.18.0.5","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:32:45.750Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:32:54.142Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:32:54.143Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:33:24.203Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:33:24.206Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:33:54.285Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:33:54.287Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:34:24.358Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"36ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:34:24.394Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:34:54.485Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:34:54.488Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:35:24.553Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:35:24.554Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:35:54.623Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:35:54.625Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:36:24.688Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:36:24.690Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:36:54.778Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:36:54.781Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:37:24.870Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:37:24.873Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:37:54.966Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:37:54.968Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:38:25.064Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:38:25.066Z","url":"/"}
{"ip":"::ffff:172.18.0.5","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T18:38:50.489Z","url":"/api/auth/login","userAgent":"curl/8.14.1"}
{"duration":"3949ms","ip":"::ffff:172.18.0.5","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:38:54.438Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:38:55.165Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:38:55.167Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:39:25.255Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"39ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:39:25.294Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:39:55.387Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:39:55.389Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:40:25.477Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:40:25.480Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:40:55.555Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:40:55.557Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:41:25.646Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:41:25.648Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:41:55.720Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:41:55.723Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:42:25.806Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:42:25.807Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:42:55.895Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:42:55.896Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:43:25.990Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:43:25.993Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:43:56.084Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:43:56.086Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:44:26.148Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"34ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:44:26.182Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:44:56.251Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:44:56.254Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:45:26.322Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:45:26.324Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:45:56.418Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:45:56.420Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:46:26.513Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:46:26.514Z","url":"/"}
{"ip":"::ffff:172.18.0.5","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T18:46:38.766Z","url":"/api/auth/login","userAgent":"node"}
{"duration":"3924ms","ip":"::ffff:172.18.0.5","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:46:42.690Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:46:56.586Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:46:56.588Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:47:26.677Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"12ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:47:26.689Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:47:56.755Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:47:56.758Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:48:26.824Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:48:26.826Z","url":"/"}
{"ip":"::ffff:172.18.0.1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T18:48:55.788Z","url":"/api/auth/login","userAgent":"curl/8.7.1"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:48:57.020Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"308ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:48:57.329Z","url":"/"}
{"duration":"3958ms","ip":"::ffff:172.18.0.1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:48:59.746Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:49:27.422Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"46ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:49:27.468Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:49:57.539Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:49:57.542Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:50:27.623Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:50:27.625Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:50:57.714Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:50:57.718Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:51:27.808Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:51:27.809Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:51:57.862Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"1ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:51:57.863Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:52:27.952Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:52:27.954Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:52:58.038Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:52:58.040Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:53:28.130Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:53:28.132Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:53:58.224Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:53:58.226Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:54:28.315Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"37ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:54:28.353Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:54:58.434Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"3ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:54:58.437Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:55:28.521Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"2ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:55:28.523Z","url":"/"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T18:55:47.633Z"}
{"level":"info","message":"Server running on port 3001","service":"docket-api","timestamp":"2025-06-25T18:56:03.229Z"}
{"level":"info","message":"Environment: development","service":"docket-api","timestamp":"2025-06-25T18:56:03.232Z"}
{"level":"info","message":"Connected to Redis","service":"docket-api","timestamp":"2025-06-25T18:56:03.250Z"}
{"level":"info","message":"SMTP connection verified successfully","service":"docket-api","timestamp":"2025-06-25T18:56:03.817Z"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:56:06.438Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"121ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:56:06.559Z","url":"/"}
{"ip":"::ffff:172.19.0.5","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:56:25.990Z","url":"/api/health"}
{"duration":"6ms","ip":"::ffff:172.19.0.5","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:56:25.995Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:56:36.639Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"8ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:56:36.647Z","url":"/"}
{"ip":"::ffff:172.19.0.5","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T18:56:44.561Z","url":"/api/auth/login"}
{"duration":"3900ms","ip":"::ffff:172.19.0.5","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:56:48.461Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:57:06.716Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:57:06.722Z","url":"/"}
{"ip":"::ffff:172.19.0.1","level":"info","message":"Incoming request","method":"POST","service":"docket-api","timestamp":"2025-06-25T18:57:22.523Z","url":"/api/auth/login","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0"}
{"duration":"3981ms","ip":"::ffff:172.19.0.1","level":"info","message":"Request completed","method":"POST","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:57:26.505Z","url":"/login"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:57:36.810Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"13ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:57:36.824Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:58:06.907Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:58:06.912Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:58:37.002Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:58:37.005Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:59:07.101Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"4ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:59:07.106Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T18:59:37.200Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"5ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T18:59:37.205Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T19:00:07.300Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T19:00:07.306Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T19:00:37.403Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T19:00:37.409Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T19:01:07.498Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"45ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T19:01:07.542Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T19:01:37.603Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T19:01:37.609Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T19:02:07.688Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"7ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T19:02:07.696Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T19:02:37.793Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"8ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T19:02:37.801Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T19:03:07.892Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"6ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T19:03:07.898Z","url":"/"}
{"ip":"::1","level":"info","message":"Incoming request","method":"GET","service":"docket-api","timestamp":"2025-06-25T19:03:37.989Z","url":"/api/health","userAgent":"curl/8.12.1"}
{"duration":"8ms","ip":"::1","level":"info","message":"Request completed","method":"GET","service":"docket-api","statusCode":200,"timestamp":"2025-06-25T19:03:37.996Z","url":"/"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","service":"docket-api","timestamp":"2025-06-25T19:03:54.773Z"}

View file

@ -1,2 +0,0 @@
{"error":"Only PDF files are allowed","ip":"::1","level":"error","message":"Error occurred:","method":"POST","service":"docket-api","stack":"Error: Only PDF files are allowed\n at fileFilter (/Users/chaulmark/Eliza-MAD-docket-website/backend/src/routes/documents.ts:39:10)\n at wrappedFileFilter (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/multer@1.4.5-lts.2/node_modules/multer/index.js:44:7)\n at Multipart.<anonymous> (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/multer@1.4.5-lts.2/node_modules/multer/lib/make-middleware.js:109:7)\n at Multipart.emit (node:events:507:28)\n at HeaderParser.cb (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:358:14)\n at HeaderParser.push (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:162:20)\n at SBMH.ssCb [as _cb] (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:394:37)\n at feed (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/streamsearch@1.1.0/node_modules/streamsearch/lib/sbmh.js:219:14)\n at SBMH.push (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/streamsearch@1.1.0/node_modules/streamsearch/lib/sbmh.js:104:16)\n at Multipart._write (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:567:19)","timestamp":"2025-06-24T22:43:07.987Z","url":"/api/documents/upload","userAgent":"curl/8.7.1"}
{"error":"Only PDF files are allowed","ip":"::1","level":"error","message":"Error occurred:","method":"POST","service":"docket-api","stack":"Error: Only PDF files are allowed\n at fileFilter (/Users/chaulmark/Eliza-MAD-docket-website/backend/src/routes/documents.ts:39:10)\n at wrappedFileFilter (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/multer@1.4.5-lts.2/node_modules/multer/index.js:44:7)\n at Multipart.<anonymous> (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/multer@1.4.5-lts.2/node_modules/multer/lib/make-middleware.js:109:7)\n at Multipart.emit (node:events:507:28)\n at HeaderParser.cb (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:358:14)\n at HeaderParser.push (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:162:20)\n at SBMH.ssCb [as _cb] (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:394:37)\n at feed (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/streamsearch@1.1.0/node_modules/streamsearch/lib/sbmh.js:219:14)\n at SBMH.push (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/streamsearch@1.1.0/node_modules/streamsearch/lib/sbmh.js:104:16)\n at Multipart._write (/Users/chaulmark/Eliza-MAD-docket-website/backend/node_modules/.pnpm/busboy@1.6.0/node_modules/busboy/lib/types/multipart.js:567:19)","timestamp":"2025-06-24T22:47:43.556Z","url":"/api/documents/upload","userAgent":"curl/8.7.1"}

View file

@ -1,60 +0,0 @@
{
"name": "eliza-mad-docket-backend",
"version": "1.0.0",
"description": "Backend API for Eliza Kragh v. MAD court docket website",
"main": "dist/index.js",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
"prisma:deploy": "prisma migrate deploy",
"prisma:studio": "prisma studio",
"test": "jest",
"test:watch": "jest --watch",
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix"
},
"dependencies": {
"@prisma/adapter-pg": "^7.1.0",
"@prisma/client": "^7.1.0",
"bcryptjs": "^3.0.3",
"compression": "^1.8.1",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.2.1",
"express-rate-limit": "^8.2.1",
"helmet": "^8.1.0",
"joi": "^18.0.2",
"jsonwebtoken": "^9.0.3",
"multer": "^2.0.2",
"nodemailer": "^7.0.11",
"pg": "^8.16.3",
"prisma": "^7.1.0",
"redis": "^5.10.0",
"uuid": "^13.0.0",
"winston": "^3.19.0"
},
"devDependencies": {
"@types/bcryptjs": "^3.0.0",
"@types/compression": "^1.8.1",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/jest": "^30.0.0",
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.0.0",
"@types/node": "^25.0.2",
"@types/nodemailer": "^7.0.4",
"@types/uuid": "^11.0.0",
"@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"eslint": "^9.39.2",
"jest": "^30.2.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
},
"engines": {
"node": ">=22.0.0"
}
}

6809
backend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,12 +0,0 @@
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: env("DATABASE_URL"),
},
});

View file

@ -1,80 +0,0 @@
-- CreateTable
CREATE TABLE "docket_entries" (
"id" SERIAL NOT NULL,
"date" DATE NOT NULL,
"document_type" VARCHAR(100) NOT NULL,
"summary" TEXT NOT NULL,
"court_notes" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "docket_entries_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "documents" (
"id" SERIAL NOT NULL,
"docket_entry_id" INTEGER NOT NULL,
"original_filename" VARCHAR(255) NOT NULL,
"stored_filename" VARCHAR(255) NOT NULL,
"file_path" VARCHAR(500) NOT NULL,
"title" VARCHAR(255) NOT NULL,
"summary" TEXT,
"notes" TEXT,
"is_primary" BOOLEAN NOT NULL DEFAULT false,
"file_size" INTEGER NOT NULL,
"display_order" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "documents_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "subscriptions" (
"id" SERIAL NOT NULL,
"email" VARCHAR(255) NOT NULL,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"unsubscribe_token" VARCHAR(255) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "subscriptions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "admin_users" (
"id" SERIAL NOT NULL,
"username" VARCHAR(50) NOT NULL,
"password_hash" VARCHAR(255) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"last_login" TIMESTAMP(3),
CONSTRAINT "admin_users_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "idx_docket_entries_date" ON "docket_entries"("date" DESC);
-- CreateIndex
CREATE UNIQUE INDEX "documents_stored_filename_key" ON "documents"("stored_filename");
-- CreateIndex
CREATE INDEX "idx_documents_docket_entry" ON "documents"("docket_entry_id");
-- CreateIndex
CREATE INDEX "idx_documents_display_order" ON "documents"("docket_entry_id", "display_order");
-- CreateIndex
CREATE UNIQUE INDEX "subscriptions_email_key" ON "subscriptions"("email");
-- CreateIndex
CREATE UNIQUE INDEX "subscriptions_unsubscribe_token_key" ON "subscriptions"("unsubscribe_token");
-- CreateIndex
CREATE INDEX "idx_subscriptions_active" ON "subscriptions"("is_active");
-- CreateIndex
CREATE UNIQUE INDEX "admin_users_username_key" ON "admin_users"("username");
-- AddForeignKey
ALTER TABLE "documents" ADD CONSTRAINT "documents_docket_entry_id_fkey" FOREIGN KEY ("docket_entry_id") REFERENCES "docket_entries"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View file

@ -1,20 +0,0 @@
/*
Warnings:
- You are about to drop the column `court_notes` on the `docket_entries` table. All the data in the column will be lost.
- You are about to drop the column `document_type` on the `docket_entries` table. All the data in the column will be lost.
- You are about to drop the column `is_primary` on the `documents` table. All the data in the column will be lost.
- Added the required column `title` to the `docket_entries` table without a default value. This is not possible if the table is not empty.
- Added the required column `mime_type` to the `documents` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "docket_entries" DROP COLUMN "court_notes",
DROP COLUMN "document_type",
ADD COLUMN "notes" TEXT,
ADD COLUMN "title" VARCHAR(500) NOT NULL;
-- AlterTable
ALTER TABLE "documents" DROP COLUMN "is_primary",
ADD COLUMN "mime_type" VARCHAR(100) NOT NULL,
ALTER COLUMN "title" SET DATA TYPE VARCHAR(500);

View file

@ -1,3 +0,0 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View file

@ -1,70 +0,0 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
model DocketEntry {
id Int @id @default(autoincrement())
date DateTime @db.Date
title String @db.VarChar(500)
summary String @db.Text
notes String? @db.Text
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
documents Document[]
@@map("docket_entries")
@@index([date(sort: Desc)], name: "idx_docket_entries_date")
}
model Document {
id Int @id @default(autoincrement())
docketEntryId Int @map("docket_entry_id")
title String @db.VarChar(500)
originalFilename String @map("original_filename") @db.VarChar(255)
storedFilename String @unique @map("stored_filename") @db.VarChar(255)
filePath String @map("file_path") @db.VarChar(500)
fileSize Int @map("file_size")
mimeType String @map("mime_type") @db.VarChar(100)
summary String? @db.Text
notes String? @db.Text
displayOrder Int @default(0) @map("display_order")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
docketEntry DocketEntry @relation(fields: [docketEntryId], references: [id], onDelete: Cascade)
@@map("documents")
@@index([docketEntryId], name: "idx_documents_docket_entry")
@@index([docketEntryId, displayOrder], name: "idx_documents_display_order")
}
model Subscription {
id Int @id @default(autoincrement())
email String @unique @db.VarChar(255)
isActive Boolean @default(true) @map("is_active")
unsubscribeToken String @unique @map("unsubscribe_token") @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at")
@@map("subscriptions")
@@index([isActive], name: "idx_subscriptions_active")
}
model AdminUser {
id Int @id @default(autoincrement())
username String @unique @db.VarChar(50)
passwordHash String @map("password_hash") @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at")
lastLogin DateTime? @map("last_login")
@@map("admin_users")
}

View file

@ -1,63 +0,0 @@
#!/usr/bin/env node
const bcrypt = require('bcryptjs');
const { PrismaClient } = require('@prisma/client');
const crypto = require('crypto');
const prisma = new PrismaClient();
async function setupAdmin() {
try {
console.log('🔧 Setting up admin user for production...');
// Generate secure credentials
const username = 'eliza';
const password = crypto.randomBytes(12).toString('base64').replace(/[+/=]/g, '').substring(0, 16);
console.log('\n📋 Generated Credentials:');
console.log('Username:', username);
console.log('Password:', password);
console.log('\n⚠ IMPORTANT: Save these credentials securely!');
// Hash the password
const saltRounds = 12;
const passwordHash = await bcrypt.hash(password, saltRounds);
// Check if admin user already exists
const existingAdmin = await prisma.adminUser.findUnique({
where: { username }
});
if (existingAdmin) {
// Update existing admin
await prisma.adminUser.update({
where: { username },
data: {
passwordHash
}
});
console.log('\n✅ Admin user updated successfully!');
} else {
// Create new admin user
await prisma.adminUser.create({
data: {
username,
passwordHash
}
});
console.log('\n✅ Admin user created successfully!');
}
console.log('\n🚀 Production setup complete!');
console.log('Eliza can now log in with the credentials above.');
} catch (error) {
console.error('❌ Error setting up admin user:', error);
process.exit(1);
} finally {
await prisma.$disconnect();
}
}
// Run the setup
setupAdmin();

View file

@ -1,66 +0,0 @@
#!/usr/bin/env node
const bcrypt = require('bcryptjs');
const { PrismaClient } = require('@prisma/client');
const crypto = require('crypto');
const prisma = new PrismaClient();
async function setupAdmin() {
try {
console.log('🔧 Setting up admin user for production...');
// Generate secure credentials
const username = 'eliza';
const password = crypto.randomBytes(12).toString('base64').replace(/[+/=]/g, '').substring(0, 16);
console.log('\n📋 Generated Credentials:');
console.log('Username:', username);
console.log('Password:', password);
console.log('\n⚠ IMPORTANT: Save these credentials securely!');
// Hash the password
const saltRounds = 12;
const passwordHash = await bcrypt.hash(password, saltRounds);
// Check if admin user already exists
const existingAdmin = await prisma.adminUser.findUnique({
where: { username }
});
if (existingAdmin) {
// Update existing admin
await prisma.adminUser.update({
where: { username },
data: {
passwordHash,
updatedAt: new Date()
}
});
console.log('\n✅ Admin user updated successfully!');
} else {
// Create new admin user
await prisma.adminUser.create({
data: {
username,
passwordHash,
createdAt: new Date(),
updatedAt: new Date()
}
});
console.log('\n✅ Admin user created successfully!');
}
console.log('\n🚀 Production setup complete!');
console.log('Eliza can now log in with the credentials above.');
} catch (error) {
console.error('❌ Error setting up admin user:', error);
process.exit(1);
} finally {
await prisma.$disconnect();
}
}
// Run the setup
setupAdmin();

View file

@ -1,178 +0,0 @@
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import compression from 'compression';
import rateLimit from 'express-rate-limit';
import dotenv from 'dotenv';
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import pg from 'pg';
import { createClient } from 'redis';
import { logger } from './utils/logger';
import { errorHandler } from './middleware/errorHandler';
import { requestLogger } from './middleware/requestLogger';
// Import routes
import authRoutes from './routes/auth';
import docketRoutes from './routes/docket';
import documentRoutes from './routes/documents';
import subscriptionRoutes from './routes/subscriptions';
import healthRoutes from './routes/health';
import notificationRoutes from './routes/notifications';
// Load environment variables
dotenv.config();
const app = express();
const PORT = process.env.PORT || 3001;
// Trust proxy when behind reverse proxy (Caddy)
app.set('trust proxy', true);
// Initialize Prisma client with PostgreSQL adapter
const connectionString = process.env.DATABASE_URL;
const pool = new pg.Pool({ connectionString });
const adapter = new PrismaPg(pool);
export const prisma = new PrismaClient({
adapter,
log: ['query', 'info', 'warn', 'error'],
});
// Initialize Redis client
export const redis = createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379',
});
// Connect to Redis
redis.connect().catch((error) => {
logger.error('Redis connection failed:', error);
});
redis.on('error', (error) => {
logger.error('Redis error:', error);
});
redis.on('connect', () => {
logger.info('Connected to Redis');
});
// Security middleware
app.use(helmet({
crossOriginResourcePolicy: { policy: "cross-origin" }
}));
// CORS configuration
app.use(cors({
origin: process.env.NODE_ENV === 'production'
? [
process.env.FRONTEND_URL,
'https://mad-lawsuit.org',
'https://files.mad-lawsuit.org',
'http://frontend:806',
'http://localhost:806'
]
: [
'http://localhost:3000',
'http://localhost:806',
'http://frontend:806',
'http://frontend:3000',
'https://mad-lawsuit.org',
'https://files.mad-lawsuit.org'
],
credentials: true,
}));
// Rate limiting with proper proxy configuration
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
standardHeaders: true,
legacyHeaders: false,
// Skip rate limiting validation errors in production behind proxy
skip: (req) => {
// Skip rate limiting if we can't determine the real IP
return false;
},
keyGenerator: (req) => {
// Use X-Forwarded-For header if available, otherwise fall back to req.ip
return req.ip || 'unknown';
},
});
app.use('/api', limiter);
// Stricter rate limiting for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per windowMs
message: 'Too many authentication attempts, please try again later.',
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => {
return req.ip || 'unknown';
},
});
app.use('/api/auth', authLimiter);
// Body parsing middleware
app.use(compression());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Request logging
app.use(requestLogger);
// Routes
app.use('/api/health', healthRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/docket-entries', docketRoutes);
app.use('/api/documents', documentRoutes);
app.use('/api/subscriptions', subscriptionRoutes);
app.use('/api/notifications', notificationRoutes);
// 404 handler - Express 5 compatible
app.use((req, res) => {
res.status(404).json({
success: false,
message: 'Route not found',
});
});
// Error handling middleware (must be last)
app.use(errorHandler);
// Graceful shutdown
process.on('SIGTERM', async () => {
logger.info('SIGTERM received, shutting down gracefully');
// Close database connection
await prisma.$disconnect();
// Close Redis connection
await redis.quit();
process.exit(0);
});
process.on('SIGINT', async () => {
logger.info('SIGINT received, shutting down gracefully');
// Close database connection
await prisma.$disconnect();
// Close Redis connection
await redis.quit();
process.exit(0);
});
// Start server
app.listen(PORT, () => {
logger.info(`Server running on port ${PORT}`);
logger.info(`Environment: ${process.env.NODE_ENV || 'development'}`);
});
export default app;

View file

@ -1,62 +0,0 @@
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { prisma } from '../index';
export interface AuthenticatedRequest extends Request {
user?: {
userId: number;
username: string;
};
}
export const authenticateToken = async (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
success: false,
message: 'Access token required',
});
}
try {
const jwtSecret = process.env['JWT_SECRET'];
if (!jwtSecret) {
return res.status(500).json({
success: false,
message: 'Server configuration error',
});
}
const decoded = jwt.verify(token, jwtSecret) as any;
// Verify user still exists
const user = await prisma.adminUser.findUnique({
where: { id: decoded.userId },
});
if (!user) {
return res.status(401).json({
success: false,
message: 'Invalid token - user not found',
});
}
req.user = {
userId: decoded.userId,
username: decoded.username,
};
next();
} catch (error) {
return res.status(403).json({
success: false,
message: 'Invalid or expired token',
});
}
};

View file

@ -1,49 +0,0 @@
import { Request, Response, NextFunction } from 'express';
import { logger } from '../utils/logger';
export interface AppError extends Error {
statusCode?: number;
isOperational?: boolean;
}
export const errorHandler = (
error: AppError,
req: Request,
res: Response,
next: NextFunction
): void => {
const statusCode = error.statusCode || 500;
const message = error.message || 'Internal Server Error';
// Log error details
logger.error('Error occurred:', {
error: error.message,
stack: error.stack,
url: req.url,
method: req.method,
ip: req.ip,
userAgent: req.get('User-Agent'),
});
// Don't leak error details in production
const isDevelopment = process.env.NODE_ENV === 'development';
res.status(statusCode).json({
success: false,
message: isDevelopment ? message : 'Something went wrong',
...(isDevelopment && { stack: error.stack }),
});
};
export const createError = (message: string, statusCode: number = 500): AppError => {
const error: AppError = new Error(message);
error.statusCode = statusCode;
error.isOperational = true;
return error;
};
export const asyncHandler = (fn: Function) => {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};

View file

@ -1,32 +0,0 @@
import { Request, Response, NextFunction } from 'express';
import { logger } from '../utils/logger';
export const requestLogger = (req: Request, res: Response, next: NextFunction): void => {
const start = Date.now();
// Log request
logger.info('Incoming request', {
method: req.method,
url: req.url,
ip: req.ip,
userAgent: req.get('User-Agent'),
});
// Override res.end to log response
const originalEnd = res.end;
res.end = function(chunk?: any, encoding?: any): Response {
const duration = Date.now() - start;
logger.info('Request completed', {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`,
ip: req.ip,
});
return originalEnd.call(this, chunk, encoding);
} as any;
next();
};

View file

@ -1,108 +0,0 @@
import { Router, Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { prisma } from '../index';
import { asyncHandler } from '../middleware/errorHandler';
import { authenticateToken, AuthenticatedRequest } from '../middleware/auth';
import Joi from 'joi';
const router = Router();
// Validation schemas
const loginSchema = Joi.object({
username: Joi.string().required(),
password: Joi.string().required(),
});
// Login endpoint
router.post('/login', asyncHandler(async (req: Request, res: Response) => {
const { error, value } = loginSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
const { username, password } = value;
// Find admin user
const admin = await prisma.adminUser.findUnique({
where: { username },
});
if (!admin) {
return res.status(401).json({
success: false,
message: 'Invalid credentials',
});
}
// Verify password
const isValidPassword = await bcrypt.compare(password, admin.passwordHash);
if (!isValidPassword) {
return res.status(401).json({
success: false,
message: 'Invalid credentials',
});
}
// Update last login
await prisma.adminUser.update({
where: { id: admin.id },
data: { lastLogin: new Date() },
});
// Generate JWT token
const jwtSecret = process.env['JWT_SECRET'];
const jwtExpiresIn = process.env['JWT_EXPIRES_IN'] || '24h';
if (!jwtSecret) {
return res.status(500).json({
success: false,
message: 'Server configuration error',
});
}
const token = jwt.sign(
{
userId: admin.id,
username: admin.username
},
jwtSecret,
{
expiresIn: jwtExpiresIn
} as jwt.SignOptions
);
res.json({
success: true,
message: 'Login successful',
token,
user: {
id: admin.id,
username: admin.username,
},
});
}));
// Verify token endpoint
router.get('/verify', authenticateToken, asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
res.json({
success: true,
message: 'Token is valid',
user: req.user,
});
}));
// Logout endpoint (client-side token removal, but we can log it)
router.post('/logout', authenticateToken, asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
res.json({
success: true,
message: 'Logged out successfully',
});
}));
export default router;

View file

@ -1,228 +0,0 @@
import { Router, Request, Response } from 'express';
import { prisma } from '../index';
import { asyncHandler } from '../middleware/errorHandler';
import { authenticateToken, AuthenticatedRequest } from '../middleware/auth';
import { emailService } from '../services/emailService';
import Joi from 'joi';
const router = Router();
// Helper function to normalize date input to avoid timezone issues
function normalizeDate(dateInput: any): Date {
if (typeof dateInput === 'string') {
// If it's a string in YYYY-MM-DD format, create date in local time
const [year, month, day] = dateInput.split('-');
return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
}
if (dateInput instanceof Date) {
// If it's already a Date object, extract date parts and recreate to avoid timezone issues
const year = dateInput.getFullYear();
const month = dateInput.getMonth();
const day = dateInput.getDate();
return new Date(year, month, day);
}
return dateInput;
}
// Validation schemas
const createDocketEntrySchema = Joi.object({
date: Joi.date().required(),
title: Joi.string().required().max(500),
summary: Joi.string().required(),
notes: Joi.string().optional().allow(''),
});
const updateDocketEntrySchema = Joi.object({
date: Joi.date().optional(),
title: Joi.string().optional().max(500),
summary: Joi.string().optional(),
notes: Joi.string().optional().allow(''),
});
// Public endpoint - Get all docket entries with documents
router.get('/', asyncHandler(async (req: Request, res: Response) => {
const entries = await prisma.docketEntry.findMany({
include: {
documents: {
orderBy: {
displayOrder: 'asc',
},
},
},
orderBy: {
date: 'asc',
},
});
res.json({
success: true,
entries,
});
}));
// Admin endpoint - Get single docket entry
router.get('/:id', authenticateToken, asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
const entryId = parseInt(req.params.id);
if (isNaN(entryId)) {
return res.status(400).json({
success: false,
message: 'Invalid entry ID',
});
}
const entry = await prisma.docketEntry.findUnique({
where: { id: entryId },
include: {
documents: {
orderBy: {
displayOrder: 'asc',
},
},
},
});
if (!entry) {
return res.status(404).json({
success: false,
message: 'Docket entry not found',
});
}
res.json({
success: true,
entry,
});
}));
// Admin endpoint - Create new docket entry
router.post('/', authenticateToken, asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
const { error, value } = createDocketEntrySchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
// Normalize date to avoid timezone issues
const normalizedData = { ...value };
if (value.date) {
normalizedData.date = normalizeDate(value.date);
}
const entry = await prisma.docketEntry.create({
data: normalizedData,
include: {
documents: {
orderBy: {
displayOrder: 'asc',
},
},
},
});
// Email notifications are now manual-only via the green mail button
// Automatic notifications disabled per user request
res.status(201).json({
success: true,
message: 'Docket entry created successfully',
entry,
});
}));
// Admin endpoint - Update docket entry
router.put('/:id', authenticateToken, asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
const entryId = parseInt(req.params.id);
if (isNaN(entryId)) {
return res.status(400).json({
success: false,
message: 'Invalid entry ID',
});
}
const { error, value } = updateDocketEntrySchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
// Normalize date to avoid timezone issues
const normalizedData = { ...value };
if (value.date) {
normalizedData.date = normalizeDate(value.date);
}
try {
const entry = await prisma.docketEntry.update({
where: { id: entryId },
data: normalizedData,
include: {
documents: {
orderBy: {
displayOrder: 'asc',
},
},
},
});
res.json({
success: true,
message: 'Docket entry updated successfully',
entry,
});
} catch (error: any) {
if (error.code === 'P2025') {
return res.status(404).json({
success: false,
message: 'Docket entry not found',
});
}
throw error;
}
}));
// Admin endpoint - Delete docket entry
router.delete('/:id', authenticateToken, asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
const entryId = parseInt(req.params.id);
if (isNaN(entryId)) {
return res.status(400).json({
success: false,
message: 'Invalid entry ID',
});
}
try {
// Delete associated documents first (cascade should handle this, but being explicit)
await prisma.document.deleteMany({
where: { docketEntryId: entryId },
});
// Delete the docket entry
await prisma.docketEntry.delete({
where: { id: entryId },
});
res.json({
success: true,
message: 'Docket entry deleted successfully',
});
} catch (error: any) {
if (error.code === 'P2025') {
return res.status(404).json({
success: false,
message: 'Docket entry not found',
});
}
throw error;
}
}));
export default router;

View file

@ -1,276 +0,0 @@
import { Router, Request, Response } from 'express';
import { prisma } from '../index';
import { asyncHandler } from '../middleware/errorHandler';
import { authenticateToken, AuthenticatedRequest } from '../middleware/auth';
import multer from 'multer';
import { v4 as uuidv4 } from 'uuid';
import path from 'path';
import fs from 'fs';
import Joi from 'joi';
const router = Router();
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = process.env['UPLOAD_DIR'] || './uploads';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const uuid = uuidv4();
const extension = path.extname(file.originalname);
cb(null, `${uuid}${extension}`);
},
});
const upload = multer({
storage,
limits: {
fileSize: parseInt(process.env['MAX_FILE_SIZE'] || '262144000'), // 250MB default
},
fileFilter: (req, file, cb) => {
// Allow PDF files with multiple possible MIME types
const allowedMimeTypes = [
'application/pdf',
'application/x-pdf',
'application/acrobat',
'applications/vnd.pdf',
'text/pdf',
'text/x-pdf'
];
// Check MIME type or file extension
const isValidPDF = allowedMimeTypes.includes(file.mimetype) ||
file.originalname.toLowerCase().endsWith('.pdf');
// Log for debugging
console.log('Upload attempt:', {
mimetype: file.mimetype,
filename: file.originalname,
isValid: isValidPDF
});
if (isValidPDF) {
cb(null, true);
} else {
cb(new Error(`Only PDF files are allowed. Received: ${file.mimetype} for file: ${file.originalname}`));
}
},
});
// Validation schemas
const createDocumentSchema = Joi.object({
docketEntryId: Joi.number().integer().required(),
title: Joi.string().required().max(500),
summary: Joi.string().optional().allow(''),
notes: Joi.string().optional().allow(''),
displayOrder: Joi.number().integer().min(0).optional(),
});
const updateDocumentSchema = Joi.object({
title: Joi.string().optional().max(500),
summary: Joi.string().optional().allow(''),
notes: Joi.string().optional().allow(''),
displayOrder: Joi.number().integer().min(0).optional(),
});
// Public endpoint - Download document
router.get('/:id/download', asyncHandler(async (req: Request, res: Response) => {
const documentId = parseInt(req.params['id'] || '');
if (isNaN(documentId)) {
return res.status(400).json({
success: false,
message: 'Invalid document ID',
});
}
const document = await prisma.document.findUnique({
where: { id: documentId },
});
if (!document) {
return res.status(404).json({
success: false,
message: 'Document not found',
});
}
const filePath = document.filePath;
if (!fs.existsSync(filePath)) {
return res.status(404).json({
success: false,
message: 'File not found on server',
});
}
res.setHeader('Content-Type', document.mimeType);
res.setHeader('Content-Disposition', `inline; filename="${document.originalFilename}"`);
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
}));
// Admin endpoint - Upload document
router.post('/upload', authenticateToken, upload.single('file'), asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
if (!req.file) {
return res.status(400).json({
success: false,
message: 'No file uploaded',
});
}
const { error, value } = createDocumentSchema.validate(req.body);
if (error) {
// Clean up uploaded file if validation fails
fs.unlinkSync(req.file.path);
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
const { docketEntryId, title, summary, notes, displayOrder } = value;
// Verify docket entry exists
const docketEntry = await prisma.docketEntry.findUnique({
where: { id: docketEntryId },
});
if (!docketEntry) {
// Clean up uploaded file
fs.unlinkSync(req.file.path);
return res.status(404).json({
success: false,
message: 'Docket entry not found',
});
}
// Get next display order if not provided
let finalDisplayOrder = displayOrder;
if (finalDisplayOrder === undefined) {
const lastDocument = await prisma.document.findFirst({
where: { docketEntryId },
orderBy: { displayOrder: 'desc' },
});
finalDisplayOrder = lastDocument ? lastDocument.displayOrder + 1 : 0;
}
const document = await prisma.document.create({
data: {
docketEntryId,
title,
originalFilename: req.file.originalname,
storedFilename: req.file.filename,
filePath: req.file.path,
fileSize: req.file.size,
mimeType: req.file.mimetype,
summary: summary || '',
notes: notes || '',
displayOrder: finalDisplayOrder,
},
});
res.status(201).json({
success: true,
message: 'Document uploaded successfully',
document,
});
}));
// Admin endpoint - Update document metadata
router.put('/:id', authenticateToken, asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
const documentId = parseInt(req.params['id'] || '');
if (isNaN(documentId)) {
return res.status(400).json({
success: false,
message: 'Invalid document ID',
});
}
const { error, value } = updateDocumentSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
try {
const document = await prisma.document.update({
where: { id: documentId },
data: value,
});
res.json({
success: true,
message: 'Document updated successfully',
document,
});
} catch (error: any) {
if (error.code === 'P2025') {
return res.status(404).json({
success: false,
message: 'Document not found',
});
}
throw error;
}
}));
// Admin endpoint - Delete document
router.delete('/:id', authenticateToken, asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
const documentId = parseInt(req.params['id'] || '');
if (isNaN(documentId)) {
return res.status(400).json({
success: false,
message: 'Invalid document ID',
});
}
try {
const document = await prisma.document.findUnique({
where: { id: documentId },
});
if (!document) {
return res.status(404).json({
success: false,
message: 'Document not found',
});
}
// Delete the file from filesystem
if (fs.existsSync(document.filePath)) {
fs.unlinkSync(document.filePath);
}
// Delete from database
await prisma.document.delete({
where: { id: documentId },
});
res.json({
success: true,
message: 'Document deleted successfully',
});
} catch (error: any) {
if (error.code === 'P2025') {
return res.status(404).json({
success: false,
message: 'Document not found',
});
}
throw error;
}
}));
export default router;

View file

@ -1,37 +0,0 @@
import { Router, Request, Response } from 'express';
import { prisma } from '../index';
import { redis } from '../index';
const router = Router();
router.get('/', async (req: Request, res: Response) => {
try {
// Check database connection
await prisma.$queryRaw`SELECT 1`;
// Check Redis connection
await redis.ping();
res.json({
success: true,
message: 'API is healthy',
timestamp: new Date().toISOString(),
services: {
database: 'connected',
redis: 'connected',
},
});
} catch (error) {
res.status(503).json({
success: false,
message: 'Service unavailable',
timestamp: new Date().toISOString(),
services: {
database: 'disconnected',
redis: 'disconnected',
},
});
}
});
export default router;

View file

@ -1,59 +0,0 @@
import { Router, Response } from 'express';
import { authenticateToken, AuthenticatedRequest } from '../middleware/auth';
import { emailService } from '../services/emailService';
import { logger } from '../utils/logger';
const router = Router();
// Manual email notification endpoint
router.post('/send/:docketEntryId', authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
try {
const { docketEntryId } = req.params;
// Import prisma here to avoid circular dependency
const { prisma } = await import('../index');
// Get the docket entry with details
const docketEntry = await prisma.docketEntry.findUnique({
where: { id: parseInt(docketEntryId) },
include: {
documents: {
orderBy: { displayOrder: 'asc' }
}
}
});
if (!docketEntry) {
return res.status(404).json({
success: false,
message: 'Docket entry not found'
});
}
// Send the email notification using the BCC functionality
await emailService.notifySubscribersOfNewEntry(
docketEntry.title,
docketEntry.date.toISOString(),
docketEntry.summary
);
logger.info(`Manual email notification sent for docket entry: ${docketEntry.title}`, {
docketEntryId: docketEntry.id,
adminUser: req.user?.username
});
res.json({
success: true,
message: 'Email notification sent successfully to all subscribers'
});
} catch (error) {
logger.error('Failed to send manual email notification:', error);
res.status(500).json({
success: false,
message: 'Failed to send email notification'
});
}
});
export default router;

View file

@ -1,121 +0,0 @@
import { Router, Request, Response } from 'express';
import { prisma } from '../index';
import { asyncHandler } from '../middleware/errorHandler';
import { authenticateToken, AuthenticatedRequest } from '../middleware/auth';
import Joi from 'joi';
import { v4 as uuidv4 } from 'uuid';
const router = Router();
// Validation schemas
const subscribeSchema = Joi.object({
email: Joi.string().email().required(),
});
// Subscribe to notifications
router.post('/subscribe', asyncHandler(async (req: Request, res: Response) => {
const { error, value } = subscribeSchema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
message: error.details[0].message,
});
}
const { email } = value;
// Check if already subscribed
const existing = await prisma.subscription.findUnique({
where: { email },
});
if (existing) {
if (existing.isActive) {
return res.status(400).json({
success: false,
message: 'Email is already subscribed',
});
} else {
// Reactivate subscription
await prisma.subscription.update({
where: { email },
data: { isActive: true },
});
}
} else {
// Create new subscription
await prisma.subscription.create({
data: {
email,
unsubscribeToken: uuidv4(),
},
});
}
res.json({
success: true,
message: 'Successfully subscribed to notifications',
});
}));
// Admin endpoint - Get all subscribers
router.get('/', authenticateToken, asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
const subscribers = await prisma.subscription.findMany({
orderBy: { createdAt: 'desc' },
select: {
id: true,
email: true,
isActive: true,
createdAt: true,
},
});
const stats = {
total: subscribers.length,
active: subscribers.filter(sub => sub.isActive).length,
inactive: subscribers.filter(sub => !sub.isActive).length,
};
res.json({
success: true,
subscribers,
stats,
});
}));
// Admin endpoint - Toggle subscriber status
router.put('/:id/toggle', authenticateToken, asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
const subscriberId = parseInt(req.params.id);
if (isNaN(subscriberId)) {
return res.status(400).json({
success: false,
message: 'Invalid subscriber ID',
});
}
const subscriber = await prisma.subscription.findUnique({
where: { id: subscriberId },
});
if (!subscriber) {
return res.status(404).json({
success: false,
message: 'Subscriber not found',
});
}
const updated = await prisma.subscription.update({
where: { id: subscriberId },
data: { isActive: !subscriber.isActive },
});
res.json({
success: true,
message: `Subscriber ${updated.isActive ? 'activated' : 'deactivated'} successfully`,
subscriber: updated,
});
}));
export default router;

View file

@ -1,225 +0,0 @@
import { logger } from '../utils/logger';
import nodemailer from 'nodemailer';
interface EmailOptions {
to: string;
bcc?: string[];
subject: string;
html: string;
}
export class EmailService {
private static instance: EmailService;
private transporter: nodemailer.Transporter;
private constructor() {
// Initialize Gmail SMTP transporter
this.transporter = nodemailer.createTransport({
host: process.env['SMTP_HOST'] || 'smtp.gmail.com',
port: parseInt(process.env['SMTP_PORT'] || '587'),
secure: process.env['SMTP_SECURE'] === 'true', // false for 587, true for 465
auth: {
user: process.env['GOOGLE_EMAIL'],
pass: process.env['GOOGLE_APP_PASSWORD'],
},
});
// Verify SMTP connection
this.verifyConnection();
}
public static getInstance(): EmailService {
if (!EmailService.instance) {
EmailService.instance = new EmailService();
}
return EmailService.instance;
}
private async verifyConnection(): Promise<void> {
try {
await this.transporter.verify();
logger.info('SMTP connection verified successfully');
} catch (error) {
logger.error('SMTP connection failed:', error);
}
}
async sendEmail(options: EmailOptions): Promise<boolean> {
try {
const mailOptions: any = {
from: {
name: 'Court Docket System',
address: process.env['GOOGLE_EMAIL'] || 'system@deafgain.org',
},
to: options.to,
subject: options.subject,
html: options.html,
};
// Add BCC if provided
if (options.bcc && options.bcc.length > 0) {
mailOptions.bcc = options.bcc;
}
const result = await this.transporter.sendMail(mailOptions);
logger.info('Email sent successfully:', {
to: options.to,
bcc: options.bcc ? `${options.bcc.length} recipients` : 'none',
subject: options.subject,
messageId: result.messageId,
});
return true;
} catch (error) {
logger.error('Failed to send email:', error);
return false;
}
}
async notifySubscribersOfNewEntry(entryTitle: string, entryDate: string, entrySummary: string): Promise<void> {
try {
// Get all active subscribers
const { prisma } = await import('../index');
const subscribers = await prisma.subscription.findMany({
where: { isActive: true },
});
if (subscribers.length === 0) {
logger.info('No active subscribers to notify');
return;
}
const subject = `New Court Filing: ${entryTitle}`;
const html = this.generateNotificationEmail(entryTitle, entryDate, entrySummary);
const subscriberEmails = subscribers.map((subscriber: any) => subscriber.email);
// Send single email with all subscribers as BCC
const success = await this.sendEmail({
to: process.env['GOOGLE_EMAIL'] || 'system@deafgain.org',
bcc: subscriberEmails,
subject,
html,
});
if (success) {
logger.info(`Email notification sent successfully to ${subscriberEmails.length} subscribers via BCC`);
} else {
logger.error(`Failed to send BCC email notification to ${subscriberEmails.length} subscribers`);
}
} catch (error) {
logger.error('Failed to notify subscribers:', error);
}
}
private generateNotificationEmail(title: string, date: string, summary: string): string {
const formattedDate = new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>New Court Filing Notification</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
background-color: #1f2937;
color: white;
padding: 20px;
text-align: center;
border-radius: 8px 8px 0 0;
}
.content {
background-color: #f9fafb;
padding: 30px;
border-radius: 0 0 8px 8px;
border: 1px solid #e5e7eb;
}
.filing-info {
background-color: white;
padding: 20px;
border-radius: 6px;
margin: 20px 0;
border-left: 4px solid #3b82f6;
}
.date {
color: #6b7280;
font-size: 14px;
margin-bottom: 10px;
}
.title {
font-size: 18px;
font-weight: bold;
color: #1f2937;
margin-bottom: 15px;
}
.summary {
color: #4b5563;
line-height: 1.5;
}
.footer {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
font-size: 12px;
color: #6b7280;
text-align: center;
}
.button {
display: inline-block;
background-color: #3b82f6;
color: white;
padding: 12px 24px;
text-decoration: none;
border-radius: 6px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="header">
<h1>Eliza Kragh v. Montana Association of the Deaf</h1>
<p>New Court Filing Notification</p>
</div>
<div class="content">
<p>A new filing has been added to the court docket:</p>
<div class="filing-info">
<div class="date">${formattedDate}</div>
<div class="title">${title}</div>
<div class="summary">${summary}</div>
</div>
<p>You can view the complete docket and any associated documents by visiting the <a href="https://mad-lawsuit.org/" style="color: #3b82f6; text-decoration: none;">website</a>:</p>
<a href="https://mad-lawsuit.org/" class="button">
View Court Docket
</a>
<div class="footer">
<p>You are receiving this email because you subscribed to notifications for this case.</p>
<p>This is an automated notification from the court docket system.</p>
<p>To unsubscribe, please send an email to eliza.kragh@gmail.com</p>
</div>
</div>
</body>
</html>
`;
}
}
export const emailService = EmailService.getInstance();

View file

@ -1,38 +0,0 @@
import winston from 'winston';
const logFormat = winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
);
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: logFormat,
defaultMeta: { service: 'docket-api' },
transports: [
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5,
}),
new winston.transports.File({
filename: 'logs/combined.log',
maxsize: 5242880, // 5MB
maxFiles: 5,
}),
],
});
// If we're not in production, log to the console as well
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
export default logger;

View file

@ -1,41 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "CommonJS",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": false,
"declarationMap": false,
"sourceMap": true,
"removeComments": true,
"noImplicitAny": false,
"noImplicitReturns": false,
"noImplicitThis": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"exactOptionalPropertyTypes": false,
"noImplicitOverride": false,
"noPropertyAccessFromIndexSignature": false,
"noUncheckedIndexedAccess": false,
"allowUnusedLabels": true,
"allowUnreachableCode": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts"
]
}

23
bootstrap/app.php Normal file
View file

@ -0,0 +1,23 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
\App\Http\Middleware\HandleInertiaRequests::class,
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
]);
//
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();

2
bootstrap/cache/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

5
bootstrap/providers.php Normal file
View file

@ -0,0 +1,5 @@
<?php
return [
App\Providers\AppServiceProvider::class,
];

View file

@ -1,10 +1,82 @@
# Active Context - Current Work Status
## Current Task: PRODUCTION SERVER MIGRATION COMPLETED ✅
## Current Task: v2.0 LARAVEL + INERTIA + VUE FOUNDATION COMPLETE ✅
**MAD Lawsuit Website v2.0 - Complete Technology Stack Migration**:
- **v1.0 Status**: Fully operational at https://mad-lawsuit.org (Next.js + Express + React)
- **v2.0 Goal**: Rebuild with Laravel + Inertia + Vue + TypeScript
- **Current Phase**: Foundation complete, home page working, ready for features
- **Status**: TESTED LOCALLY - WEBSITE RENDERING CORRECTLY ✅
### v2.0 Technology Stack
**Backend**:
- **Framework**: Laravel 12.43.1 (latest stable)
- **PHP**: 8.3.28
- **Database**: PostgreSQL (will migrate from v1.0)
- **ORM**: Eloquent (replacing Prisma)
- **Auth**: Laravel Sanctum
- **API**: Inertia.js server-side
**Frontend**:
- **Framework**: Vue 3 + TypeScript
- **Routing**: Inertia.js (SSR-like experience)
- **Styling**: Tailwind CSS 3.x
- **Build Tool**: Vite 7.x
- **Components**: Vue SFC (Single File Components)
**Development Environment**:
- **PHP**: Installed via Homebrew (8.3.28)
- **Composer**: 2.9.2
- **Node**: Latest stable
- **NPM**: With legacy-peer-deps for Vite compatibility
### v2.0 Progress Checklist
- [x] Install PHP 8.3 and Composer locally
- [x] Create Laravel 12 project
- [x] Install Laravel Breeze with Vue + Inertia + TypeScript
- [x] Resolve NPM dependency conflicts (Vite 7 compatibility)
- [x] Install all NPM packages successfully
- [x] Create database migrations (docket_entries, documents, subscriptions, admin_users)
- [x] Create Eloquent models with relationships
- [x] Run migrations successfully (SQLite)
- [x] Create HomeController with Inertia
- [x] Build Vue home page component matching v1.0 design
- [x] Test locally with development servers (php artisan serve + npm run dev)
- [x] Verify website rendering correctly
- [ ] Implement email subscription API
- [ ] Add document download functionality
- [ ] Configure PostgreSQL connection (for production)
- [ ] Build admin authentication
- [ ] Implement admin dashboard (CRUD operations)
- [ ] Set up file storage for PDFs
- [ ] Implement email notifications
- [ ] Export v1.0 production data
- [ ] Import data into v2.0
- [ ] Deploy to v2.mad-lawsuit.org for testing
### v2.0 Design Specifications
**Complete v1.0 documentation captured**:
- ✅ Visual design (colors, typography, layout)
- ✅ Component specifications (forms, cards, buttons)
- ✅ Functional requirements (interactions, animations)
- ✅ Data structures (63 entries, 63 PDFs, 28 subscribers)
- ✅ Admin dashboard specs (complete CRUD operations)
- ✅ Production data verified from live database
**Reference Document**: `cline_docs/v1_design_specifications.md` (400+ lines)
### Next Immediate Steps
1. **Create Laravel Migrations** - Match v1.0 Prisma schema
2. **Configure .env** - PostgreSQL connection settings
3. **Build Vue Components** - Replicate v1.0 design exactly
4. **Implement Routes** - Public and admin routes
5. **Test Locally** - Verify all functionality works
## Previous Task: v1.0 PRODUCTION SERVER MIGRATION COMPLETED ✅
**MAD Lawsuit Website Successfully Migrated to New Infrastructure**:
- **Old Server**: chrishaulmark.com (DigitalOcean VPS - being decommissioned)
- **New Server**: 10.4.0.205 (Dedicated server behind NAT)
- **Status**: FULLY OPERATIONAL WITH ALL DATA MIGRATED ✅
- **v1.0 Tagged**: Git tag `v1.0` created and pushed to Gitea
### Migration Implementation Details
**Infrastructure Changes**:

View file

@ -0,0 +1,505 @@
# v1.0 Design Specifications - For v2.0 Replication
## Overview
This document captures the complete design and functionality of the v1.0 website (Next.js + Express) to guide the v2.0 Laravel + Inertia + Vue implementation.
## Visual Design
### Color Scheme
- **Primary Background**: Dark slate blue/gray (#3f4a5c, #4a5568)
- **Secondary Background**: Medium gray/brown (#6b6b6b, #7a7a7a)
- **Card Background**: White (#ffffff)
- **Text Primary**: Black (#000000)
- **Text Secondary**: Gray (#6b7280, #9ca3af)
- **Accent Green**: Light green for status badges (#d1fae5, #10b981)
- **Button Green**: Medium green (#6b9080, #5a8070)
- **Button Hover**: Darker green (#4a6050)
### Typography
- **Main Heading**: Large, bold, white text - "ELIZABETH KRAGH v. MONTANA ASSOCIATION OF THE DEAF"
- **Subheading**: Light gray text - "Court Docket & Legal Documents"
- **Section Headers**: Bold, centered - "Case Information", "Court Docket Entries"
- **Body Text**: Standard weight, readable size
- **Dates**: Small, gray text above entry titles
### Layout Structure
#### 1. Hero Section (Top)
```
┌─────────────────────────────────────────────────────────┐
│ Dark Blue Background │
│ │
│ ELIZABETH KRAGH │
│ v. │
│ MONTANA ASSOCIATION OF THE DEAF │
│ │
│ Court Docket & Legal Documents │
│ │
│ Stay Informed │
│ [Email Input] [Subscribe to Updates Button] │
│ │
└─────────────────────────────────────────────────────────┘
```
#### 2. Case Information Card
```
┌─────────────────────────────────────────────────────────┐
│ Case Information │
│ │
│ Case Title │
│ Elizabeth Kragh v. Montana Association of the Deaf │
│ │
│ Status │
│ [Active Litigation Badge] │
│ │
│ Last Updated │
│ November 12, 2025 │
│ │
└─────────────────────────────────────────────────────────┘
```
#### 3. Court Docket Entries Section
```
┌─────────────────────────────────────────────────────────┐
│ Court Docket Entries │
│ │
│ Chronological record of all court filings and │
│ proceedings │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ [#] May 7, 2025 [▼] │ │
│ │ Complaint Exhibits │ │
│ │ │ │
│ │ Brief Summary: │ │
│ │ The exhibits show the Montana Association... │ │
│ │ │ │
│ │ [View PDF Button] │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ [#] May 7, 2025 [▼] │ │
│ │ Affidavit in Support of Complaint... │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ... (more entries) │
│ │
└─────────────────────────────────────────────────────────┘
```
## Component Specifications
### 1. Email Subscription Form
- **Input Field**:
- Placeholder: "Enter your email address"
- White background
- Rounded corners
- Border styling
- **Button**:
- Text: "Subscribe to Updates"
- Green background (#6b9080)
- White text
- Icon: Mail/envelope icon
- Hover effect: Darker green
### 2. Case Information Card
- **Container**: White background, rounded corners, shadow
- **Labels**: Gray text, smaller font
- **Values**: Black text, larger font, bold
- **Status Badge**:
- Light green background (#d1fae5)
- Dark green text (#10b981)
- Rounded pill shape
- Text: "Active Litigation"
### 3. Docket Entry Cards
- **Container**: White background, rounded corners, shadow
- **Number Badge**:
- Dark circle with white number
- Positioned on left side
- **Date**: Small gray text at top
- **Title**: Bold black text, larger font
- **Expand/Collapse Icon**: Chevron down/up on right
- **Expanded State Shows**:
- "Brief Summary:" label
- Summary text in paragraph
- "View PDF" button (green, white text)
- **Hover Effect**: Slight shadow increase
### 4. View PDF Button
- **Style**: Green background, white text, rounded
- **Icon**: Document/file icon
- **Text**: "View PDF"
- **Action**: Opens PDF in new tab or viewer
## Functional Requirements
### 1. Email Subscription
- Input validation for email format
- Subscribe button sends POST request to `/api/subscriptions/subscribe`
- Success message displayed after subscription
- Error handling for duplicate emails
### 2. Docket Entry Expansion
- Click anywhere on entry card to expand/collapse
- Smooth animation for expand/collapse
- Only one entry expanded at a time (accordion behavior)
- Chevron icon rotates on expand/collapse
### 3. PDF Viewing
- "View PDF" button opens document
- PDFs served from `/api/documents/{id}/download`
- Opens in new browser tab
- Proper MIME type handling
### 4. Responsive Design
- Mobile-friendly layout
- Cards stack vertically on small screens
- Email form adjusts for mobile
- Touch-friendly tap targets
## Data Structure
### Docket Entry Object
```typescript
{
id: number
date: Date
title: string
summary: string
notes: string (optional)
documents: Document[]
createdAt: Date
updatedAt: Date
}
```
### Document Object
```typescript
{
id: number
docketEntryId: number
title: string
originalFilename: string
storedFilename: string
filePath: string
fileSize: number
mimeType: string
summary: string (optional)
notes: string (optional)
displayOrder: number
createdAt: Date
updatedAt: Date
}
```
### Subscription Object
```typescript
{
id: number
email: string
isActive: boolean
unsubscribeToken: string
createdAt: Date
}
```
## API Endpoints (for v2.0 Implementation)
### Public Endpoints
- `GET /api/docket-entries` - Fetch all docket entries with documents
- `POST /api/subscriptions/subscribe` - Subscribe to email updates
- `GET /api/documents/{id}/download` - Download/view PDF document
- `GET /api/health` - Health check
### Admin Endpoints (Protected)
- `POST /api/auth/login` - Admin login
- `GET /api/auth/verify` - Verify JWT token
- `GET /api/docket-entries/{id}` - Get single entry
- `POST /api/docket-entries` - Create new entry (triggers email)
- `PUT /api/docket-entries/{id}` - Update entry
- `DELETE /api/docket-entries/{id}` - Delete entry
- `POST /api/documents/upload` - Upload PDF document
## Animation & Interactions
### Hover Effects
- **Buttons**: Darken background color
- **Cards**: Increase shadow, slight lift effect
- **Links**: Underline or color change
### Transitions
- **Card Expansion**: 300ms ease-in-out
- **Button Hover**: 200ms ease
- **Page Load**: Fade in effect
### Loading States
- Spinner or skeleton screens while fetching data
- Disabled state for buttons during submission
## Footer
- **Text**: "Designed by DeafGain LLC"
- **Link**: http://deafgain.org
- **Style**:
- Dark background matching hero section
- Gray text with yellow link (#fbbf24)
- Centered alignment
- Border top separator
## Notes for v2.0 Implementation
### Key Differences to Maintain
1. **Same visual appearance** - colors, fonts, spacing
2. **Same user interactions** - expand/collapse, email subscription
3. **Same data structure** - compatible with v1.0 database export
4. **Same API contract** - frontend expects same response format
### Technology Mapping
- **Next.js Pages** → **Inertia.js Pages (Vue)**
- **React Components** → **Vue Components**
- **Express Routes** → **Laravel Routes**
- **Prisma ORM** → **Laravel Eloquent ORM**
- **JWT Auth** → **Laravel Sanctum/Passport**
- **Tailwind CSS****Tailwind CSS** (same)
### Priority Features for v2.0
1. ✅ Public docket viewing (exact same layout)
2. ✅ Email subscription (same functionality)
3. ✅ PDF document viewing (same behavior)
4. ✅ Admin authentication (Laravel-based)
5. ✅ Admin dashboard (CRUD operations)
6. ✅ Document upload (with validation)
7. ✅ Email notifications (on new entries)
## Current Production Data (Verified from Live Database)
### Database Statistics
- **63 docket entries** with dates from May 7, 2025 to November 12, 2025
- **63 PDF documents** (158MB total, 1 document per entry)
- **28 email subscribers** (27 active, 1 inactive)
- **1 admin user** with secure authentication
- **Active litigation status**
- **Last updated**: November 12, 2025
### Sample Docket Entries (Most Recent)
```
ID: 73 | Date: Nov 12, 2025
Title: Reply Brief in Support of Motion to Modify Scheduling Order
Summary: Plaintiff Elizabeth Kragh asks the judge to extend the October 31 deadline...
ID: 75 | Date: Nov 12, 2025
Title: Reply Brief in Support of Motion for Additional Discovery Time
Summary: Plaintiff Elizabeth Kragh responds to MAD's opposition, arguing the organization admits...
ID: 76 | Date: Nov 12, 2025
Title: Reply Brief in Support of Motion to Compel Discovery
Summary: Plaintiff Elizabeth Kragh responds to MAD's opposition, emphasizing that MAD admits...
ID: 72 | Date: Oct 29, 2025
Title: Order Granting Motion to Extend Time For Filing Reply Briefs
Summary: This is the court's electronic filing receipt confirming that Judge Tara Elliott granted...
ID: 71 | Date: Oct 28, 2025
Title: Motion to Extend Time for Filing Reply Briefs
Summary: This motion requests more time to file reply briefs in response to the Montana Association...
```
### Sample Documents
```
ID: 4 | Entry: 1 | Title: 05-07-25-affidavit | Size: 2.8MB
ID: 5 | Entry: 5 | Title: 05-07-25-complaint | Size: 12MB
ID: 6 | Entry: 6 | Title: 05-07-25-exhibits-complaint | Size: 40MB
ID: 8 | Entry: 8 | Title: 05-16-25-amended-cert-of-service | Size: 848KB
ID: 9 | Entry: 9 | Title: 05-14-25-cert-service | Size: 823KB
```
### Email Subscribers (28 total)
- **Most recent**: quarks.tattoo-09@icloud.com (Nov 19, 2025)
- **Includes**: eliza.kragh@gmail.com, chris@sigd.net, and 26 other supporters
- **Active**: 27 subscribers receiving notifications
- **Inactive**: 1 subscriber (mike.crago@gmail.com)
### File Storage
- **Location**: `/docker/websites/mad-lawsuit/uploads/`
- **Naming**: UUID-based (e.g., `004f5cd2-d600-4125-86da-bca491183fcb.pdf`)
- **Total size**: 158MB across 63 PDF files
- **File sizes**: Range from 441KB to 40MB
- **Format**: All application/pdf MIME type
## Admin Dashboard (v1.0 Implementation)
### Admin Authentication
- **Route**: `/admin`
- **Login Form**:
- Username input field
- Password input field (masked)
- "Login" button
- Error message display for failed attempts
- **Authentication**: JWT-based with bcrypt password hashing
- **Session**: Token stored in localStorage/cookies
- **Protected Routes**: All `/admin/*` routes require valid JWT
### Admin Dashboard Layout
**Route**: `/admin/dashboard`
```
┌─────────────────────────────────────────────────────────┐
│ Header: "Admin Dashboard" | [Logout Button] │
├─────────────────────────────────────────────────────────┤
│ │
│ Navigation Tabs: │
│ [Docket Entries] [Upload Document] [Subscribers] │
│ │
├─────────────────────────────────────────────────────────┤
│ │
│ Docket Entries Management: │
│ │
│ [+ Create New Entry Button] │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Entry #73 | Nov 12, 2025 │ │
│ │ Reply Brief in Support of Motion... │ │
│ │ [Edit] [Delete] [View] │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Entry #75 | Nov 12, 2025 │ │
│ │ Reply Brief in Support of Motion... │ │
│ │ [Edit] [Delete] [View] │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ... (pagination for all 63 entries) │
│ │
└─────────────────────────────────────────────────────────┘
```
### Create/Edit Docket Entry Form
**Fields**:
- **Date**: Date picker (required)
- **Title**: Text input, max 500 characters (required)
- **Summary**: Textarea, rich text (required)
- **Notes**: Textarea, optional
- **Document Upload**: File input (PDF only, max 10MB)
- Shows file name after selection
- Validates file type and size
- Displays upload progress
- **Actions**:
- [Save Entry] button (green)
- [Cancel] button (gray)
- [Delete Entry] button (red, edit mode only)
**Behavior**:
- Creating new entry triggers email notification to all active subscribers
- Editing entry does NOT trigger email (only new entries)
- Deleting entry also deletes associated document file
- Form validation before submission
- Success/error messages displayed
### Upload Document Page
**Route**: `/admin/upload`
```
┌─────────────────────────────────────────────────────────┐
│ Upload Document to Existing Entry │
│ │
│ Select Docket Entry: │
│ [Dropdown: All entries with date and title] │
│ │
│ Document Title: │
│ [Text input] │
│ │
│ Choose PDF File: │
│ [File input] [Browse...] │
│ │
│ Summary (optional): │
│ [Textarea] │
│ │
│ Notes (optional): │
│ [Textarea] │
│ │
│ [Upload Document Button] │
│ │
└─────────────────────────────────────────────────────────┘
```
**Validation**:
- PDF files only (application/pdf MIME type)
- Maximum file size: 10MB
- Required: docket_entry_id, title, file
- Optional: summary, notes
### Subscribers Management Page
**Route**: `/admin/subscribers`
```
┌─────────────────────────────────────────────────────────┐
│ Email Subscribers (28 total, 27 active) │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ quarks.tattoo-09@icloud.com │ │
│ │ Subscribed: Nov 19, 2025 | Status: Active │ │
│ │ [Deactivate] │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ letsgetonacid222@gmail.com │ │
│ │ Subscribed: Oct 28, 2025 | Status: Active │ │
│ │ [Deactivate] │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ mike.crago@gmail.com │ │
│ │ Subscribed: Jul 14, 2025 | Status: Inactive │ │
│ │ [Activate] │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ... (all 28 subscribers listed) │
│ │
└─────────────────────────────────────────────────────────┘
```
**Features**:
- View all subscribers with email, subscription date, and status
- Toggle active/inactive status
- Search/filter subscribers
- Export subscriber list (optional)
- No delete function (preserve data integrity)
### Admin Dashboard Features Summary
1. **Authentication**: Secure JWT-based login
2. **Docket Management**: Full CRUD operations on entries
3. **Document Upload**: PDF upload with validation
4. **Subscriber Management**: View and manage email list
5. **Email Notifications**: Automatic on new entry creation
6. **Responsive Design**: Works on desktop and tablet
7. **Error Handling**: Comprehensive validation and error messages
8. **Logout**: Clear session and redirect to login
## Data Export Strategy for v2.0 Migration
### Database Export
```bash
# Export all tables as SQL dump
ssh chaulmark@10.4.0.205
cd ~/websites/mad-lawsuit.org
docker compose exec postgres pg_dump -U docket_user -d docket_db > mad-lawsuit-v1-export.sql
# Or export as JSON for Laravel seeders
docker compose exec postgres psql -U docket_user -d docket_db -c "COPY (SELECT * FROM docket_entries) TO STDOUT WITH CSV HEADER" > docket_entries.csv
docker compose exec postgres psql -U docket_user -d docket_db -c "COPY (SELECT * FROM documents) TO STDOUT WITH CSV HEADER" > documents.csv
docker compose exec postgres psql -U docket_user -d docket_db -c "COPY (SELECT * FROM subscriptions) TO STDOUT WITH CSV HEADER" > subscriptions.csv
```
### File Export
```bash
# Copy all PDF files
rsync -avz chaulmark@10.4.0.205:/docker/websites/mad-lawsuit/uploads/ ./v1-uploads/
# Total: 158MB, 63 files
```
### Import into v2.0 Laravel
1. **Database**: Create Laravel migrations matching v1.0 schema
2. **Seeders**: Import CSV data into Laravel database
3. **Storage**: Copy PDFs to Laravel storage/app/public/documents/
4. **Mapping**: Update file_path in documents table to Laravel paths
This specification ensures v2.0 will be a pixel-perfect, functionally identical replacement for v1.0, just built with Laravel + Inertia + Vue instead of Next.js + Express.

90
composer.json Normal file
View file

@ -0,0 +1,90 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"inertiajs/inertia-laravel": "^2.0",
"laravel/framework": "^12.0",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.10.1",
"tightenco/ziggy": "^2.0"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/breeze": "^2.3",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpunit/phpunit": "^11.5.3"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

126
config/app.php Normal file
View file

@ -0,0 +1,126 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application, which will be used when the
| framework needs to place the application's name in a notification or
| other UI elements where an application name needs to be displayed.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| the application so that it's available within Artisan commands.
|
*/
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. The timezone
| is set to "UTC" by default as it is suitable for most use cases.
|
*/
'timezone' => 'UTC',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by Laravel's translation / localization methods. This option can be
| set to any locale for which you plan to have translation strings.
|
*/
'locale' => env('APP_LOCALE', 'en'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string to ensure that all encrypted values
| are secure. You should do this prior to deploying the application.
|
*/
'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
...array_filter(
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
),
],
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
];

115
config/auth.php Normal file
View file

@ -0,0 +1,115 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the number of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
];

117
config/cache.php Normal file
View file

@ -0,0 +1,117 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
|
*/
'default' => env('CACHE_STORE', 'database'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane",
| "failover", "null"
|
*/
'stores' => [
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
'failover' => [
'driver' => 'failover',
'stores' => [
'database',
'array',
],
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
| stores, there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
];

183
config/database.php Normal file
View file

@ -0,0 +1,183 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
'transaction_mode' => 'DEFERRED',
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
|
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
'persistent' => env('REDIS_PERSISTENT', false),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
],
];

80
config/filesystems.php Normal file
View file

@ -0,0 +1,80 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
|
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Below you may configure as many filesystem disks as necessary, and you
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true,
'throw' => false,
'report' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
'report' => false,
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

132
config/logging.php Normal file
View file

@ -0,0 +1,132 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that is utilized to write
| messages to your logs. The value provided here should match one of
| the channels present in the list of "channels" configured below.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Laravel
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
|
| Available drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],
'formatter' => env('LOG_STDERR_FORMATTER'),
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

118
config/mail.php Normal file
View file

@ -0,0 +1,118 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
|
*/
'default' => env('MAIL_MAILER', 'log'),
/*
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "resend", "log", "array",
| "failover", "roundrobin"
|
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
'retry_after' => 60,
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all emails sent by your application to be sent from
| the same address. Here you may specify a name and address that is
| used globally for all emails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
];

129
config/queue.php Normal file
View file

@ -0,0 +1,129 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
|
*/
'default' => env('QUEUE_CONNECTION', 'database'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection options for every queue backend
| used by your application. An example configuration is provided for
| each backend supported by Laravel. You're also free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
| "deferred", "background", "failover", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
'deferred' => [
'driver' => 'deferred',
],
'background' => [
'driver' => 'background',
],
'failover' => [
'driver' => 'failover',
'connections' => [
'database',
'deferred',
],
],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control how and where failed jobs are stored. Laravel ships with
| support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs',
],
];

38
config/services.php Normal file
View file

@ -0,0 +1,38 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],
'resend' => [
'key' => env('RESEND_API_KEY'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
];

217
config/session.php Normal file
View file

@ -0,0 +1,217 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
|
| Supported: "file", "cookie", "database", "memcached",
| "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
|
*/
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you may use the session like normal.
|
*/
'encrypt' => env('SESSION_ENCRYPT', false),
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When utilizing the "file" session driver, the session files are placed
| on disk. The default storage location is defined here; however, you
| are free to provide another location where they should be stored.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table to
| be used to store sessions. Of course, a sensible default is defined
| for you; however, you're welcome to change this to another table.
|
*/
'table' => env('SESSION_TABLE', 'sessions'),
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using one of the framework's cache driven session backends, you may
| define the cache store which should be used to store the session data
| between requests. This must match one of your defined cache stores.
|
| Affects: "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the session cookie that is created by
| the framework. Typically, you should not need to change this value
| since doing so does not grant a meaningful security improvement.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug((string) env('APP_NAME', 'laravel')).'-session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application, but you're free to change this when necessary.
|
*/
'path' => env('SESSION_PATH', '/'),
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| This value determines the domain and subdomains the session cookie is
| available to. By default, the cookie will be available to the root
| domain without subdomains. Typically, this shouldn't be changed.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. It's unlikely you should disable this option.
|
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" to permit secure cross-site requests.
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
];

1
database/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.sqlite*

View file

@ -0,0 +1,44 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}

View file

@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View file

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};

Some files were not shown because too many files have changed in this diff Show more