feat: improve build stage

This commit is contained in:
Benjamin
2025-10-26 22:44:30 +01:00
parent 714f122cfa
commit 77018a975d
7 changed files with 2985 additions and 5 deletions

684
docs/en/admin-guide.md Normal file
View File

@@ -0,0 +1,684 @@
# Admin Guide
Complete guide for administrators using Ackify to manage documents, expected signers, and email reminders.
## Table of Contents
- [Getting Admin Access](#getting-admin-access)
- [Admin Dashboard](#admin-dashboard)
- [Document Management](#document-management)
- [Expected Signers](#expected-signers)
- [Email Reminders](#email-reminders)
- [Monitoring & Statistics](#monitoring--statistics)
- [Best Practices](#best-practices)
- [Troubleshooting](#troubleshooting)
---
## Getting Admin Access
### Prerequisites
To access admin features, your email must be configured in the `ACKIFY_ADMIN_EMAILS` environment variable.
```bash
# In .env file
ACKIFY_ADMIN_EMAILS=admin@company.com,manager@company.com
```
**After adding your email:**
1. Restart Ackify: `docker compose restart ackify-ce`
2. Log out and log in again
3. You should now see "Admin" link in the navigation
### Verify Admin Access
Visit `/admin` - if you see the admin dashboard, you have admin access.
---
## Admin Dashboard
**URL**: `/admin`
The admin dashboard provides:
- **Total Documents**: Number of documents in the system
- **Expected Readers**: Total number of expected signers across all documents
- **Active Documents**: Documents that are not soft-deleted
- **Document List**: Paginated list (20 per page) with search
### Dashboard Features
#### Quick Stats
Three KPI cards at the top:
- Total documents count
- Total expected readers/signers
- Active documents (non-deleted)
#### Document Search
- Search by title, document ID, or URL
- Real-time filtering
#### Document List
**Desktop view** - Table with columns:
- Document ID
- Title
- URL
- Created date
- Creator
- Actions (View details)
**Mobile view** - Card layout with:
- Document ID and title
- Creation info
- Tap to view details
#### Pagination
- 20 documents per page
- Previous/Next buttons
- Current page indicator
---
## Document Management
### Creating a Document
**From Admin Dashboard:**
1. Click "Create New Document" button
2. Fill in the form:
- **Reference** (required): URL, file path, or custom ID
- **Title** (optional): Auto-generated from URL if empty
- **Description** (optional): Additional context
3. Click "Create Document"
**Automatic Features:**
- **Unique ID Generation**: Collision-resistant base36 doc_id
- **Title Extraction**: Auto-extracts from URL if not provided
- **Checksum Calculation**: For remote URLs (if admin and file < 10MB)
**Example:**
```
Reference: https://docs.company.com/policy-2025.pdf
Title: Security Policy 2025 (auto-extracted or manual)
Description: Annual security compliance policy
```
**Result:**
- doc_id: `k7m2n4p8` (auto-generated)
- Checksum: Auto-calculated SHA-256 (if URL is accessible)
### Viewing Document Details
**URL**: `/admin/docs/{docId}`
Provides comprehensive document information:
#### 1. **Metadata Section**
Edit document information:
- Title
- URL
- Description
- Checksum (SHA-256, SHA-512, or MD5)
- Checksum Algorithm
**To edit:**
1. Click "Edit Metadata" button
2. Modify fields
3. Click "Save Changes"
4. Confirmation modal for critical changes (checksum, algorithm)
#### 2. **Statistics Panel**
Real-time signature tracking:
- **Expected**: Number of expected signers
- **Signed**: Number who have signed
- **Pending**: Not yet signed
- **Completion**: Percentage complete
#### 3. **Expected Signers Section**
Lists all expected signers with status:
- **Email**: Signer's email address
- **Status**: ✅ Signed or ⏳ Pending
- **Added**: Date when added to expected list
- **Days Since Added**: Time tracking
- **Last Reminder**: When last reminder was sent
- **Reminder Count**: Total reminders sent
- **Actions**: Remove signer button
**Color coding:**
- Green background: Signer has signed
- Default background: Pending signature
#### 4. **Unexpected Signatures**
Shows users who signed but weren't on expected list:
- User email
- Signed date
- Indicates organic/unexpected participation
#### 5. **Actions**
- **Send Reminders**: Email pending signers
- **Share Link**: Generate and copy signing link
- **Delete Document**: Soft delete (preserves signature history)
### Updating Document Metadata
**Important fields:**
**Title & Description:**
- Can be changed freely
- No confirmation required
**URL:**
- Updates where document is located
- Confirmation modal shown
**Checksum & Algorithm:**
- Critical for integrity verification
- Confirmation modal warns of impact
- Change only if document version changed
**Workflow:**
1. Click "Edit Metadata"
2. Modify desired fields
3. Click "Save Changes"
4. If checksum/algorithm changed, confirm in modal
5. Success notification displayed
### Deleting a Document
**Soft Delete Behavior:**
- Document marked as deleted (`deleted_at` timestamp set)
- Signature history preserved
- Document no longer appears in public lists
- Admin can still view via direct URL
- Signatures CASCADE update (marked with `doc_deleted_at`)
**To delete:**
1. Go to document detail page (`/admin/docs/{docId}`)
2. Click "Delete Document" button
3. Confirm deletion in modal
4. Document moved to deleted state
**Note**: There is no "undelete" - this is permanent soft delete.
---
## Expected Signers
Expected signers are users you want to track for document completion.
### Adding Expected Signers
**From document detail page:**
1. Scroll to "Expected Signers" section
2. Click "Add Expected Signer" button
3. Enter email address(es):
- Single: `alice@company.com`
- Multiple: Comma-separated `alice@company.com,bob@company.com`
4. Optionally add notes
5. Click "Add"
**API endpoint:**
```http
POST /api/v1/admin/documents/{docId}/signers
Content-Type: application/json
X-CSRF-Token: {token}
{
"emails": ["alice@company.com", "bob@company.com"],
"notes": "Board members - Q1 2025"
}
```
**Constraints:**
- Email must be valid format
- UNIQUE constraint: Cannot add same email twice to same document
- Added by current admin user (tracked in `added_by`)
### Removing Expected Signers
**From document detail page:**
1. Find signer in Expected Signers list
2. Click "Remove" button next to their email
3. Confirm removal
**API endpoint:**
```http
DELETE /api/v1/admin/documents/{docId}/signers/{email}
X-CSRF-Token: {token}
```
**Effect:**
- Signer removed from expected list
- Does NOT delete their signature if they already signed
- Reminder history preserved in `reminder_logs`
### Tracking Completion Status
**Document Status API:**
```http
GET /api/v1/admin/documents/{docId}/status
```
**Response:**
```json
{
"docId": "abc123",
"expectedCount": 10,
"signedCount": 7,
"pendingCount": 3,
"completionPercentage": 70.0
}
```
**Visual indicators:**
- Progress bar showing completion percentage
- Color-coded status: Green (signed), Orange (pending)
- Days since added (helps identify slow signers)
---
## Email Reminders
Email reminders are sent asynchronously via the `email_queue` system.
### Sending Reminders
**From document detail page:**
1. Click "Send Reminders" button
2. Modal opens with options:
- **Send to**: All pending OR specific emails
- **Document URL**: Pre-filled, can customize
- **Language**: en, fr, es, de, it
3. Click "Send Reminders"
4. Confirmation: "X reminders queued for sending"
**API endpoint:**
```http
POST /api/v1/admin/documents/{docId}/reminders
Content-Type: application/json
X-CSRF-Token: {token}
{
"emails": ["alice@company.com"], // Optional: specific emails
"docURL": "https://docs.company.com/policy.pdf",
"locale": "en"
}
```
**Behavior:**
- Sends to ALL pending signers if `emails` not specified
- Sends to specific `emails` if provided (even if already signed)
- Emails queued in `email_queue` table
- Background worker processes queue
- Retry on failure (3 attempts, exponential backoff)
### Email Templates
**Location**: `backend/templates/emails/`
**Available templates:**
- `reminder.html` - HTML version
- `reminder.txt` - Plain text version
**Variables available in templates:**
- `{{.DocTitle}}` - Document title
- `{{.DocURL}}` - Document URL
- `{{.RecipientEmail}}` - Recipient's email
- `{{.SenderName}}` - Admin who sent reminder
- `{{.OrganisationName}}` - From ACKIFY_ORGANISATION
**Locales**: en, fr, es, de, it
- Template directory: `templates/emails/{locale}/`
- Fallback to default locale if translation missing
### Reminder History
**View reminder log:**
```http
GET /api/v1/admin/documents/{docId}/reminders
```
**Response:**
```json
{
"reminders": [
{
"id": 123,
"docId": "abc123",
"recipientEmail": "alice@company.com",
"sentAt": "2025-01-15T10:30:00Z",
"sentBy": "admin@company.com",
"templateUsed": "reminder",
"status": "sent",
"errorMessage": null
}
]
}
```
**Status values:**
- `queued` - In email_queue, not yet processed
- `sent` - Successfully delivered
- `failed` - Delivery failed (check errorMessage)
- `bounced` - Email bounced back
**Tracking:**
- Last reminder sent date shown per signer
- Reminder count shown per signer
- Helps avoid over-sending
### Email Queue Monitoring
**Check queue status (PostgreSQL):**
```sql
-- Pending emails
SELECT id, to_addresses, subject, status, scheduled_for
FROM email_queue
WHERE status IN ('pending', 'processing')
ORDER BY priority DESC, scheduled_for ASC;
-- Failed emails
SELECT id, to_addresses, last_error, retry_count
FROM email_queue
WHERE status = 'failed';
```
**Worker configuration:**
- Batch size: 10 emails
- Poll interval: 5 seconds
- Max retries: 3
- Cleanup: 7 days retention
---
## Monitoring & Statistics
### Document-Level Statistics
**Completion tracking:**
- Expected vs Signed counts
- Pending signer list
- Completion percentage
- Average time to sign
**Reminder effectiveness:**
- Reminders sent count
- Success/failure rates
- Time between reminder and signature
### System-Wide Metrics
**PostgreSQL queries:**
```sql
-- Total documents
SELECT COUNT(*) FROM documents WHERE deleted_at IS NULL;
-- Total signatures
SELECT COUNT(*) FROM signatures;
-- Documents by completion status
SELECT
CASE
WHEN signed_count = expected_count THEN '100%'
WHEN signed_count >= expected_count * 0.75 THEN '75-99%'
WHEN signed_count >= expected_count * 0.50 THEN '50-74%'
ELSE '<50%'
END as completion_bracket,
COUNT(*) as doc_count
FROM (
SELECT
d.doc_id,
COUNT(DISTINCT es.email) as expected_count,
COUNT(DISTINCT s.user_email) as signed_count
FROM documents d
LEFT JOIN expected_signers es ON d.doc_id = es.doc_id
LEFT JOIN signatures s ON d.doc_id = s.doc_id AND s.user_email = es.email
WHERE d.deleted_at IS NULL
GROUP BY d.doc_id
) stats
GROUP BY completion_bracket;
-- Email queue statistics
SELECT status, COUNT(*), MIN(created_at), MAX(created_at)
FROM email_queue
GROUP BY status;
```
### Export Data
**Signatures for a document:**
```sql
COPY (
SELECT s.user_email, s.user_name, s.signed_at, s.payload_hash
FROM signatures s
WHERE s.doc_id = 'your_doc_id'
ORDER BY s.signed_at
) TO '/tmp/signatures_export.csv' WITH CSV HEADER;
```
**Expected signers status:**
```sql
COPY (
SELECT
es.email,
CASE WHEN s.id IS NOT NULL THEN 'Signed' ELSE 'Pending' END as status,
es.added_at,
s.signed_at
FROM expected_signers es
LEFT JOIN signatures s ON es.doc_id = s.doc_id AND es.email = s.user_email
WHERE es.doc_id = 'your_doc_id'
) TO '/tmp/expected_signers_export.csv' WITH CSV HEADER;
```
---
## Best Practices
### 1. Document Creation
**Do:**
- Use descriptive titles
- Add clear descriptions
- Include document URL for easy access
- Store checksum for integrity verification
- Create expected signers list before sharing
**Don't:**
- Use generic titles like "Document 1"
- Leave URL empty if document is accessible online
- Change checksums unless document actually changed
### 2. Expected Signers Management
**Do:**
- Add expected signers before sending document link
- Use clear notes to explain why signers are expected
- Review pending signers regularly
- Remove signers who are no longer relevant
**Don't:**
- Add hundreds of signers at once (use batches)
- Send reminders too frequently (max once per week)
- Remove signers who have already signed (preserve history)
### 3. Email Reminders
**Do:**
- Wait 3-5 days before first reminder
- Send in recipient's preferred language
- Include clear document title and URL
- Track reminder history to avoid spam
- Send reminders during business hours
**Don't:**
- Send daily reminders (causes fatigue)
- Send without checking if already signed
- Use generic subjects (personalize with doc title)
- Send outside business hours
### 4. Data Integrity
**Do:**
- Regularly backup PostgreSQL database
- Verify checksums match actual documents
- Monitor email queue for failures
- Review unexpected signatures (may indicate broader interest)
- Export important signature data
**Don't:**
- Delete documents with active signatures
- Modify timestamps manually in database
- Ignore failed email deliveries
- Change checksums without updating the document
### 5. Security
**Do:**
- Limit admin access to trusted users only
- Use HTTPS in production (`ACKIFY_BASE_URL=https://...`)
- Rotate `ACKIFY_OAUTH_COOKIE_SECRET` periodically
- Monitor admin actions via application logs
- Use OAuth allowed domain restrictions
**Don't:**
- Share admin credentials
- Run without HTTPS in production
- Disable CSRF protection
- Ignore authentication failures in logs
---
## Troubleshooting
### Common Issues
#### 1. Admin Link Not Visible
**Problem**: Can't see "Admin" link in navigation
**Solutions:**
- Verify email in `ACKIFY_ADMIN_EMAILS` environment variable
- Restart Ackify: `docker compose restart ackify-ce`
- Log out and log back in
- Check logs: `docker compose logs ackify-ce | grep admin`
#### 2. Emails Not Sending
**Problem**: Reminders queued but not delivered
**Diagnosis:**
```sql
SELECT * FROM email_queue WHERE status = 'failed' ORDER BY created_at DESC LIMIT 10;
```
**Solutions:**
- Check SMTP configuration (`ACKIFY_MAIL_HOST`, `ACKIFY_MAIL_USERNAME`, etc.)
- Verify SMTP credentials are correct
- Check email worker logs: `docker compose logs ackify-ce | grep email`
- Ensure `ACKIFY_MAIL_FROM` is valid sender address
- Test SMTP connection manually
#### 3. Duplicate Signer Error
**Problem**: "Email already exists as expected signer"
**Cause**: UNIQUE constraint on (doc_id, email)
**Solution**: This is expected behavior - each email can only be added once per document
#### 4. Checksum Mismatch
**Problem**: Users report checksum doesn't match
**Solutions:**
- Verify stored checksum matches actual document
- Check algorithm used (SHA-256, SHA-512, MD5)
- Recalculate checksum and update via Edit Metadata
- Ensure users are downloading correct version
#### 5. Document Not Appearing
**Problem**: Created document doesn't show in list
**Solutions:**
- Check if document was soft-deleted (`deleted_at IS NOT NULL`)
- Verify creation succeeded (check response/logs)
- Clear browser cache
- Check database: `SELECT * FROM documents WHERE doc_id = 'your_id';`
#### 6. Signature Already Exists
**Problem**: User can't sign document again
**Cause**: UNIQUE constraint (doc_id, user_sub) - one signature per user per document
**Solution**: This is expected - users cannot sign the same document twice
### Getting Help
**Logs:**
```bash
# Application logs
docker compose logs -f ackify-ce
# Database logs
docker compose logs -f ackify-db
# Email worker logs (grep email)
docker compose logs ackify-ce | grep -i email
```
**Database inspection:**
```bash
# Connect to PostgreSQL
docker compose exec ackify-db psql -U ackifyr ackify
# Useful queries
SELECT * FROM documents ORDER BY created_at DESC LIMIT 10;
SELECT * FROM expected_signers WHERE doc_id = 'your_doc_id';
SELECT * FROM email_queue WHERE status != 'sent' ORDER BY created_at DESC;
```
**Report issues:**
- GitHub: https://github.com/btouchard/ackify-ce/issues
- Include logs and error messages
- Describe expected vs actual behavior
---
## Quick Reference
### Environment Variables
```bash
ACKIFY_ADMIN_EMAILS=admin@company.com
ACKIFY_MAIL_HOST=smtp.gmail.com
ACKIFY_MAIL_FROM=noreply@company.com
```
### Key Endpoints
```
GET /admin # Dashboard
GET /admin/docs/{docId} # Document detail
POST /admin/documents/{docId}/signers # Add signer
POST /admin/documents/{docId}/reminders # Send reminders
PUT /admin/documents/{docId}/metadata # Update metadata
```
### Important Tables
- `documents` - Document metadata
- `signatures` - User signatures
- `expected_signers` - Who should sign
- `reminder_logs` - Email history
- `email_queue` - Async email queue
### Keyboard Shortcuts (Frontend)
- Search bar auto-focus on dashboard
- Enter to submit forms
- Esc to close modals
---
**Last Updated**: 2025-10-26
**Version**: 1.0.0

355
docs/en/architecture.md Normal file
View File

@@ -0,0 +1,355 @@
# Architecture
Technical stack and design principles of Ackify.
## Overview
Ackify is a **modern monolithic application** with clear backend/frontend separation.
```
┌─────────────────────────────────────────┐
│ Client Browser │
│ (Vue.js 3 SPA + TypeScript) │
└──────────────┬──────────────────────────┘
│ HTTPS / JSON
┌──────────────▼──────────────────────────┐
│ Go Backend (API-first) │
│ ├─ RESTful API v1 (chi router) │
│ ├─ OAuth2 Service │
│ ├─ Ed25519 Crypto │
│ └─ SMTP Email (optional) │
└──────────────┬──────────────────────────┘
│ PostgreSQL protocol
┌──────────────▼──────────────────────────┐
│ PostgreSQL 16 Database │
│ (Signatures + Metadata + Sessions) │
└─────────────────────────────────────────┘
```
## Backend (Go)
### Simplified Clean Architecture
```
backend/
├── cmd/
│ ├── community/ # Entry point + dependency injection
│ └── migrate/ # SQL migrations tool
├── internal/
│ ├── domain/
│ │ └── models/ # Business entities (User, Signature, Document)
│ ├── application/
│ │ └── services/ # Business logic (SignatureService, etc.)
│ ├── infrastructure/
│ │ ├── auth/ # OAuth2 service
│ │ ├── database/ # PostgreSQL repositories
│ │ ├── email/ # SMTP service
│ │ ├── config/ # Environment variables
│ │ └── i18n/ # Backend i18n
│ └── presentation/
│ ├── api/ # HTTP handlers API v1
│ └── handlers/ # Legacy OAuth handlers
├── pkg/
│ ├── crypto/ # Ed25519 signatures
│ ├── logger/ # Structured logging
│ ├── services/ # OAuth provider detection
│ └── web/ # HTTP server setup
├── migrations/ # SQL migrations
├── templates/ # Email templates (HTML/text)
└── locales/ # Backend translations
```
### Applied Go Principles
**Interfaces**:
- ✅ Defined in the package that uses them
- ✅ Principle "accept interfaces, return structs"
- ✅ Repositories implemented in `infrastructure/database/`
**Dependency Injection**:
- ✅ Explicit constructors in `main.go`
- ✅ No complex DI container
- ✅ Clear and visible dependencies
**Code Quality**:
-`go fmt` and `go vet` clean
- ✅ No dead code
- ✅ Simple and focused interfaces
## Frontend (Vue.js 3)
### SPA Structure
```
webapp/
├── src/
│ ├── components/ # Reusable components
│ │ ├── ui/ # shadcn/vue components
│ │ └── ...
│ ├── pages/ # Pages (router views)
│ │ ├── Home.vue
│ │ ├── Admin.vue
│ │ └── ...
│ ├── services/ # API client (axios)
│ ├── stores/ # Pinia state management
│ ├── router/ # Vue Router config
│ ├── locales/ # Translations (fr, en, es, de, it)
│ └── composables/ # Vue composables
├── public/ # Static assets
└── scripts/ # Build scripts
```
### Frontend Stack
- **Vue 3** - Composition API
- **TypeScript** - Type safety
- **Vite** - Build tool (fast HMR)
- **Pinia** - State management
- **Vue Router** - Client routing
- **Tailwind CSS** - Utility-first styling
- **shadcn/vue** - UI components
- **vue-i18n** - Internationalization
### Routing
```typescript
const routes = [
{ path: '/', component: Home }, // Public
{ path: '/signatures', component: MySignatures }, // Auth required
{ path: '/admin', component: Admin } // Admin only
]
```
Frontend handles:
- Route `/` with query param `?doc=xxx` → Signature page
- Route `/admin` → Admin dashboard
- Route `/signatures` → My signatures
## Database
### PostgreSQL Schema
Main tables:
- `signatures` - Ed25519 signatures
- `documents` - Document metadata
- `expected_signers` - Signer tracking
- `reminder_logs` - Email history
- `checksum_verifications` - Integrity verifications
- `oauth_sessions` - OAuth2 sessions + refresh tokens
See [Database](database.md) for complete schema.
### Migrations
- Format: `XXXX_description.up.sql` / `XXXX_description.down.sql`
- Applied automatically on startup (service `ackify-migrate`)
- Tool: `/backend/cmd/migrate`
## Security
### Cryptography
**Ed25519**:
- Digital signatures (elliptic curve)
- 256-bit private key
- Guaranteed non-repudiation
**SHA-256**:
- Payload hashing before signing
- Tampering detection
- Blockchain-like chaining (`prev_hash`)
**AES-256-GCM**:
- OAuth2 refresh token encryption
- Key derived from `ACKIFY_OAUTH_COOKIE_SECRET`
### OAuth2 + PKCE
**Flow**:
1. Client generates `code_verifier` (random)
2. Calculates `code_challenge = SHA256(code_verifier)`
3. Auth request with `code_challenge`
4. Provider returns `code`
5. Token exchange with `code + code_verifier`
**Security**:
- Protection against code interception
- S256 method (SHA-256)
- Automatically enabled
### Sessions
- Secure cookies (HttpOnly, Secure, SameSite=Lax)
- HMAC-SHA256 encryption
- PostgreSQL storage with encrypted refresh tokens
- Duration: 30 days
- Automatic cleanup: 37 days
## Build & Deployment
### Multi-Stage Docker
```dockerfile
# Stage 1 - Frontend build
FROM node:22-alpine AS frontend
COPY webapp/ /build/webapp/
RUN npm ci && npm run build
# Output: webapp/dist/
# Stage 2 - Backend build + embed frontend
FROM golang:alpine AS backend
ENV GOTOOLCHAIN=auto
COPY backend/ /build/backend/
COPY --from=frontend /build/webapp/dist/ /build/backend/cmd/community/web/dist/
RUN go build -o community ./cmd/community
# Frontend embedded via embed.FS
# Stage 3 - Runtime (distroless)
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=backend /build/backend/community /app/community
CMD ["/app/community"]
```
**Result**:
- Final image < 30 MB
- Single binary (backend + frontend)
- No runtime dependencies
### Runtime Injection
`ACKIFY_BASE_URL` is injected into `index.html` at startup:
```go
// Replaces __ACKIFY_BASE_URL__ with actual value
html = strings.ReplaceAll(html, "__ACKIFY_BASE_URL__", baseURL)
```
Allows changing domain without rebuild.
## Performance
### Backend
- **Connection pooling** PostgreSQL (25 max)
- **Prepared statements** - SQL injection prevention
- **Rate limiting** - 5 auth/min, 10 doc/min, 100 req/min
- **Structured logging** - JSON with request IDs
### Frontend
- **Code splitting** - Lazy loading routes
- **Tree shaking** - Dead code elimination
- **Minification** - Optimized production builds
- **HMR** - Hot Module Replacement (dev)
### Database
- **Indexes** on (doc_id, user_sub, session_id)
- **Constraints** UNIQUE for guarantees
- **Triggers** for immutability
- **Autovacuum** enabled
## Scalability
### Current Limits
- ✅ Monolith: ~10k req/s
- ✅ PostgreSQL: Single instance
- ✅ Sessions: In-database (no Redis)
### Horizontal Scaling (future)
For > 100k req/s:
1. **Load Balancer** - Multiple backend instances
2. **PostgreSQL read replicas** - Separate read/write
3. **Redis** - Session cache + rate limiting
4. **CDN** - Static assets
## Monitoring
### Structured Logs
JSON format:
```json
{
"level": "info",
"timestamp": "2025-01-15T14:30:00Z",
"request_id": "abc123",
"method": "POST",
"path": "/api/v1/signatures",
"duration_ms": 42,
"status": 201
}
```
### Health Check
```http
GET /api/v1/health
```
Response:
```json
{
"status": "healthy",
"database": "connected"
}
```
### Metrics (future)
- Prometheus metrics endpoint
- Grafana dashboards
- Alerting (PagerDuty, Slack)
## Tests
### Coverage
**72.6% code coverage** (unit + integration)
- Unit tests: 180+ tests
- Integration tests: 33 PostgreSQL tests
- CI/CD: GitHub Actions + Codecov
See [Development](development.md) to run tests.
## Technical Choices
### Why Go?
- ✅ Native performance (compiled)
- ✅ Simple concurrency (goroutines)
- ✅ Strong typing
- ✅ Single binary
- ✅ Simple deployment
### Why Vue 3?
- ✅ Modern Composition API
- ✅ Native TypeScript
- ✅ Reactive by default
- ✅ Rich ecosystem
- ✅ Excellent performance
### Why PostgreSQL?
- ✅ ACID compliance
- ✅ Integrity constraints
- ✅ Triggers
- ✅ JSON support
- ✅ Mature and stable
### Why Ed25519?
- ✅ Modern security (elliptic curve)
- ✅ Performance > RSA
- ✅ Short signatures (64 bytes)
- ✅ Standard crypto/ed25519 Go
## References
- [Go Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
- [Vue 3 Composition API](https://vuejs.org/guide/extras/composition-api-faq.html)
- [Ed25519 Spec](https://ed25519.cr.yp.to/)
- [OAuth2 + PKCE](https://oauth.net/2/pkce/)

604
docs/en/database.md Normal file
View File

@@ -0,0 +1,604 @@
# Database
PostgreSQL schema, migrations, and integrity guarantees.
## Overview
Ackify uses **PostgreSQL 16+** with:
- Versioned SQL migrations
- Strict integrity constraints
- Triggers for immutability
- Indexes for performance
## Main Schema
### Table `signatures`
Stores Ed25519 cryptographic signatures.
```sql
CREATE TABLE signatures (
id BIGSERIAL PRIMARY KEY,
doc_id TEXT NOT NULL,
user_sub TEXT NOT NULL, -- OAuth user ID (sub claim)
user_email TEXT NOT NULL,
user_name TEXT, -- User name (optional)
signed_at TIMESTAMPTZ NOT NULL,
payload_hash TEXT NOT NULL, -- SHA-256 of payload
signature TEXT NOT NULL, -- Ed25519 signature (base64)
nonce TEXT NOT NULL, -- Anti-replay attack
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
referer TEXT, -- Source (optional)
prev_hash TEXT, -- Hash of previous signature (chaining)
UNIQUE (doc_id, user_sub) -- ONE signature per user/document
);
CREATE INDEX idx_signatures_doc_id ON signatures(doc_id);
CREATE INDEX idx_signatures_user_sub ON signatures(user_sub);
```
**Guarantees**:
- ✅ One signature per user/document (UNIQUE constraint)
- ✅ Immutable timestamp via PostgreSQL trigger
- ✅ Hash chaining (blockchain-like) via `prev_hash`
- ✅ Cryptographic non-repudiation (Ed25519)
### Table `documents`
Document metadata.
```sql
CREATE TABLE documents (
doc_id TEXT PRIMARY KEY,
title TEXT NOT NULL DEFAULT '',
url TEXT NOT NULL DEFAULT '', -- Source document URL
checksum TEXT NOT NULL DEFAULT '', -- SHA-256, SHA-512, or MD5
checksum_algorithm TEXT NOT NULL DEFAULT 'SHA-256',
description TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_by TEXT NOT NULL DEFAULT '' -- Creator admin's user_sub
);
```
**Usage**:
- Title, description displayed in interface
- URL included in reminder emails
- Checksum for integrity verification (optional)
### Table `expected_signers`
Expected signers for tracking.
```sql
CREATE TABLE expected_signers (
id BIGSERIAL PRIMARY KEY,
doc_id TEXT NOT NULL,
email TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '', -- Name for personalization
added_at TIMESTAMPTZ NOT NULL DEFAULT now(),
added_by TEXT NOT NULL, -- Admin who added
notes TEXT,
UNIQUE (doc_id, email)
);
CREATE INDEX idx_expected_signers_doc_id ON expected_signers(doc_id);
```
**Features**:
- Completion tracking (% signed)
- Email reminder sending
- Unexpected signature detection
### Table `reminder_logs`
Email reminder history.
```sql
CREATE TABLE reminder_logs (
id BIGSERIAL PRIMARY KEY,
doc_id TEXT NOT NULL,
recipient_email TEXT NOT NULL,
sent_at TIMESTAMPTZ NOT NULL DEFAULT now(),
sent_by TEXT NOT NULL, -- Admin who sent
template_used TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('sent', 'failed', 'bounced')),
error_message TEXT,
FOREIGN KEY (doc_id, recipient_email)
REFERENCES expected_signers(doc_id, email)
);
CREATE INDEX idx_reminder_logs_doc_id ON reminder_logs(doc_id);
```
### Table `checksum_verifications`
Integrity verification history.
```sql
CREATE TABLE checksum_verifications (
id BIGSERIAL PRIMARY KEY,
doc_id TEXT NOT NULL,
verified_by TEXT NOT NULL,
verified_at TIMESTAMPTZ NOT NULL DEFAULT now(),
stored_checksum TEXT NOT NULL,
calculated_checksum TEXT NOT NULL,
algorithm TEXT NOT NULL,
is_valid BOOLEAN NOT NULL,
error_message TEXT,
FOREIGN KEY (doc_id) REFERENCES documents(doc_id)
);
CREATE INDEX idx_checksum_verifications_doc_id ON checksum_verifications(doc_id);
```
### Table `oauth_sessions`
OAuth2 sessions with encrypted refresh tokens.
```sql
CREATE TABLE oauth_sessions (
id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL UNIQUE, -- Gorilla session ID
user_sub TEXT NOT NULL, -- OAuth user ID
refresh_token_encrypted BYTEA NOT NULL, -- Encrypted AES-256-GCM
access_token_expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_refreshed_at TIMESTAMPTZ,
user_agent TEXT,
ip_address INET
);
CREATE INDEX idx_oauth_sessions_session_id ON oauth_sessions(session_id);
CREATE INDEX idx_oauth_sessions_user_sub ON oauth_sessions(user_sub);
CREATE INDEX idx_oauth_sessions_updated_at ON oauth_sessions(updated_at);
```
**Security**:
- Encrypted refresh tokens (AES-256-GCM)
- Automatic cleanup after 37 days
- IP + User-Agent tracking to detect theft
### Table `email_queue`
Asynchronous email queue with retry mechanism.
```sql
CREATE TABLE email_queue (
id BIGSERIAL PRIMARY KEY,
-- Email metadata
to_addresses TEXT[] NOT NULL, -- Recipient email addresses
cc_addresses TEXT[], -- CC addresses (optional)
bcc_addresses TEXT[], -- BCC addresses (optional)
subject TEXT NOT NULL, -- Email subject
template TEXT NOT NULL, -- Template name (e.g., 'reminder')
locale TEXT NOT NULL DEFAULT 'fr', -- Email language (en, fr, es, de, it)
data JSONB NOT NULL DEFAULT '{}', -- Template variables
headers JSONB, -- Custom email headers (optional)
-- Queue management
status TEXT NOT NULL DEFAULT 'pending' -- pending, processing, sent, failed, cancelled
CHECK (status IN ('pending', 'processing', 'sent', 'failed', 'cancelled')),
priority INT NOT NULL DEFAULT 0, -- Higher = processed first (0=normal, 10=high, 100=urgent)
retry_count INT NOT NULL DEFAULT 0, -- Number of retry attempts
max_retries INT NOT NULL DEFAULT 3, -- Maximum retry limit
-- Tracking
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
scheduled_for TIMESTAMPTZ NOT NULL DEFAULT now(), -- Earliest processing time
processed_at TIMESTAMPTZ, -- When email was sent
next_retry_at TIMESTAMPTZ, -- Calculated retry time (exponential backoff)
-- Error tracking
last_error TEXT, -- Last error message
error_details JSONB, -- Detailed error information
-- Reference tracking (optional)
reference_type TEXT, -- e.g., 'reminder', 'notification'
reference_id TEXT, -- e.g., doc_id
created_by TEXT -- User who queued the email
);
-- Indexes for efficient queue processing
CREATE INDEX idx_email_queue_status_scheduled
ON email_queue(status, scheduled_for)
WHERE status IN ('pending', 'processing');
CREATE INDEX idx_email_queue_priority_scheduled
ON email_queue(priority DESC, scheduled_for ASC)
WHERE status = 'pending';
CREATE INDEX idx_email_queue_retry
ON email_queue(next_retry_at)
WHERE status = 'processing' AND retry_count < max_retries;
CREATE INDEX idx_email_queue_reference
ON email_queue(reference_type, reference_id);
CREATE INDEX idx_email_queue_created_at
ON email_queue(created_at DESC);
```
**Features**:
- **Asynchronous processing**: Emails processed by background worker
- **Retry mechanism**: Exponential backoff (1min, 2min, 4min, 8min, 16min, 32min...)
- **Priority support**: High-priority emails processed first
- **Scheduled sending**: Delay email delivery with `scheduled_for`
- **Error tracking**: Detailed error logging and retry history
- **Reference tracking**: Link emails to documents or other entities
**Automatic retry calculation**:
```sql
-- Function to calculate next retry time with exponential backoff
CREATE OR REPLACE FUNCTION calculate_next_retry_time(retry_count INT)
RETURNS TIMESTAMPTZ AS $$
BEGIN
-- Exponential backoff: 1min, 2min, 4min, 8min, 16min, 32min...
RETURN now() + (interval '1 minute' * power(2, retry_count));
END;
$$ LANGUAGE plpgsql;
```
**Worker configuration**:
- Batch size: 10 emails per batch
- Poll interval: 5 seconds
- Concurrent sends: 5 simultaneous emails
- Old email cleanup: 7 days retention for sent/failed emails
## Migrations
### Migration Management
Migrations are in `/backend/migrations/` with format:
```
XXXX_description.up.sql # "up" migration
XXXX_description.down.sql # "down" rollback
```
**Current files**:
- `0001_init.up.sql` - Signatures table
- `0002_expected_signers.up.sql` - Expected signers
- `0003_reminder_logs.up.sql` - Reminder logs
- `0004_add_name_to_expected_signers.up.sql` - Signer names
- `0005_create_documents_table.up.sql` - Documents metadata
- `0006_create_new_tables.up.sql` - Checksum verifications and email queue
- `0007_oauth_sessions.up.sql` - OAuth sessions with refresh tokens
### Applying Migrations
**Via Docker Compose** (automatic):
```bash
docker compose up -d
# The ackify-migrate service applies migrations on startup
```
**Manually**:
```bash
cd backend
go run ./cmd/migrate up
```
**Rollback last migration**:
```bash
go run ./cmd/migrate down
```
### Custom Migrations
To create a new migration:
1. Create `XXXX_my_feature.up.sql`:
```sql
-- Migration up
ALTER TABLE signatures ADD COLUMN new_field TEXT;
```
2. Create `XXXX_my_feature.down.sql`:
```sql
-- Rollback
ALTER TABLE signatures DROP COLUMN new_field;
```
3. Apply:
```bash
go run ./cmd/migrate up
```
## PostgreSQL Triggers
### Immutability of `created_at`
Trigger preventing `created_at` modification:
```sql
CREATE OR REPLACE FUNCTION prevent_created_at_update()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.created_at <> OLD.created_at THEN
RAISE EXCEPTION 'created_at cannot be modified';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER prevent_signatures_created_at_update
BEFORE UPDATE ON signatures
FOR EACH ROW
EXECUTE FUNCTION prevent_created_at_update();
```
**Guarantee**: No signature can be backdated.
### Auto-update of `updated_at`
For tables with `updated_at`:
```sql
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_documents_updated_at
BEFORE UPDATE ON documents
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
```
## Useful Queries
### View document signatures
```sql
SELECT
user_email,
user_name,
signed_at,
payload_hash,
signature
FROM signatures
WHERE doc_id = 'my_document'
ORDER BY signed_at DESC;
```
### Completion status
```sql
WITH expected AS (
SELECT COUNT(*) as total
FROM expected_signers
WHERE doc_id = 'my_document'
),
signed AS (
SELECT COUNT(*) as count
FROM signatures s
INNER JOIN expected_signers e ON s.user_email = e.email AND s.doc_id = e.doc_id
WHERE s.doc_id = 'my_document'
)
SELECT
e.total as expected,
s.count as signed,
ROUND(100.0 * s.count / NULLIF(e.total, 0), 2) as completion_pct
FROM expected e, signed s;
```
### Missing signers
```sql
SELECT
e.email,
e.name,
e.added_at
FROM expected_signers e
LEFT JOIN signatures s ON e.email = s.user_email AND e.doc_id = s.doc_id
WHERE e.doc_id = 'my_document' AND s.id IS NULL
ORDER BY e.added_at;
```
### Unexpected signatures
```sql
SELECT
s.user_email,
s.signed_at
FROM signatures s
LEFT JOIN expected_signers e ON s.user_email = e.email AND s.doc_id = e.doc_id
WHERE s.doc_id = 'my_document' AND e.id IS NULL
ORDER BY s.signed_at DESC;
```
### Email queue status
```sql
-- View pending emails
SELECT
id,
to_addresses,
subject,
status,
priority,
retry_count,
scheduled_for,
created_at
FROM email_queue
WHERE status IN ('pending', 'processing')
ORDER BY priority DESC, scheduled_for ASC
LIMIT 20;
-- Failed emails needing attention
SELECT
id,
to_addresses,
subject,
retry_count,
max_retries,
last_error,
next_retry_at
FROM email_queue
WHERE status = 'failed'
ORDER BY created_at DESC;
-- Email statistics by status
SELECT
status,
COUNT(*) as count,
MIN(created_at) as oldest,
MAX(created_at) as newest
FROM email_queue
GROUP BY status
ORDER BY status;
```
## Backup & Restore
### PostgreSQL Backup
```bash
# Full backup
docker compose exec ackify-db pg_dump -U ackifyr ackify > backup.sql
# Compressed backup
docker compose exec ackify-db pg_dump -U ackifyr ackify | gzip > backup.sql.gz
```
### Restore
```bash
# Restore from backup
cat backup.sql | docker compose exec -T ackify-db psql -U ackifyr ackify
# Restore from compressed backup
gunzip -c backup.sql.gz | docker compose exec -T ackify-db psql -U ackifyr ackify
```
### Automated Backup
Example cron for daily backup:
```bash
0 2 * * * docker compose -f /path/to/compose.yml exec -T ackify-db pg_dump -U ackifyr ackify | gzip > /backups/ackify-$(date +\%Y\%m\%d).sql.gz
```
## Performance
### Indexes
Indexes are automatically created for:
- `signatures(doc_id)` - Document queries
- `signatures(user_sub)` - User queries
- `expected_signers(doc_id)` - Completion tracking
- `oauth_sessions(session_id)` - Session lookups
### Connection Pooling
The Go backend automatically handles connection pooling:
- Max open connections: 25
- Max idle connections: 5
- Connection max lifetime: 5 minutes
### Vacuum & Analyze
PostgreSQL handles automatically via `autovacuum`. To force:
```sql
VACUUM ANALYZE signatures;
VACUUM ANALYZE documents;
```
## Monitoring
### Table sizes
```sql
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
```
### Statistics
```sql
SELECT * FROM pg_stat_user_tables WHERE schemaname = 'public';
```
### Active connections
```sql
SELECT
datname,
usename,
application_name,
client_addr,
state,
query
FROM pg_stat_activity
WHERE datname = 'ackify';
```
## Security
### In Production
- ✅ Use SSL: `?sslmode=require` in DSN
- ✅ Strong password for PostgreSQL
- ✅ Restrict network connections
- ✅ Encrypted backups
- ✅ Regular secret rotation
### SSL Configuration
```bash
# In .env
ACKIFY_DB_DSN=postgres://user:pass@host:5432/ackify?sslmode=require
```
### Audit Trail
All important operations are tracked:
- `signatures.created_at` - Signature timestamp
- `expected_signers.added_by` - Who added
- `reminder_logs.sent_by` - Who sent reminder
- `checksum_verifications.verified_by` - Who verified
## Troubleshooting
### Blocked migrations
```bash
# Check status
docker compose logs ackify-migrate
# Force rollback
docker compose exec ackify-ce /app/migrate down
docker compose exec ackify-ce /app/migrate up
```
### UNIQUE constraint violated
Error: `duplicate key value violates unique constraint`
**Cause**: User already signed this document.
**Solution**: This is normal behavior (one signature per user/doc).
### Connection refused
Verify PostgreSQL is started:
```bash
docker compose ps ackify-db
docker compose logs ackify-db
```

122
docs/en/deployment.md Normal file
View File

@@ -0,0 +1,122 @@
# Deployment
Production deployment guide with Docker Compose.
## Production with Docker Compose
### Recommended Architecture
```
[Internet] → [Reverse Proxy (Traefik/Nginx)] → [Ackify Container]
[PostgreSQL Container]
```
### Production compose.yml
See the `/compose.yml` file at the project root for complete configuration.
**Included services**:
- `ackify-migrate` - PostgreSQL migrations (run once)
- `ackify-ce` - Main application
- `ackify-db` - PostgreSQL 16
### Production .env Configuration
```bash
# Application
APP_DNS=sign.company.com
ACKIFY_BASE_URL=https://sign.company.com
ACKIFY_ORGANISATION="ACME Corporation"
ACKIFY_LOG_LEVEL=info
# Database (strong password)
POSTGRES_USER=ackifyr
POSTGRES_PASSWORD=$(openssl rand -base64 32)
POSTGRES_DB=ackify
# OAuth2
ACKIFY_OAUTH_PROVIDER=google
ACKIFY_OAUTH_CLIENT_ID=your_client_id
ACKIFY_OAUTH_CLIENT_SECRET=your_client_secret
ACKIFY_OAUTH_ALLOWED_DOMAIN=@company.com
# Security (generate with openssl)
ACKIFY_OAUTH_COOKIE_SECRET=$(openssl rand -base64 64)
ACKIFY_ED25519_PRIVATE_KEY=$(openssl rand -base64 64)
# Administration
ACKIFY_ADMIN_EMAILS=admin@company.com,cto@company.com
```
## Reverse Proxy
### Traefik
Add labels in `compose.yml`:
```yaml
services:
ackify-ce:
labels:
- "traefik.enable=true"
- "traefik.http.routers.ackify.rule=Host(`sign.company.com`)"
- "traefik.http.routers.ackify.entrypoints=websecure"
- "traefik.http.routers.ackify.tls.certresolver=letsencrypt"
```
### Nginx
```nginx
server {
listen 443 ssl http2;
server_name sign.company.com;
ssl_certificate /etc/letsencrypt/live/sign.company.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sign.company.com/privkey.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
## Security Checklist
- ✅ HTTPS with valid certificate
- ✅ Strong secrets (64+ bytes)
- ✅ PostgreSQL SSL in production
- ✅ Restricted OAuth domain
- ✅ Logs in info mode
- ✅ Automatic backup
- ✅ Active monitoring
## Backup
```bash
# Daily PostgreSQL backup
docker compose exec -T ackify-db pg_dump -U ackifyr ackify | gzip > backup-$(date +%Y%m%d).sql.gz
# Restore
gunzip -c backup.sql.gz | docker compose exec -T ackify-db psql -U ackifyr ackify
```
## Update
```bash
# Pull new image
docker compose pull ackify-ce
# Restart
docker compose up -d
# Verify
docker compose logs -f ackify-ce
curl https://sign.company.com/api/v1/health
```
See [Getting Started](getting-started.md) for more details.

513
docs/en/development.md Normal file
View File

@@ -0,0 +1,513 @@
# Development
Guide for contributing and developing on Ackify.
## Development Setup
### Prerequisites
- **Go 1.24.5+**
- **Node.js 22+** and npm
- **PostgreSQL 16+**
- **Docker & Docker Compose**
- Git
### Clone & Setup
```bash
# Clone
git clone https://github.com/btouchard/ackify-ce.git
cd ackify-ce
# Copy .env
cp .env.example .env
# Edit .env with your OAuth2 credentials
nano .env
```
## Backend Development
### Build
```bash
cd backend
go mod download
go build ./cmd/community
```
### Run
```bash
# Start PostgreSQL with Docker
docker compose up -d ackify-db
# Apply migrations
go run ./cmd/migrate up
# Launch app
./community
```
API accessible at `http://localhost:8080`.
### Tests
```bash
# Unit tests
go test -v -short ./...
# Tests with coverage
go test -coverprofile=coverage.out ./internal/... ./pkg/...
# View coverage
go tool cover -html=coverage.out
# Integration tests (PostgreSQL required)
docker compose -f ../compose.test.yml up -d
INTEGRATION_TESTS=1 go test -tags=integration -v ./internal/infrastructure/database/
docker compose -f ../compose.test.yml down
```
### Linting
```bash
# Format
go fmt ./...
# Vet
go vet ./...
# Staticcheck (optional)
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...
```
## Frontend Development
### Setup
```bash
cd webapp
npm install
```
### Dev Server
```bash
npm run dev
```
Frontend accessible at `http://localhost:5173` with Hot Module Replacement.
### Production Build
```bash
npm run build
# Output: webapp/dist/
```
### Type Checking
```bash
npm run type-check
```
### i18n Validation
```bash
npm run lint:i18n
```
Verifies all translations are complete.
## Docker Development
### Local Build
```bash
# Build complete image (frontend + backend)
docker compose -f compose.local.yml up -d --build
# Logs
docker compose -f compose.local.yml logs -f ackify-ce
# Rebuild after changes
docker compose -f compose.local.yml up -d --force-recreate ackify-ce --build
```
### Debug
```bash
# Shell in container
docker compose exec ackify-ce sh
# PostgreSQL shell
docker compose exec ackify-db psql -U ackifyr -d ackify
```
## Code Structure
### Backend
```
backend/
├── cmd/
│ ├── community/ # main.go + dependency injection
│ └── migrate/ # Migration tool
├── internal/
│ ├── domain/models/ # Entities (User, Signature, Document)
│ ├── application/services/ # Business logic
│ ├── infrastructure/
│ │ ├── auth/ # OAuth2
│ │ ├── database/ # Repositories
│ │ ├── email/ # SMTP
│ │ └── config/ # Config
│ └── presentation/api/ # HTTP handlers
└── pkg/ # Utilities
```
### Frontend
```
webapp/src/
├── components/ # Vue components
├── pages/ # Pages (router)
├── services/ # API client
├── stores/ # Pinia stores
├── router/ # Vue Router
└── locales/ # Translations
```
## Code Conventions
### Go
**Naming**:
- Packages: lowercase, singular (`user`, `signature`)
- Interfaces: suffix `er` or descriptive (`SignatureRepository`, `EmailSender`)
- Constructors: `New...()` or `...From...()`
**Example**:
```go
// Service
type SignatureService struct {
repo SignatureRepository
crypto CryptoService
}
func NewSignatureService(repo SignatureRepository, crypto CryptoService) *SignatureService {
return &SignatureService{repo: repo, crypto: crypto}
}
// Method
func (s *SignatureService) CreateSignature(ctx context.Context, docID, userSub string) (*models.Signature, error) {
// ...
}
```
**Errors**:
```go
// Wrapping
return nil, fmt.Errorf("failed to create signature: %w", err)
// Custom errors
var ErrAlreadySigned = errors.New("user has already signed this document")
```
### TypeScript
**Naming**:
- Components: PascalCase (`DocumentCard.vue`)
- Composables: camelCase with `use` prefix (`useAuth.ts`)
- Stores: camelCase with `Store` suffix (`userStore.ts`)
**Example**:
```typescript
// Composable
export function useAuth() {
const user = ref<User | null>(null)
async function login() {
// ...
}
return { user, login }
}
// Store
export const useUserStore = defineStore('user', () => {
const currentUser = ref<User | null>(null)
async function fetchMe() {
const { data } = await api.get('/users/me')
currentUser.value = data
}
return { currentUser, fetchMe }
})
```
## Adding a Feature
### 1. Planning
- Define required API endpoints
- SQL schema if needed
- User interface
### 2. Backend
```bash
# 1. Create migration if needed
touch backend/migrations/XXXX_my_feature.up.sql
touch backend/migrations/XXXX_my_feature.down.sql
# 2. Create model
# backend/internal/domain/models/my_model.go
# 3. Create repository interface
# backend/internal/application/services/my_service.go
# 4. Implement repository
# backend/internal/infrastructure/database/my_repository.go
# 5. Create API handler
# backend/internal/presentation/api/myfeature/handler.go
# 6. Register routes
# backend/internal/presentation/api/router.go
```
### 3. Frontend
```bash
# 1. Create API service
# webapp/src/services/myFeatureService.ts
# 2. Create Pinia store
# webapp/src/stores/myFeatureStore.ts
# 3. Create components
# webapp/src/components/MyFeature.vue
# 4. Add translations
# webapp/src/locales/{fr,en,es,de,it}.json
# 5. Add routes if needed
# webapp/src/router/index.ts
```
### 4. Tests
```bash
# Backend
# backend/internal/presentation/api/myfeature/handler_test.go
# Test
go test -v ./internal/presentation/api/myfeature/
```
### 5. Documentation
Update:
- `/api/openapi.yaml` - OpenAPI specification
- `/docs/api.md` - API documentation
- `/docs/features/my-feature.md` - User guide
## Debugging
### Backend
```go
// Structured logs
logger.Info("signature created",
"doc_id", docID,
"user_sub", userSub,
"signature_id", sig.ID,
)
// Debug via Delve (optional)
dlv debug ./cmd/community
```
### Frontend
```typescript
// Vue DevTools (Chrome/Firefox extension)
// Inspect: Components, Pinia stores, Router
// Console debug
console.log('[DEBUG] User:', user.value)
// Breakpoints via browser
debugger
```
## SQL Migrations
### Create Migration
```sql
-- XXXX_add_field.up.sql
ALTER TABLE signatures ADD COLUMN new_field TEXT;
-- XXXX_add_field.down.sql
ALTER TABLE signatures DROP COLUMN new_field;
```
### Apply
```bash
go run ./cmd/migrate up
```
### Rollback
```bash
go run ./cmd/migrate down
```
## Integration Tests
### Setup PostgreSQL Test
```bash
docker compose -f compose.test.yml up -d
```
### Run Tests
```bash
INTEGRATION_TESTS=1 go test -tags=integration -v ./internal/infrastructure/database/
```
### Cleanup
```bash
docker compose -f compose.test.yml down -v
```
## CI/CD
### GitHub Actions
Project uses `.github/workflows/ci.yml`:
**Jobs**:
1. **Lint** - go fmt, go vet, eslint
2. **Test Backend** - Unit + integration tests
3. **Test Frontend** - Type checking + i18n validation
4. **Coverage** - Upload to Codecov
5. **Build** - Verify Docker image builds
### Pre-commit Hooks (optional)
```bash
# Install pre-commit
pip install pre-commit
# Setup hooks
pre-commit install
# Run manually
pre-commit run --all-files
```
## Contribution
### Git Workflow
```bash
# 1. Create branch
git checkout -b feature/my-feature
# 2. Develop + commit
git add .
git commit -m "feat: add my feature"
# 3. Push
git push origin feature/my-feature
# 4. Create Pull Request on GitHub
```
### Commit Messages
Format: `type: description`
**Types**:
- `feat` - New feature
- `fix` - Bug fix
- `docs` - Documentation
- `refactor` - Refactoring
- `test` - Tests
- `chore` - Maintenance
**Examples**:
```
feat: add checksum verification feature
fix: resolve OAuth callback redirect loop
docs: update API documentation for signatures
refactor: simplify signature service logic
test: add integration tests for expected signers
```
### Code Review
**Checklist**:
- ✅ Tests pass (CI green)
- ✅ Code formatted (`go fmt`, `eslint`)
- ✅ No committed secrets
- ✅ Documentation updated
- ✅ Complete translations (i18n)
## Troubleshooting
### Backend won't start
```bash
# Check PostgreSQL
docker compose ps ackify-db
docker compose logs ackify-db
# Check environment variables
cat .env
# Detailed logs
ACKIFY_LOG_LEVEL=debug ./community
```
### Frontend build fails
```bash
# Clean and reinstall
rm -rf node_modules package-lock.json
npm install
# Check Node version
node --version # Should be 22+
```
### Tests fail
```bash
# Backend - check PostgreSQL test
docker compose -f compose.test.yml ps
# Frontend - check types
npm run type-check
```
## Resources
- [Go Documentation](https://go.dev/doc/)
- [Vue 3 Guide](https://vuejs.org/guide/)
- [PostgreSQL Docs](https://www.postgresql.org/docs/)
- [Chi Router](https://github.com/go-chi/chi)
- [Pinia](https://pinia.vuejs.org/)
## Support
- [GitHub Issues](https://github.com/btouchard/ackify-ce/issues)
- [GitHub Discussions](https://github.com/btouchard/ackify-ce/discussions)

684
docs/fr/admin-guide.md Normal file
View File

@@ -0,0 +1,684 @@
# Guide Administrateur
Guide complet pour les administrateurs utilisant Ackify pour gérer les documents, les signataires attendus et les rappels email.
## Table des Matières
- [Obtenir l'accès Admin](#obtenir-laccès-admin)
- [Dashboard Admin](#dashboard-admin)
- [Gestion des Documents](#gestion-des-documents)
- [Signataires Attendus](#signataires-attendus)
- [Rappels Email](#rappels-email)
- [Monitoring & Statistiques](#monitoring--statistiques)
- [Bonnes Pratiques](#bonnes-pratiques)
- [Dépannage](#dépannage)
---
## Obtenir l'accès Admin
### Prérequis
Pour accéder aux fonctionnalités admin, votre email doit être configuré dans la variable d'environnement `ACKIFY_ADMIN_EMAILS`.
```bash
# Dans le fichier .env
ACKIFY_ADMIN_EMAILS=admin@company.com,manager@company.com
```
**Après ajout de votre email:**
1. Redémarrer Ackify: `docker compose restart ackify-ce`
2. Se déconnecter et se reconnecter
3. Vous devriez maintenant voir le lien "Admin" dans la navigation
### Vérifier l'accès Admin
Visitez `/admin` - si vous voyez le dashboard admin, vous avez l'accès admin.
---
## Dashboard Admin
**URL**: `/admin`
Le dashboard admin fournit:
- **Total Documents**: Nombre de documents dans le système
- **Lecteurs Attendus**: Nombre total de signataires attendus sur tous les documents
- **Documents Actifs**: Documents non supprimés
- **Liste Documents**: Liste paginée (20 par page) avec recherche
### Fonctionnalités du Dashboard
#### Statistiques Rapides
Trois cartes KPI en haut:
- Nombre total de documents
- Total des lecteurs/signataires attendus
- Documents actifs (non supprimés)
#### Recherche de Documents
- Recherche par titre, ID document ou URL
- Filtrage en temps réel
#### Liste des Documents
**Vue desktop** - Tableau avec colonnes:
- ID Document
- Titre
- URL
- Date de création
- Créateur
- Actions (Voir détails)
**Vue mobile** - Mise en page carte avec:
- ID document et titre
- Info création
- Tap pour voir détails
#### Pagination
- 20 documents par page
- Boutons Précédent/Suivant
- Indicateur de page actuelle
---
## Gestion des Documents
### Créer un Document
**Depuis le Dashboard Admin:**
1. Cliquer sur le bouton "Créer Nouveau Document"
2. Remplir le formulaire:
- **Référence** (requis): URL, chemin fichier ou ID personnalisé
- **Titre** (optionnel): Auto-généré depuis l'URL si vide
- **Description** (optionnel): Contexte additionnel
3. Cliquer sur "Créer Document"
**Fonctionnalités Automatiques:**
- **Génération ID Unique**: doc_id base36 résistant aux collisions
- **Extraction Titre**: Auto-extrait de l'URL si non fourni
- **Calcul Checksum**: Pour URLs distantes (si admin et fichier < 10MB)
**Exemple:**
```
Référence: https://docs.company.com/politique-2025.pdf
Titre: Politique de Sécurité 2025 (auto-extrait ou manuel)
Description: Politique annuelle de conformité sécurité
```
**Résultat:**
- doc_id: `k7m2n4p8` (auto-généré)
- Checksum: SHA-256 auto-calculé (si URL accessible)
### Voir les Détails d'un Document
**URL**: `/admin/docs/{docId}`
Fournit des informations complètes sur le document:
#### 1. **Section Métadonnées**
Éditer les informations du document:
- Titre
- URL
- Description
- Checksum (SHA-256, SHA-512 ou MD5)
- Algorithme Checksum
**Pour éditer:**
1. Cliquer sur "Éditer Métadonnées"
2. Modifier les champs
3. Cliquer sur "Sauvegarder"
4. Modal de confirmation pour changements critiques (checksum, algorithme)
#### 2. **Panneau Statistiques**
Suivi des signatures en temps réel:
- **Attendus**: Nombre de signataires attendus
- **Signés**: Nombre ayant signé
- **En Attente**: Pas encore signé
- **Complétion**: Pourcentage complété
#### 3. **Section Signataires Attendus**
Liste tous les signataires attendus avec statut:
- **Email**: Adresse email du signataire
- **Statut**: ✅ Signé ou ⏳ En attente
- **Ajouté**: Date d'ajout à la liste attendue
- **Jours Depuis Ajout**: Suivi du temps
- **Dernier Rappel**: Quand le dernier rappel a été envoyé
- **Nb Rappels**: Total rappels envoyés
- **Actions**: Bouton retirer signataire
**Code couleur:**
- Fond vert: Signataire a signé
- Fond par défaut: Signature en attente
#### 4. **Signatures Inattendues**
Affiche les utilisateurs ayant signé mais pas sur la liste attendue:
- Email utilisateur
- Date de signature
- Indique participation organique/inattendue
#### 5. **Actions**
- **Envoyer Rappels**: Emailer les signataires en attente
- **Partager Lien**: Générer et copier lien de signature
- **Supprimer Document**: Suppression douce (préserve historique signatures)
### Mettre à Jour les Métadonnées du Document
**Champs importants:**
**Titre & Description:**
- Peuvent être changés librement
- Pas de confirmation requise
**URL:**
- Met à jour où le document est localisé
- Modal de confirmation affiché
**Checksum & Algorithme:**
- Critique pour vérification d'intégrité
- Modal de confirmation avertit de l'impact
- Changer uniquement si version du document a changé
**Workflow:**
1. Cliquer sur "Éditer Métadonnées"
2. Modifier les champs désirés
3. Cliquer sur "Sauvegarder"
4. Si checksum/algorithme changé, confirmer dans modal
5. Notification de succès affichée
### Supprimer un Document
**Comportement Suppression Douce:**
- Document marqué comme supprimé (timestamp `deleted_at` défini)
- Historique signatures préservé
- Document n'apparaît plus dans listes publiques
- Admin peut toujours voir via URL directe
- Signatures CASCADE update (marquées avec `doc_deleted_at`)
**Pour supprimer:**
1. Aller sur page détail document (`/admin/docs/{docId}`)
2. Cliquer sur bouton "Supprimer Document"
3. Confirmer suppression dans modal
4. Document déplacé en état supprimé
**Note**: Il n'y a pas de "restauration" - c'est une suppression douce permanente.
---
## Signataires Attendus
Les signataires attendus sont les utilisateurs que vous souhaitez suivre pour la complétion du document.
### Ajouter des Signataires Attendus
**Depuis la page détail document:**
1. Défiler jusqu'à la section "Signataires Attendus"
2. Cliquer sur bouton "Ajouter Signataire Attendu"
3. Entrer adresse(s) email:
- Simple: `alice@company.com`
- Multiple: Séparées par virgules `alice@company.com,bob@company.com`
4. Optionnellement ajouter des notes
5. Cliquer sur "Ajouter"
**Endpoint API:**
```http
POST /api/v1/admin/documents/{docId}/signers
Content-Type: application/json
X-CSRF-Token: {token}
{
"emails": ["alice@company.com", "bob@company.com"],
"notes": "Membres du conseil - Q1 2025"
}
```
**Contraintes:**
- Email doit avoir format valide
- Contrainte UNIQUE: Impossible d'ajouter même email deux fois au même document
- Ajouté par admin utilisateur actuel (suivi dans `added_by`)
### Retirer des Signataires Attendus
**Depuis la page détail document:**
1. Trouver signataire dans liste Signataires Attendus
2. Cliquer sur bouton "Retirer" à côté de leur email
3. Confirmer le retrait
**Endpoint API:**
```http
DELETE /api/v1/admin/documents/{docId}/signers/{email}
X-CSRF-Token: {token}
```
**Effet:**
- Signataire retiré de la liste attendue
- NE supprime PAS leur signature s'ils ont déjà signé
- Historique des rappels préservé dans `reminder_logs`
### Suivre le Statut de Complétion
**API Statut Document:**
```http
GET /api/v1/admin/documents/{docId}/status
```
**Réponse:**
```json
{
"docId": "abc123",
"expectedCount": 10,
"signedCount": 7,
"pendingCount": 3,
"completionPercentage": 70.0
}
```
**Indicateurs visuels:**
- Barre de progression montrant pourcentage complétion
- Statut code couleur: Vert (signé), Orange (en attente)
- Jours depuis ajout (aide identifier signataires lents)
---
## Rappels Email
Les rappels email sont envoyés de manière asynchrone via le système `email_queue`.
### Envoyer des Rappels
**Depuis la page détail document:**
1. Cliquer sur bouton "Envoyer Rappels"
2. Modal s'ouvre avec options:
- **Envoyer à**: Tous en attente OU emails spécifiques
- **URL Document**: Pré-rempli, peut personnaliser
- **Langue**: en, fr, es, de, it
3. Cliquer sur "Envoyer Rappels"
4. Confirmation: "X rappels mis en file pour envoi"
**Endpoint API:**
```http
POST /api/v1/admin/documents/{docId}/reminders
Content-Type: application/json
X-CSRF-Token: {token}
{
"emails": ["alice@company.com"], // Optionnel: emails spécifiques
"docURL": "https://docs.company.com/politique.pdf",
"locale": "fr"
}
```
**Comportement:**
- Envoie à TOUS les signataires en attente si `emails` non spécifié
- Envoie aux `emails` spécifiques si fournis (même si déjà signé)
- Emails mis en file dans table `email_queue`
- Worker background traite la file
- Retry en cas d'échec (3 tentatives, exponential backoff)
### Templates Email
**Emplacement**: `backend/templates/emails/`
**Templates disponibles:**
- `reminder.html` - Version HTML
- `reminder.txt` - Version texte simple
**Variables disponibles dans les templates:**
- `{{.DocTitle}}` - Titre document
- `{{.DocURL}}` - URL document
- `{{.RecipientEmail}}` - Email destinataire
- `{{.SenderName}}` - Admin ayant envoyé rappel
- `{{.OrganisationName}}` - Depuis ACKIFY_ORGANISATION
**Locales**: en, fr, es, de, it
- Répertoire template: `templates/emails/{locale}/`
- Fallback vers locale par défaut si traduction manquante
### Historique des Rappels
**Voir log des rappels:**
```http
GET /api/v1/admin/documents/{docId}/reminders
```
**Réponse:**
```json
{
"reminders": [
{
"id": 123,
"docId": "abc123",
"recipientEmail": "alice@company.com",
"sentAt": "2025-01-15T10:30:00Z",
"sentBy": "admin@company.com",
"templateUsed": "reminder",
"status": "sent",
"errorMessage": null
}
]
}
```
**Valeurs de statut:**
- `queued` - Dans email_queue, pas encore traité
- `sent` - Livré avec succès
- `failed` - Échec de livraison (voir errorMessage)
- `bounced` - Email retourné
**Suivi:**
- Date dernier rappel envoyé affiché par signataire
- Nombre rappels affiché par signataire
- Aide éviter sur-envoi
### Monitoring de la File Email
**Vérifier statut de la file (PostgreSQL):**
```sql
-- Emails en attente
SELECT id, to_addresses, subject, status, scheduled_for
FROM email_queue
WHERE status IN ('pending', 'processing')
ORDER BY priority DESC, scheduled_for ASC;
-- Emails échoués
SELECT id, to_addresses, last_error, retry_count
FROM email_queue
WHERE status = 'failed';
```
**Configuration du worker:**
- Taille lot: 10 emails
- Intervalle polling: 5 secondes
- Max retries: 3
- Cleanup: Rétention 7 jours
---
## Monitoring & Statistiques
### Statistiques Niveau Document
**Suivi complétion:**
- Nombre Attendus vs Signés
- Liste signataires en attente
- Pourcentage complétion
- Temps moyen pour signer
**Efficacité des rappels:**
- Nombre rappels envoyés
- Taux succès/échec
- Temps entre rappel et signature
### Métriques Système Global
**Requêtes PostgreSQL:**
```sql
-- Total documents
SELECT COUNT(*) FROM documents WHERE deleted_at IS NULL;
-- Total signatures
SELECT COUNT(*) FROM signatures;
-- Documents par statut complétion
SELECT
CASE
WHEN signed_count = expected_count THEN '100%'
WHEN signed_count >= expected_count * 0.75 THEN '75-99%'
WHEN signed_count >= expected_count * 0.50 THEN '50-74%'
ELSE '<50%'
END as completion_bracket,
COUNT(*) as doc_count
FROM (
SELECT
d.doc_id,
COUNT(DISTINCT es.email) as expected_count,
COUNT(DISTINCT s.user_email) as signed_count
FROM documents d
LEFT JOIN expected_signers es ON d.doc_id = es.doc_id
LEFT JOIN signatures s ON d.doc_id = s.doc_id AND s.user_email = es.email
WHERE d.deleted_at IS NULL
GROUP BY d.doc_id
) stats
GROUP BY completion_bracket;
-- Statistiques file email
SELECT status, COUNT(*), MIN(created_at), MAX(created_at)
FROM email_queue
GROUP BY status;
```
### Exporter les Données
**Signatures pour un document:**
```sql
COPY (
SELECT s.user_email, s.user_name, s.signed_at, s.payload_hash
FROM signatures s
WHERE s.doc_id = 'votre_doc_id'
ORDER BY s.signed_at
) TO '/tmp/signatures_export.csv' WITH CSV HEADER;
```
**Statut signataires attendus:**
```sql
COPY (
SELECT
es.email,
CASE WHEN s.id IS NOT NULL THEN 'Signé' ELSE 'En attente' END as status,
es.added_at,
s.signed_at
FROM expected_signers es
LEFT JOIN signatures s ON es.doc_id = s.doc_id AND es.email = s.user_email
WHERE es.doc_id = 'votre_doc_id'
) TO '/tmp/expected_signers_export.csv' WITH CSV HEADER;
```
---
## Bonnes Pratiques
### 1. Création de Documents
**À Faire:**
- Utiliser titres descriptifs
- Ajouter descriptions claires
- Inclure URL document pour accès facile
- Stocker checksum pour vérification intégrité
- Créer liste signataires attendus avant partage
**À Éviter:**
- Utiliser titres génériques comme "Document 1"
- Laisser URL vide si document accessible en ligne
- Changer checksums sauf si document réellement changé
### 2. Gestion Signataires Attendus
**À Faire:**
- Ajouter signataires attendus avant d'envoyer lien document
- Utiliser notes claires pour expliquer pourquoi signataires attendus
- Réviser signataires en attente régulièrement
- Retirer signataires qui ne sont plus pertinents
**À Éviter:**
- Ajouter centaines de signataires d'un coup (utiliser lots)
- Envoyer rappels trop fréquemment (max une fois par semaine)
- Retirer signataires ayant déjà signé (préserver historique)
### 3. Rappels Email
**À Faire:**
- Attendre 3-5 jours avant premier rappel
- Envoyer dans langue préférée du destinataire
- Inclure titre et URL document clairs
- Suivre historique rappels pour éviter spam
- Envoyer rappels pendant heures bureau
**À Éviter:**
- Envoyer rappels quotidiens (cause fatigue)
- Envoyer sans vérifier si déjà signé
- Utiliser sujets génériques (personnaliser avec titre doc)
- Envoyer hors heures bureau
### 4. Intégrité des Données
**À Faire:**
- Sauvegarder régulièrement base PostgreSQL
- Vérifier checksums correspondent documents réels
- Monitorer file email pour échecs
- Réviser signatures inattendues (peut indiquer intérêt plus large)
- Exporter données signatures importantes
**À Éviter:**
- Supprimer documents avec signatures actives
- Modifier timestamps manuellement en base
- Ignorer échecs livraison email
- Changer checksums sans mettre à jour document
### 5. Sécurité
**À Faire:**
- Limiter accès admin aux utilisateurs de confiance uniquement
- Utiliser HTTPS en production (`ACKIFY_BASE_URL=https://...`)
- Tourner `ACKIFY_OAUTH_COOKIE_SECRET` périodiquement
- Monitorer actions admin via logs application
- Utiliser restrictions domaine OAuth autorisé
**À Éviter:**
- Partager identifiants admin
- Fonctionner sans HTTPS en production
- Désactiver protection CSRF
- Ignorer échecs authentification dans logs
---
## Dépannage
### Problèmes Courants
#### 1. Lien Admin Non Visible
**Problème**: Ne peut pas voir lien "Admin" dans navigation
**Solutions:**
- Vérifier email dans variable `ACKIFY_ADMIN_EMAILS`
- Redémarrer Ackify: `docker compose restart ackify-ce`
- Se déconnecter et se reconnecter
- Vérifier logs: `docker compose logs ackify-ce | grep admin`
#### 2. Emails Non Envoyés
**Problème**: Rappels mis en file mais pas livrés
**Diagnostic:**
```sql
SELECT * FROM email_queue WHERE status = 'failed' ORDER BY created_at DESC LIMIT 10;
```
**Solutions:**
- Vérifier configuration SMTP (`ACKIFY_MAIL_HOST`, `ACKIFY_MAIL_USERNAME`, etc.)
- Vérifier identifiants SMTP corrects
- Vérifier logs worker email: `docker compose logs ackify-ce | grep email`
- S'assurer `ACKIFY_MAIL_FROM` est adresse expéditeur valide
- Tester connexion SMTP manuellement
#### 3. Erreur Signataire Dupliqué
**Problème**: "Email existe déjà comme signataire attendu"
**Cause**: Contrainte UNIQUE sur (doc_id, email)
**Solution**: Comportement attendu - chaque email ne peut être ajouté qu'une fois par document
#### 4. Checksum Non Correspondant
**Problème**: Utilisateurs rapportent checksum ne correspond pas
**Solutions:**
- Vérifier checksum stocké correspond document réel
- Vérifier algorithme utilisé (SHA-256, SHA-512, MD5)
- Recalculer checksum et mettre à jour via Éditer Métadonnées
- S'assurer utilisateurs téléchargent version correcte
#### 5. Document N'apparaît Pas
**Problème**: Document créé n'apparaît pas dans liste
**Solutions:**
- Vérifier si document supprimé en douce (`deleted_at IS NOT NULL`)
- Vérifier création réussie (vérifier réponse/logs)
- Vider cache navigateur
- Vérifier base: `SELECT * FROM documents WHERE doc_id = 'votre_id';`
#### 6. Signature Déjà Existe
**Problème**: Utilisateur ne peut pas signer document à nouveau
**Cause**: Contrainte UNIQUE (doc_id, user_sub) - une signature par utilisateur par document
**Solution**: Comportement attendu - utilisateurs ne peuvent pas signer même document deux fois
### Obtenir de l'Aide
**Logs:**
```bash
# Logs application
docker compose logs -f ackify-ce
# Logs base de données
docker compose logs -f ackify-db
# Logs worker email (grep email)
docker compose logs ackify-ce | grep -i email
```
**Inspection base de données:**
```bash
# Se connecter à PostgreSQL
docker compose exec ackify-db psql -U ackifyr ackify
# Requêtes utiles
SELECT * FROM documents ORDER BY created_at DESC LIMIT 10;
SELECT * FROM expected_signers WHERE doc_id = 'votre_doc_id';
SELECT * FROM email_queue WHERE status != 'sent' ORDER BY created_at DESC;
```
**Rapporter problèmes:**
- GitHub: https://github.com/btouchard/ackify-ce/issues
- Inclure logs et messages erreur
- Décrire comportement attendu vs réel
---
## Référence Rapide
### Variables Environnement
```bash
ACKIFY_ADMIN_EMAILS=admin@company.com
ACKIFY_MAIL_HOST=smtp.gmail.com
ACKIFY_MAIL_FROM=noreply@company.com
```
### Endpoints Clés
```
GET /admin # Dashboard
GET /admin/docs/{docId} # Détail document
POST /admin/documents/{docId}/signers # Ajouter signataire
POST /admin/documents/{docId}/reminders # Envoyer rappels
PUT /admin/documents/{docId}/metadata # Mettre à jour métadonnées
```
### Tables Importantes
- `documents` - Métadonnées documents
- `signatures` - Signatures utilisateurs
- `expected_signers` - Qui doit signer
- `reminder_logs` - Historique emails
- `email_queue` - File email async
### Raccourcis Clavier (Frontend)
- Barre recherche auto-focus sur dashboard
- Entrée pour soumettre formulaires
- Échap pour fermer modales
---
**Dernière Mise à Jour**: 2025-10-26
**Version**: 1.0.0