Remove docs and marketing files relocated to old_files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-07 07:09:06 -06:00
parent 4976eafc6c
commit 821a3e452f
13 changed files with 0 additions and 5188 deletions

View File

@@ -1,67 +0,0 @@
# Shit Deploy Can't Do
This is everything `./.deploy_prod` cannot safely automate for you.
## 1. Create Infrastructure
Step:
Create Hetzner servers, networking, and load balancer.
Reason:
The script only deploys app workloads. It cannot create paid cloud resources without cloud API credentials and IaC wiring.
## 2. Join Nodes To Swarm
Step:
Run `docker swarm init` on the first manager and `docker swarm join` on other nodes.
Reason:
Joining nodes requires one-time bootstrap tokens and host-level control.
## 3. Configure Firewall And Origin Restrictions
Step:
Set firewall rules so only expected ingress paths can reach your nodes.
Reason:
Firewall policies live in provider networking controls, outside this repo.
## 4. Configure DNS / Cloudflare
Step:
Point DNS at LB, enable proxying, set SSL mode, and lock down origin access.
Reason:
DNS and CDN settings are account-level operations in Cloudflare, not deploy-time app actions.
## 5. Configure External Services
Step:
Create and configure Neon, B2, email provider, APNS, and FCM credentials.
Reason:
These credentials are issued in vendor dashboards and must be manually generated/rotated.
## 6. Seed SSH Trust
Step:
Ensure your local machine can SSH to the manager with the key in `deploy/cluster.env`.
Reason:
The script assumes SSH already works; it cannot grant itself SSH access.
## 7. First-Time Smoke Testing Beyond `/api/health/`
Step:
Manually test login, push, background jobs, and admin panel flows after first deploy.
Reason:
Automated health checks prove container readiness, not end-to-end business behavior.
## 8. Safe Secret Garbage Collection
Step:
Periodically remove old versioned Docker secrets that are no longer referenced.
Reason:
This deploy script creates versioned secrets for safe rollouts and does not auto-delete old ones to avoid breaking running services.

File diff suppressed because it is too large Load Diff

View File

@@ -1,566 +0,0 @@
# Dokku Deployment Guide for honeyDue API
This guide provides step-by-step instructions for deploying the honeyDue Go API to a remote server using Dokku.
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Server Setup](#server-setup)
3. [Install Dokku](#install-dokku)
4. [Create the App](#create-the-app)
5. [Configure PostgreSQL](#configure-postgresql)
6. [Configure Redis](#configure-redis)
7. [Set Environment Variables](#set-environment-variables)
8. [Configure Storage](#configure-storage)
9. [Deploy the Application](#deploy-the-application)
10. [Configure SSL](#configure-ssl)
11. [Set Up Worker Process](#set-up-worker-process)
12. [Push Notifications (Optional)](#push-notifications-optional)
13. [Maintenance Commands](#maintenance-commands)
14. [Troubleshooting](#troubleshooting)
---
## Prerequisites
### Server Requirements
- Ubuntu 22.04 LTS (recommended) or 20.04 LTS
- Minimum 2GB RAM (4GB+ recommended for production)
- 20GB+ disk space
- Root or sudo access
- Domain name pointed to your server's IP
### Local Requirements
- Git installed
- SSH key configured for server access
---
## Server Setup
### 1. Connect to Your Server
```bash
ssh root@your-server-ip
```
### 2. Update System Packages
```bash
apt update && apt upgrade -y
```
### 3. Set Up Swap (Recommended for servers with limited RAM)
```bash
# Create 2GB swap file
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
# Make permanent
echo '/swapfile none swap sw 0 0' | tee -a /etc/fstab
```
### 4. Configure Firewall
```bash
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
```
---
## Install Dokku
### 1. Download and Install Dokku
```bash
# Download the installation script
wget -NP . https://dokku.com/install/v0.34.4/bootstrap.sh
# Run the installer (takes 5-10 minutes)
sudo DOKKU_TAG=v0.34.4 bash bootstrap.sh
```
### 2. Configure Dokku
```bash
# Set your domain (replace with your actual domain)
dokku domains:set-global honeyDue.treytartt.com
# Add your SSH public key for deployments
# Run this from your LOCAL machine:
cat ~/.ssh/id_rsa.pub | ssh root@your-server-ip dokku ssh-keys:add admin
```
### 3. Install Required Plugins
```bash
# PostgreSQL plugin
dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
# Redis plugin
dokku plugin:install https://github.com/dokku/dokku-redis.git redis
# Let's Encrypt plugin (for SSL)
dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
```
---
## Create the App
### 1. Create the Dokku App
```bash
dokku apps:create honeydue-api
```
### 2. Set the Domain
```bash
dokku domains:add honeydue-api api.honeyDue.treytartt.com
```
### 3. Configure Buildpack (if needed)
The app uses a Dockerfile, so Dokku will auto-detect it. If you need to force Docker builds:
```bash
dokku builder:set honeydue-api build-dir .
dokku builder-dockerfile:set honeydue-api dockerfile-path Dockerfile
```
---
## Configure PostgreSQL
### 1. Create PostgreSQL Service
```bash
# Create the database service
dokku postgres:create honeydue-db
# Link to the app (automatically sets DATABASE_URL)
dokku postgres:link honeydue-db honeydue-api
```
### 2. Verify Connection
```bash
# Check the connection info
dokku postgres:info honeydue-db
# Connect to the database
dokku postgres:connect honeydue-db
```
### 3. Set Individual Database Variables
Dokku sets `DATABASE_URL` automatically, but the app expects individual variables:
```bash
# Get the database credentials
dokku postgres:info honeydue-db
# Set individual variables (replace with actual values from info command)
dokku config:set honeydue-api \
DB_HOST=dokku-postgres-honeydue-db \
DB_PORT=5432 \
POSTGRES_DB=honeydue_db \
POSTGRES_USER=postgres \
POSTGRES_PASSWORD=1mJPfu6rzG9r6xukcGbUOU5NoCg0jKfa
```
---
## Configure Redis
### 1. Create Redis Service
```bash
# Create the Redis service
dokku redis:create honeydue-redis
# Link to the app (automatically sets REDIS_URL)
dokku redis:link honeydue-redis honeydue-api
```
### 2. Verify Connection
```bash
dokku redis:info honeydue-redis
```
---
## Set Environment Variables
### 1. Required Variables
```bash
dokku config:set honeydue-api \
PORT=5000 \
DEBUG=false \
ALLOWED_HOSTS=api.honeyDue.treytartt.com,localhost \
TIMEZONE=UTC \
SECRET_KEY=8553813eda361017a02677ed504abdd331537cfe6f7cc407345f037cc22c75fc
```
### 2. Email Configuration
```bash
dokku config:set honeydue-api \
EMAIL_HOST=smtp.fastmail.com \
EMAIL_PORT=587 \
EMAIL_USE_TLS=true \
EMAIL_HOST_USER=treytartt@fastmail.com \
EMAIL_HOST_PASSWORD=2t9y4n4t497z5863 \
DEFAULT_FROM_EMAIL="honeyDue <treytartt@fastmail.com>"
```
### 3. Apple Sign In (Optional)
```bash
dokku config:set honeydue-api \
APPLE_CLIENT_ID=com.tt.honeyDue.honeyDueDev \
APPLE_TEAM_ID=V3PF3M6B6U
```
### 4. Push Notifications (Optional)
The API uses direct APNs/FCM connections (no external push server needed):
```bash
dokku config:set honeydue-api \
APNS_AUTH_KEY_PATH=/push_certs/AuthKey_R9N3SM2WD5.p8 \
APNS_AUTH_KEY_ID=R9N3SM2WD5 \
APNS_TEAM_ID=V3PF3M6B6U \
APNS_TOPIC=com.tt.honeyDue.honeyDueDev \
APNS_PRODUCTION=true \
FCM_SERVER_KEY=your-firebase-server-key
```
### 5. Admin Panel URL
```bash
dokku config:set honeydue-api \
NEXT_PUBLIC_API_URL=https://api.honeyDue.treytartt.com
```
### 6. View All Configuration
```bash
dokku config:show honeydue-api
```
---
## Configure Storage
### 1. Create Persistent Storage Directory
```bash
# Create storage directory on host
mkdir -p /var/lib/dokku/data/storage/honeydue-api/uploads
# Set permissions
chown -R 32767:32767 /var/lib/dokku/data/storage/honeydue-api
```
### 2. Mount Storage to App
```bash
dokku storage:mount honeydue-api /var/lib/dokku/data/storage/honeydue-api/uploads:/app/uploads
```
---
## Deploy the Application
### 1. Add Dokku Remote (Local Machine)
```bash
cd /path/to/honeyDueAPI-go
git remote add dokku dokku@your-server-ip:honeydue-api
```
### 2. Deploy
```bash
git push dokku master:main
# Or if your branch is already 'main':
git push dokku main
```
### 3. Watch Deployment Logs
```bash
# On server
dokku logs honeydue-api -t
```
### 4. Verify Deployment
```bash
# Check app status
dokku ps:report honeydue-api
# Check app is running
curl https://api.honeyDue.treytartt.com/api/health/
```
---
## Configure SSL
### 1. Set Let's Encrypt Email
```bash
dokku letsencrypt:set honeydue-api email admin@treytartt.com
```
### 2. Enable Let's Encrypt
```bash
dokku letsencrypt:enable honeydue-api
```
### 3. Set Up Auto-Renewal
```bash
dokku letsencrypt:cron-job --add
```
---
## Set Up Worker Process
The worker process handles background jobs (task reminders, overdue alerts, daily digests, email sending, etc.).
### 1. Configure Worker Environment Variables
```bash
dokku config:set honeydue-api \
TASK_REMINDER_HOUR=20 \
TASK_REMINDER_MINUTE=0 \
OVERDUE_REMINDER_HOUR=9 \
DAILY_DIGEST_HOUR=11
```
**Schedule times are in UTC:**
| Variable | Default | Description |
|----------|---------|-------------|
| `TASK_REMINDER_HOUR` | 20 | Hour to send "task due soon" notifications (8 PM UTC) |
| `TASK_REMINDER_MINUTE` | 0 | Minute for task reminder |
| `OVERDUE_REMINDER_HOUR` | 9 | Hour to send overdue task alerts (9 AM UTC) |
| `DAILY_DIGEST_HOUR` | 11 | Hour to send daily summary (11 AM UTC) |
### 2. Scale Worker Process
```bash
# Scale to 1 worker
dokku ps:scale honeydue-api worker=1
# Verify processes
dokku ps:report honeydue-api
```
### 3. Verify Worker is Running
```bash
# Check worker logs
dokku logs honeydue-api -p worker
# Should see:
# "Registered task reminder job"
# "Registered overdue reminder job"
# "Registered daily digest job"
# "Starting worker server..."
```
---
## Maintenance Commands
### View Logs
```bash
# Real-time logs
dokku logs honeydue-api -t
# Last 100 lines
dokku logs honeydue-api -n 100
# Worker logs
dokku logs honeydue-api -p worker
```
### Database Operations
```bash
# Connect to database
dokku postgres:connect honeydue-db
# Export database backup
dokku postgres:export honeydue-db > backup.sql
# Import database backup
dokku postgres:import honeydue-db < backup.sql
```
### Run Migrations Manually
```bash
# Enter the app container
dokku enter honeydue-api web
# Migrations run automatically on startup, but if needed:
/app/api migrate
```
### Restart App
```bash
dokku ps:restart honeydue-api
```
### Scale App
```bash
# Scale web process
dokku ps:scale honeydue-api web=2 worker=1
# View current scale
dokku ps:report honeydue-api
```
### Stop/Start App
```bash
dokku ps:stop honeydue-api
dokku ps:start honeydue-api
```
---
## Troubleshooting
### Check App Status
```bash
dokku ps:report honeydue-api
dokku logs honeydue-api -n 200
```
### Common Issues
#### 1. App Won't Start
```bash
# Check logs for errors
dokku logs honeydue-api -n 500
# Verify environment variables
dokku config:show honeydue-api
# Check if ports are available
dokku proxy:ports honeydue-api
```
#### 2. Database Connection Failed
```bash
# Verify link
dokku postgres:linked honeydue-api honeydue-db
# Check database is running
dokku postgres:info honeydue-db
# Re-link if needed
dokku postgres:unlink honeydue-db honeydue-api
dokku postgres:link honeydue-db honeydue-api
```
#### 3. Redis Connection Failed
```bash
# Verify link
dokku redis:linked honeydue-api honeydue-redis
# Check Redis is running
dokku redis:info honeydue-redis
```
#### 4. Storage/Upload Issues
```bash
# Check mounts
dokku storage:report honeydue-api
# Verify permissions
ls -la /var/lib/dokku/data/storage/honeydue-api/
```
#### 5. SSL Certificate Issues
```bash
# Check certificate status
dokku letsencrypt:list
# Renew manually
dokku letsencrypt:enable honeydue-api
```
### View Resource Usage
```bash
# Docker stats for all containers
docker stats
# Disk usage
dokku storage:report honeydue-api
df -h
```
---
## Quick Reference
| Command | Description |
|---------|-------------|
| `dokku apps:list` | List all apps |
| `dokku logs honeydue-api -t` | Tail logs |
| `dokku ps:restart honeydue-api` | Restart app |
| `dokku config:show honeydue-api` | Show env vars |
| `dokku postgres:connect honeydue-db` | Connect to DB |
| `dokku enter honeydue-api web` | Shell into container |
| `dokku ps:scale honeydue-api web=2` | Scale processes |
---
## Environment Variables Reference
| Variable | Required | Description |
|----------|----------|-------------|
| `SECRET_KEY` | Yes | 32+ character secret key |
| `DEBUG` | Yes | Set to `false` in production |
| `ALLOWED_HOSTS` | Yes | Comma-separated list of domains |
| `DATABASE_URL` | Auto | Set by postgres:link |
| `REDIS_URL` | Auto | Set by redis:link |
| `EMAIL_HOST` | Yes | SMTP server |
| `EMAIL_PORT` | Yes | SMTP port (587) |
| `EMAIL_HOST_USER` | Yes | SMTP username |
| `EMAIL_HOST_PASSWORD` | Yes | SMTP password |
| `APPLE_CLIENT_ID` | No | iOS Bundle ID |
| `APPLE_TEAM_ID` | No | Apple Developer Team ID |
| `APNS_AUTH_KEY_PATH` | No | Path to APNs .p8 key |
| `APNS_AUTH_KEY_ID` | No | APNs Key ID |
| `APNS_TEAM_ID` | No | APNs Team ID |
| `APNS_TOPIC` | No | APNs topic (bundle ID) |
| `APNS_PRODUCTION` | No | Use production APNs (default: false) |
| `FCM_SERVER_KEY` | No | Firebase Cloud Messaging server key |

View File

@@ -1,260 +0,0 @@
# Go To Prod Plan
This document is a phased production-readiness plan for the honeyDue Go API repo.
Execute phases in order. Do not skip exit criteria.
## How To Use This Plan
1. Create an issue/epic per phase.
2. Track each checklist item as a task.
3. Only advance phases after all exit criteria pass in CI and staging.
## Phase 0 - Baseline And Drift Cleanup
Goal: eliminate known repo/config drift before hardening.
### Tasks
1. Fix stale admin build/run targets in [`Makefile`](/Users/treyt/Desktop/code/HoneyDueAPI_GO/Makefile) that reference `cmd/admin` (non-existent).
2. Align worker env vars in [`docker-compose.yml`](/Users/treyt/Desktop/code/HoneyDueAPI_GO/docker-compose.yml) with Go config:
- use `TASK_REMINDER_HOUR`
- use `OVERDUE_REMINDER_HOUR`
- use `DAILY_DIGEST_HOUR`
3. Align supported locales in [`internal/i18n/i18n.go`](/Users/treyt/Desktop/code/HoneyDueAPI_GO/internal/i18n/i18n.go) with translation files in [`internal/i18n/translations`](/Users/treyt/Desktop/code/HoneyDueAPI_GO/internal/i18n/translations).
4. Remove any committed secrets/keys from repo and history; rotate immediately.
### Validation
1. `go test ./...`
2. `go build ./cmd/api ./cmd/worker`
3. `docker compose config` succeeds.
### Exit Criteria
1. No stale targets or mismatched env keys remain.
2. CI and local boot work with a single source-of-truth config model.
---
## Phase 1 - Non-Negotiable CI Gates
Goal: block regressions by policy.
### Tasks
1. Update [`/.github/workflows/backend-ci.yml`](/Users/treyt/Desktop/code/HoneyDueAPI_GO/.github/workflows/backend-ci.yml) with required jobs:
- `lint` (`go vet ./...`, `gofmt -l .`)
- `test` (`go test -race -count=1 ./...`)
- `contract` (`go test -v -run "TestRouteSpecContract|TestKMPSpecContract" ./internal/integration/`)
- `build` (`go build ./cmd/api ./cmd/worker`)
2. Add `govulncheck ./...` job.
3. Add secret scanning (for example, gitleaks).
4. Set branch protection on `main` and `develop`:
- require PR
- require all status checks
- require at least one review
- dismiss stale reviews on new commits
### Validation
1. Open test PR with intentional formatting error; ensure merge is blocked.
2. Open test PR with OpenAPI/route drift; ensure merge is blocked.
### Exit Criteria
1. No direct merge path exists without passing all gates.
---
## Phase 2 - Contract, Data, And Migration Safety
Goal: guarantee deploy safety for API behavior and schema changes.
### Tasks
1. Keep OpenAPI as source of truth in [`docs/openapi.yaml`](/Users/treyt/Desktop/code/HoneyDueAPI_GO/docs/openapi.yaml).
2. Require route/schema updates in same PR as handler changes.
3. Add migration checks in CI:
- migrate up on clean DB
- migrate down one step
- migrate up again
4. Add DB constraints for business invariants currently enforced only in service code.
5. Add idempotency protections for webhook/job handlers.
### Validation
1. Run migration smoke test pipeline against ephemeral Postgres.
2. Re-run integration contract tests after each endpoint change.
### Exit Criteria
1. Schema changes are reversible and validated before merge.
2. API contract drift is caught pre-merge.
---
## Phase 3 - Test Hardening For Failure Modes
Goal: increase confidence in edge cases and concurrency.
### Tasks
1. Add table-driven tests for task lifecycle transitions:
- cancel/uncancel
- archive/unarchive
- complete/quick-complete
- recurring next due date transitions
2. Add timezone boundary tests around midnight and DST.
3. Add concurrency tests for race-prone flows in services/repositories.
4. Add fuzz/property tests for:
- task categorization predicates
- reminder schedule logic
5. Add unauthorized-access tests for media/document/task cross-residence access.
### Validation
1. `go test -race -count=1 ./...` stays green.
2. New tests fail when logic is intentionally broken (mutation spot checks).
### Exit Criteria
1. High-risk flows have explicit edge-case coverage.
---
## Phase 4 - Security Hardening
Goal: reduce breach and abuse risk.
### Tasks
1. Add strict request size/time limits for upload and auth endpoints.
2. Add rate limits for:
- login
- forgot/reset password
- verification endpoints
- webhooks
3. Ensure logs redact secrets/tokens/PII payloads.
4. Enforce least-privilege for runtime creds and service accounts.
5. Enable dependency update cadence with security review.
### Validation
1. Abuse test scripts for brute-force and oversized payload attempts.
2. Verify logs do not expose secrets under failure paths.
### Exit Criteria
1. Security scans pass and abuse protections are enforced in runtime.
---
## Phase 5 - Observability And Operations
Goal: make production behavior measurable and actionable.
### Tasks
1. Standardize request correlation IDs across API and worker logs.
2. Define SLOs:
- API availability
- p95 latency for key endpoints
- worker queue delay
3. Add dashboards + alerts for:
- 5xx error rate
- auth failures
- queue depth/retry spikes
- DB latency
4. Add dead-letter queue review and replay procedure.
5. Document incident runbooks in [`docs/`](/Users/treyt/Desktop/code/HoneyDueAPI_GO/docs):
- DB outage
- Redis outage
- push provider outage
- webhook backlog
### Validation
1. Trigger synthetic failures in staging and confirm alerts fire.
2. Execute at least one incident drill and capture MTTR.
### Exit Criteria
1. Team can detect and recover from common failures quickly.
---
## Phase 6 - Performance And Capacity
Goal: prove headroom before production growth.
### Tasks
1. Define load profiles for hot endpoints:
- `/api/tasks/`
- `/api/static_data/`
- `/api/auth/login/`
2. Run load and soak tests in staging.
3. Capture query plans for slow SQL and add indexes where needed.
4. Validate Redis/cache fallback behavior under cache loss.
5. Tune worker concurrency and queue weights from measured data.
### Validation
1. Meet agreed latency/error SLOs under target load.
2. No sustained queue growth under steady-state load.
### Exit Criteria
1. Capacity plan is documented with clear limits and scaling triggers.
---
## Phase 7 - Release Discipline And Recovery
Goal: safe deployments and verified rollback/recovery.
### Tasks
1. Adopt canary or blue/green deploy strategy.
2. Add automatic rollback triggers based on SLO violations.
3. Add pre-deploy checklist:
- migrations reviewed
- CI green
- queue backlog healthy
- dependencies healthy
4. Validate backups with restore drills (not just backup existence).
5. Document RPO/RTO targets and current measured reality.
### Validation
1. Perform one full staging rollback rehearsal.
2. Perform one restore-from-backup rehearsal.
### Exit Criteria
1. Deploy and rollback are repeatable, scripted, and tested.
---
## Definition Of Done (Every PR)
1. `go vet ./...`
2. `gofmt -l .` returns no files
3. `go test -race -count=1 ./...`
4. Contract tests pass
5. OpenAPI updated for endpoint changes
6. Migrations added and reversible for schema changes
7. Security impact reviewed for auth/uploads/media/webhooks
8. Observability impact reviewed for new critical paths
---
## Recommended Execution Timeline
1. Week 1: Phase 0 + Phase 1
2. Week 2: Phase 2
3. Week 3-4: Phase 3 + Phase 4
4. Week 5: Phase 5
5. Week 6: Phase 6 + Phase 7 rehearsal
Adjust timeline based on team size and release pressure, but keep ordering.

View File

@@ -1,412 +0,0 @@
# Full Localization Plan for honeyDue
## Overview
Complete localization of the honeyDue property management app across three codebases:
- **Go API** - Server-side localization of errors, emails, push notifications, lookup data
- **KMM/Android** - Compose Multiplatform string resources
- **iOS** - Apple String Catalogs (.xcstrings)
**Target Languages**: English (base), Spanish, French, German, Portuguese (extensible)
**Key Decisions**:
- API returns localized strings (server-side translation)
- Accept-Language header determines locale
- Clients display what API returns for API content
- Clients localize their own UI strings
---
## Part 1: Go API Localization
### 1.1 Add Dependency
```bash
go get github.com/nicksnyder/go-i18n/v2
```
### 1.2 Directory Structure
```
honeyDueAPI-go/
├── internal/
│ └── i18n/
│ ├── i18n.go # Core setup, bundle, T() helper
│ ├── middleware.go # Gin middleware for Accept-Language
│ └── translations/
│ ├── en.json # English (base)
│ ├── es.json # Spanish
│ ├── fr.json # French
│ ├── de.json # German
│ └── pt.json # Portuguese
├── templates/
│ └── emails/
│ ├── en/
│ │ ├── welcome.html
│ │ ├── verification.html
│ │ └── password_reset.html
│ ├── es/
│ ├── fr/
│ ├── de/
│ └── pt/
```
### 1.3 Core Files to Create
**internal/i18n/i18n.go**:
- Initialize i18n bundle with embedded translation files
- Provide `T(localizer, messageID, data)` helper function
- Load all JSON translation files at startup
**internal/i18n/middleware.go**:
- Parse Accept-Language header
- Match against supported languages (en, es, fr, de, pt)
- Store localizer in Gin context
- Provide `GetLocalizer(c)` helper
### 1.4 Translation File Format
```json
{
"error.invalid_credentials": "Invalid credentials",
"error.username_taken": "Username already taken",
"error.email_taken": "Email already registered",
"error.not_authenticated": "Not authenticated",
"error.task_not_found": "Task not found",
"error.residence_access_denied": "You don't have access to this property",
"message.logged_out": "Logged out successfully",
"message.task_deleted": "Task deleted successfully",
"push.task_due_soon.title": "Task Due Soon",
"push.task_due_soon.body": "{{.TaskTitle}} is due {{.DueDate}}"
}
```
### 1.5 Handler Update Pattern
```go
// Before
c.JSON(400, gin.H{"error": "Invalid credentials"})
// After
localizer := i18n.GetLocalizer(c)
c.JSON(400, gin.H{"error": i18n.T(localizer, "error.invalid_credentials", nil)})
```
### 1.6 Database for Lookup Translations
Add translation table for task categories, priorities, statuses, frequencies:
```sql
CREATE TABLE lookup_translations (
id SERIAL PRIMARY KEY,
table_name VARCHAR(50) NOT NULL,
record_id INT NOT NULL,
locale VARCHAR(10) NOT NULL,
field_name VARCHAR(50) NOT NULL,
translated_value TEXT NOT NULL,
UNIQUE(table_name, record_id, locale, field_name)
);
```
Update lookup endpoints to return translated names based on locale.
### 1.7 Critical Go Files to Modify
| File | Action | Description |
|------|--------|-------------|
| `internal/i18n/i18n.go` | CREATE | Core i18n bundle and helpers |
| `internal/i18n/middleware.go` | CREATE | Locale detection middleware |
| `internal/i18n/translations/*.json` | CREATE | Translation files (5 languages) |
| `internal/router/router.go` | MODIFY | Add i18n middleware |
| `internal/handlers/auth_handler.go` | MODIFY | Localize ~22 error strings |
| `internal/handlers/task_handler.go` | MODIFY | Localize ~30 error strings |
| `internal/handlers/residence_handler.go` | MODIFY | Localize ~20 error strings |
| `internal/handlers/contractor_handler.go` | MODIFY | Localize ~15 error strings |
| `internal/handlers/document_handler.go` | MODIFY | Localize ~15 error strings |
| `internal/services/email_service.go` | MODIFY | Template-based emails |
| `internal/services/notification_service.go` | MODIFY | Localized push content |
| `internal/services/lookup_service.go` | MODIFY | Return translated lookups |
| `templates/emails/**` | CREATE | Email templates per language |
---
## Part 2: KMM/Android Localization
### 2.1 Strategy
Use Compose Multiplatform Resources (already in build.gradle.kts via `compose.components.resources`).
### 2.2 Directory Structure
```
HoneyDueKMM/composeApp/src/commonMain/
└── composeResources/
├── values/
│ └── strings.xml # English (base)
├── values-es/
│ └── strings.xml # Spanish
├── values-fr/
│ └── strings.xml # French
├── values-de/
│ └── strings.xml # German
└── values-pt/
└── strings.xml # Portuguese
```
### 2.3 String Resource Format
```xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Auth -->
<string name="auth_login_title">Sign In</string>
<string name="auth_login_subtitle">Manage your properties with ease</string>
<string name="auth_login_username_label">Username or Email</string>
<string name="auth_login_password_label">Password</string>
<string name="auth_login_button">Sign In</string>
<string name="auth_forgot_password">Forgot Password?</string>
<!-- Properties -->
<string name="properties_title">My Properties</string>
<string name="properties_empty_title">No properties yet</string>
<string name="properties_empty_subtitle">Add your first property to get started!</string>
<string name="properties_add_button">Add Property</string>
<!-- Tasks -->
<string name="tasks_title">Tasks</string>
<string name="tasks_add_title">Add New Task</string>
<string name="tasks_column_overdue">Overdue</string>
<string name="tasks_column_due_soon">Due Soon</string>
<!-- Common -->
<string name="common_save">Save</string>
<string name="common_cancel">Cancel</string>
<string name="common_delete">Delete</string>
<string name="common_loading">Loading…</string>
<string name="common_error_generic">Something went wrong. Please try again.</string>
<!-- Accessibility -->
<string name="a11y_back">Back</string>
<string name="a11y_close">Close</string>
<string name="a11y_add_property">Add Property</string>
</resources>
```
### 2.4 Usage in Compose
```kotlin
import honeydue.composeapp.generated.resources.Res
import honeydue.composeapp.generated.resources.*
import org.jetbrains.compose.resources.stringResource
@Composable
fun LoginScreen() {
Text(stringResource(Res.string.auth_login_title))
Button(onClick = { /* ... */ }) {
Text(stringResource(Res.string.auth_login_button))
}
}
```
### 2.5 Critical KMM Files to Modify
| File | Action | Description |
|------|--------|-------------|
| `composeResources/values/strings.xml` | CREATE | Base English strings (~500) |
| `composeResources/values-es/strings.xml` | CREATE | Spanish translations |
| `composeResources/values-fr/strings.xml` | CREATE | French translations |
| `composeResources/values-de/strings.xml` | CREATE | German translations |
| `composeResources/values-pt/strings.xml` | CREATE | Portuguese translations |
| `ui/screens/LoginScreen.kt` | MODIFY | Replace hardcoded strings |
| `ui/screens/ResidencesScreen.kt` | MODIFY | Replace hardcoded strings |
| `ui/screens/TasksScreen.kt` | MODIFY | Replace hardcoded strings |
| `ui/screens/ContractorsScreen.kt` | MODIFY | Replace hardcoded strings |
| `ui/screens/DocumentsScreen.kt` | MODIFY | Replace hardcoded strings |
| `ui/components/*.kt` | MODIFY | Replace hardcoded strings (33 files) |
| All other screen files | MODIFY | Replace hardcoded strings |
---
## Part 3: iOS Localization
### 3.1 Strategy
Use Apple String Catalogs (`.xcstrings`) - modern approach with Xcode visual editor.
### 3.2 Create String Catalog
1. Xcode: File > New > File > String Catalog
2. Name: `Localizable.xcstrings`
3. Add languages: English, Spanish, French, German, Portuguese
### 3.3 Type-Safe String Access
Create `iosApp/iosApp/Helpers/L10n.swift`:
```swift
import Foundation
enum L10n {
enum Auth {
static let loginTitle = String(localized: "auth_login_title")
static let loginSubtitle = String(localized: "auth_login_subtitle")
static let loginUsernameLabel = String(localized: "auth_login_username_label")
static let loginPasswordLabel = String(localized: "auth_login_password_label")
static let loginButton = String(localized: "auth_login_button")
static let forgotPassword = String(localized: "auth_forgot_password")
}
enum Properties {
static let title = String(localized: "properties_title")
static let emptyTitle = String(localized: "properties_empty_title")
static let emptySubtitle = String(localized: "properties_empty_subtitle")
static let addButton = String(localized: "properties_add_button")
}
enum Tasks {
static let title = String(localized: "tasks_title")
static let addTitle = String(localized: "tasks_add_title")
static let columnOverdue = String(localized: "tasks_column_overdue")
static let columnDueSoon = String(localized: "tasks_column_due_soon")
}
enum Common {
static let save = String(localized: "common_save")
static let cancel = String(localized: "common_cancel")
static let delete = String(localized: "common_delete")
static let loading = String(localized: "common_loading")
static let errorGeneric = String(localized: "common_error_generic")
}
}
```
### 3.4 Usage in SwiftUI
```swift
// Before
Text("My Properties")
.navigationTitle("My Properties")
// After
Text(L10n.Properties.title)
.navigationTitle(L10n.Properties.title)
```
### 3.5 Critical iOS Files to Modify
| File | Action | Description |
|------|--------|-------------|
| `iosApp/Localizable.xcstrings` | CREATE | String catalog with all translations |
| `iosApp/Helpers/L10n.swift` | CREATE | Type-safe string access |
| `Login/LoginView.swift` | MODIFY | Replace ~7 hardcoded strings |
| `Login/LoginViewModel.swift` | MODIFY | Replace ~12 error messages |
| `Register/RegisterView.swift` | MODIFY | Replace ~10 hardcoded strings |
| `Residence/*.swift` | MODIFY | Replace ~30 hardcoded strings |
| `Task/*.swift` | MODIFY | Replace ~50 hardcoded strings |
| `Contractor/*.swift` | MODIFY | Replace ~20 hardcoded strings |
| `Document/*.swift` | MODIFY | Replace ~15 hardcoded strings |
| `Profile/*.swift` | MODIFY | Replace ~20 hardcoded strings |
| All other view files | MODIFY | Replace hardcoded strings |
---
## Part 4: Implementation Order
### Phase 1: Infrastructure (Do First)
1. **Go API**:
- Add go-i18n dependency
- Create `internal/i18n/` package with i18n.go and middleware.go
- Create base `en.json` with all extractable strings
- Add middleware to router
- Test with curl using Accept-Language header
2. **KMM**:
- Create `composeResources/values/strings.xml` with ~50 core strings
- Verify Compose resources compile correctly
- Update one screen (LoginScreen) as proof of concept
3. **iOS**:
- Create `Localizable.xcstrings` in Xcode
- Create `L10n.swift` helper
- Add ~50 core strings
- Update LoginView as proof of concept
### Phase 2: API Full Localization
1. Update all handlers to use localized errors
2. Add lookup_translations table and seed data
3. Update lookup service to return translated names
4. Move email templates to files, create Spanish versions
5. Update push notification service for localized content
### Phase 3: Mobile String Extraction
**Order by feature (same for KMM and iOS)**:
1. Auth screens (Login, Register, Verify, Password Reset, Apple Sign In)
2. Property screens (List, Detail, Form, Join, Share, Manage Users)
3. Task screens (List, Detail, Form, Complete, Actions, Kanban)
4. Contractor screens (List, Detail, Form)
5. Document screens (List, Detail, Form, Warranties)
6. Profile screens (Profile, Settings, Notifications)
7. Common components (Dialogs, Cards, Empty States, Loading)
### Phase 4: Create Translation Files
- Create es.json, fr.json, de.json, pt.json for Go API
- Create values-es/, values-fr/, values-de/, values-pt/ for KMM
- Add all language translations to iOS String Catalog
### Phase 5: Testing & Polish
- Test all screens in each language
- Verify email rendering in each language
- Test push notifications
- Verify lookup data translations
- Handle edge cases (long strings, RTL future-proofing)
---
## String Naming Convention
Use consistent keys across all platforms:
```
<category>_<screen/feature>_<element>
Examples:
auth_login_title
auth_login_button
properties_list_empty_title
properties_form_field_name
tasks_detail_button_complete
common_button_save
common_button_cancel
error_network_timeout
a11y_button_back
```
---
## Estimated String Counts
| Platform | Approximate Strings |
|----------|---------------------|
| Go API - Errors | ~100 |
| Go API - Email templates | ~50 per language |
| Go API - Push notifications | ~20 |
| Go API - Lookup data | ~50 |
| KMM/Android | ~500 |
| iOS | ~500 |
| **Total unique strings** | ~700-800 |
---
## Translation Workflow
1. **Extract**: All English strings defined first
2. **Export**: JSON (Go), XML (Android), .xcstrings (iOS)
3. **Translate**: Use Lokalise, Crowdin, or manual translation
4. **Import**: Place translated files in correct locations
5. **Test**: Verify in each language

View File

@@ -1,294 +0,0 @@
# Push Notifications Architecture
This document describes how push notifications work in the honeyDue API.
## Overview
The honeyDue API sends push notifications directly to Apple Push Notification service (APNs) and Firebase Cloud Messaging (FCM) without any intermediate push server. This approach:
- Reduces infrastructure complexity (no Gorush or other push server needed)
- Provides direct control over notification payloads
- Supports actionable notifications with custom button types
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────┐
│ honeyDue API │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌─────────────────────┐ ┌───────────────────┐ │
│ │ Handlers │───▶│ NotificationService │───▶│ PushClient │ │
│ │ (HTTP API) │ │ │ │ │ │
│ └──────────────┘ │ - Create record │ │ ┌─────────────┐ │ │
│ │ - Get button types │ │ │ APNsClient │──┼───┼──▶ APNs
│ ┌──────────────┐ │ - Send push │ │ │ (apns2) │ │ │
│ │ Worker │───▶│ │ │ └─────────────┘ │ │
│ │ (Asynq) │ └─────────────────────┘ │ │ │
│ └──────────────┘ │ ┌─────────────┐ │ │
│ │ │ FCMClient │──┼───┼──▶ FCM
│ │ │ (HTTP) │ │ │
│ │ └─────────────┘ │ │
│ └───────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
## Components
### 1. Push Client (`internal/push/client.go`)
The unified push client that wraps both APNs and FCM clients:
```go
type Client struct {
apns *APNsClient // iOS
fcm *FCMClient // Android
}
```
**Key Methods:**
- `SendToIOS()` - Send to iOS devices
- `SendToAndroid()` - Send to Android devices
- `SendToAll()` - Send to both platforms
- `SendActionableNotification()` - Send with iOS category for action buttons
### 2. APNs Client (`internal/push/apns.go`)
Uses the [sideshow/apns2](https://github.com/sideshow/apns2) library for direct APNs communication:
- **Token-based authentication** using .p8 key file
- **Production/Sandbox** modes based on configuration
- **Batch sending** with per-token error handling
- **Category support** for actionable notifications
### 3. FCM Client (`internal/push/fcm.go`)
Uses direct HTTP calls to the FCM legacy API (`https://fcm.googleapis.com/fcm/send`):
- **Server key authentication**
- **Batch sending** via `registration_ids`
- **Data payload** for Android action handling
### 4. Notification Service (`internal/services/notification_service.go`)
Orchestrates notification creation and delivery:
```go
func (s *NotificationService) CreateAndSendTaskNotification(
ctx context.Context,
userID uint,
notifType models.NotificationType,
task *models.Task,
) error
```
**Responsibilities:**
1. Create notification record in database
2. Determine button types based on task state
3. Map button types to iOS category
4. Get user's device tokens
5. Send via PushClient
### 5. Task Button Types (`internal/services/task_button_types.go`)
Determines which action buttons to show based on task state:
| Task State | Button Types |
|------------|--------------|
| Overdue | `edit`, `complete`, `cancel`, `mark_in_progress` |
| Due Soon | `edit`, `complete`, `cancel`, `mark_in_progress` |
| In Progress | `edit`, `complete`, `cancel` |
| Cancelled | `edit` |
| Completed | `edit` |
## Scheduled Notifications
The worker process (`cmd/worker/main.go`) sends scheduled notifications using Asynq:
### Schedule
| Job | Default Time (UTC) | Config Variable |
|-----|-------------------|-----------------|
| Task Reminders | 8:00 PM | `TASK_REMINDER_HOUR`, `TASK_REMINDER_MINUTE` |
| Overdue Alerts | 9:00 AM | `OVERDUE_REMINDER_HOUR` |
| Daily Digest | 11:00 AM | `DAILY_DIGEST_HOUR` |
### Job Handlers (`internal/worker/jobs/handler.go`)
1. **HandleTaskReminder** - Sends actionable notifications for tasks due today/tomorrow
2. **HandleOverdueReminder** - Sends actionable notifications for overdue tasks
3. **HandleDailyDigest** - Sends summary notification with task statistics
## iOS Categories
iOS actionable notifications use categories to define available actions. The API sends a `category` field that maps to categories registered in the iOS app:
| Category ID | Actions |
|-------------|---------|
| `TASK_ACTIONABLE` | Complete, Edit, Skip, In Progress |
| `TASK_IN_PROGRESS` | Complete, Edit, Cancel |
| `TASK_CANCELLED` | Edit |
| `TASK_COMPLETED` | Edit |
The iOS app must register these categories in `AppDelegate`:
```swift
let completeAction = UNNotificationAction(identifier: "COMPLETE", title: "Complete")
let editAction = UNNotificationAction(identifier: "EDIT", title: "Edit")
// ... more actions
let taskCategory = UNNotificationCategory(
identifier: "TASK_ACTIONABLE",
actions: [completeAction, editAction, skipAction, inProgressAction],
intentIdentifiers: []
)
```
## Configuration
### Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `APNS_AUTH_KEY_PATH` | For iOS | Path to .p8 key file |
| `APNS_AUTH_KEY_ID` | For iOS | Key ID from Apple Developer |
| `APNS_TEAM_ID` | For iOS | Team ID from Apple Developer |
| `APNS_TOPIC` | For iOS | Bundle ID (e.g., `com.tt.honeyDue.honeyDueDev`) |
| `APNS_PRODUCTION` | No | `true` for production, `false` for sandbox |
| `APNS_USE_SANDBOX` | No | Deprecated, use `APNS_PRODUCTION` |
| `FCM_SERVER_KEY` | For Android | Firebase Cloud Messaging server key |
### Docker/Dokku Setup
Mount the .p8 key file:
```bash
# Docker
volumes:
- ./push_certs:/certs:ro
# Dokku
dokku storage:mount honeydue-api /path/to/push_certs:/certs
```
## Data Flow
### Real-time Notification (e.g., task assigned)
```
1. User assigns task via API
2. Handler calls NotificationService.CreateAndSendTaskNotification()
3. NotificationService:
a. Creates Notification record in database
b. Calls GetButtonTypesForTask() to determine actions
c. Maps buttons to iOS category
d. Gets user's device tokens from APNSDevice/GCMDevice tables
e. Calls PushClient.SendActionableNotification()
4. PushClient sends to APNs and/or FCM
5. Device receives notification with action buttons
```
### Scheduled Notification (e.g., daily overdue alert)
```
1. Asynq scheduler triggers job at configured time
2. Worker calls HandleOverdueReminder()
3. Handler:
a. Queries all overdue tasks with Preloads
b. Groups tasks by user (assigned_to or residence owner)
c. Checks user's notification preferences
d. For each user with enabled notifications:
- Sends up to 5 individual actionable notifications
- Sends summary if more than 5 tasks
4. NotificationService creates records and sends via PushClient
```
## Device Token Management
Device tokens are stored in two tables:
- `push_notifications_apnsdevice` - iOS device tokens
- `push_notifications_gcmdevice` - Android device tokens (FCM)
Fields:
- `registration_id` - The device token
- `user_id` - Associated user
- `active` - Whether to send to this device
- `device_id` - Unique device identifier
Tokens are registered via:
- `POST /api/push/apns/` - Register iOS device
- `POST /api/push/gcm/` - Register Android device
## Error Handling
### APNs Errors
The APNs client handles individual token failures:
- Logs each failure with token prefix and reason
- Continues sending to remaining tokens
- Returns error only if ALL tokens fail
Common APNs errors:
- `BadDeviceToken` - Invalid or expired token
- `Unregistered` - App uninstalled
- `TopicDisallowed` - Bundle ID mismatch
### FCM Errors
The FCM client parses the response to identify failed tokens:
- Logs failures with error type
- Returns error only if ALL tokens fail
Common FCM errors:
- `InvalidRegistration` - Invalid token
- `NotRegistered` - App uninstalled
- `MismatchSenderId` - Wrong server key
## Debugging
### Check Push Configuration
```go
log.Info().
Bool("ios_enabled", pushClient.IsIOSEnabled()).
Bool("android_enabled", pushClient.IsAndroidEnabled()).
Msg("Push notification client initialized")
```
### View Worker Logs
```bash
# Docker
docker-compose logs -f worker
# Dokku
dokku logs honeydue-api -p worker
```
### Test Push Manually
The API has a test endpoint (debug mode only):
```bash
curl -X POST https://api.example.com/api/push/test/ \
-H "Authorization: Token <token>" \
-H "Content-Type: application/json" \
-d '{"title": "Test", "message": "Hello"}'
```
## Migration from Gorush
The API previously used Gorush as an intermediate push server. The migration to direct APNs/FCM involved:
1. **Added** `internal/push/apns.go` - Direct APNs using apns2 library
2. **Added** `internal/push/fcm.go` - Direct FCM using HTTP
3. **Added** `internal/push/client.go` - Unified client wrapper
4. **Removed** Gorush service from docker-compose.yml
5. **Removed** `GORUSH_URL` configuration
Benefits of direct approach:
- No additional container/service to manage
- Lower latency (one less hop)
- Full control over notification payload
- Simplified debugging

View File

@@ -1,281 +0,0 @@
# Secure Media Access Control Plan
## Problem Statement
Users upload private images and documents (task completion photos, warranties, documents) that may contain sensitive information. Currently, these files are **publicly accessible** via predictable URLs (`/uploads/{category}/{timestamp}_{uuid}.{ext}`). Anyone who knows or guesses a URL can access the file without authentication.
**Goal**: Ensure only users with access to a residence can view media files associated with that residence.
## User Decisions
| Decision | Choice |
|----------|--------|
| **Security Method** | Token-based proxy endpoint |
| **Protected Media** | All uploaded media (completions, documents, warranties) |
---
## Current Architecture (Problem)
```
Mobile App Go API Disk
│ │ │
├─→ POST /api/documents/ ─────────→│ Save to /uploads/docs/ │
│ (authenticated) │────────────────────────────→│
│ │ │
│←─ { file_url: "/uploads/..." } ←─│ │
│ │ │
├─→ GET /uploads/docs/file.jpg ───→│ Static middleware │
│ (NO AUTH CHECK!) │←───────────────────────────→│
│←─ file bytes ←──────────────────←│ │
```
**Issues**:
1. `r.Static("/uploads", cfg.Storage.UploadDir)` serves files without authentication
2. File URLs are predictable (timestamp + short UUID)
3. URLs can be shared/leaked, granting permanent access
4. No audit trail for file access
---
## Recommended Solution: Authenticated Media Proxy
### Architecture
```
Mobile App Go API Disk
│ │ │
├─→ POST /api/documents/ ─────────→│ Save to /uploads/docs/ │
│ (authenticated) │────────────────────────────→│
│ │ │
│←─ { file_url: "/api/media/..." }←│ Return proxy URL │
│ │ │
├─→ GET /api/media/{type}/{id}────→│ Media Handler: │
│ Authorization: Token xxx │ 1. Verify token │
│ │ 2. Get file record │
│ │ 3. Check residence access │
│ │ 4. Stream file │
│←─ file bytes ←──────────────────←│←───────────────────────────→│
```
### Key Changes
1. **Remove public static file serving** - No more `r.Static("/uploads", ...)`
2. **Create authenticated media endpoint** - `GET /api/media/{type}/{id}`
3. **Update stored URLs** - Store internal paths, return proxy URLs in API responses
4. **Add access control** - Verify user has residence access before serving
---
## Implementation Plan
### Phase 1: Create Media Handler
**New File: `/internal/handlers/media_handler.go`**
```go
type MediaHandler struct {
documentRepo repositories.DocumentRepository
taskRepo repositories.TaskRepository
residenceRepo repositories.ResidenceRepository
storageSvc *services.StorageService
}
// GET /api/media/document/{id}
// GET /api/media/document-image/{id}
// GET /api/media/completion-image/{id}
func (h *MediaHandler) ServeMedia(c *gin.Context) {
// 1. Extract user from auth context
user := c.MustGet(middleware.AuthUserKey).(*models.User)
// 2. Get media type and ID from path
mediaType := c.Param("type") // "document", "document-image", "completion-image"
mediaID := c.Param("id")
// 3. Look up the file record and get residence ID
residenceID, filePath, err := h.getFileInfo(mediaType, mediaID)
// 4. Check residence access
hasAccess, _ := h.residenceRepo.HasAccess(residenceID, user.ID)
if !hasAccess {
c.JSON(403, gin.H{"error": "Access denied"})
return
}
// 5. Stream the file
c.File(filePath)
}
```
### Phase 2: Update Models & Responses
**Change how URLs are stored and returned:**
| Model | Current Storage | New Storage | API Response |
|-------|-----------------|-------------|--------------|
| `Document.FileURL` | `/uploads/docs/file.pdf` | `docs/file.pdf` (relative) | `/api/media/document/{id}` |
| `DocumentImage.ImageURL` | `/uploads/images/img.jpg` | `images/img.jpg` (relative) | `/api/media/document-image/{id}` |
| `TaskCompletionImage.ImageURL` | `/uploads/completions/img.jpg` | `completions/img.jpg` (relative) | `/api/media/completion-image/{id}` |
**Update Response DTOs:**
```go
// /internal/dto/responses/document_response.go
type DocumentResponse struct {
ID uint `json:"id"`
// Don't expose FileURL directly
// FileURL string `json:"file_url"` // REMOVE
MediaURL string `json:"media_url"` // NEW: "/api/media/document/123"
// ...
}
type DocumentImageResponse struct {
ID uint `json:"id"`
MediaURL string `json:"media_url"` // "/api/media/document-image/456"
Caption string `json:"caption"`
}
```
### Phase 3: Update Router
**File: `/internal/router/router.go`**
```go
// REMOVE this line:
// r.Static("/uploads", cfg.Storage.UploadDir)
// ADD authenticated media routes:
media := api.Group("/media")
media.Use(middleware.AuthRequired(cfg, userService))
{
media.GET("/document/:id", mediaHandler.ServeDocument)
media.GET("/document-image/:id", mediaHandler.ServeDocumentImage)
media.GET("/completion-image/:id", mediaHandler.ServeCompletionImage)
}
```
### Phase 4: Update Mobile Clients
**iOS - Update image loading to include auth headers:**
```swift
// Before: Direct URL access
AsyncImage(url: URL(string: document.fileUrl))
// After: Use authenticated request
AuthenticatedImage(url: document.mediaUrl)
// New component that adds auth header
struct AuthenticatedImage: View {
let url: String
var body: some View {
// Use URLSession with auth header, or
// use a library like Kingfisher with request modifier
}
}
```
**KMM - Update Ktor client for image requests:**
```kotlin
// Add auth header to image requests
suspend fun fetchMedia(mediaUrl: String): ByteArray {
val token = TokenStorage.getToken()
return httpClient.get(mediaUrl) {
header("Authorization", "Token $token")
}.body()
}
```
---
## Files to Modify
### Go API (honeyDueAPI-go)
| File | Change |
|------|--------|
| `/internal/handlers/media_handler.go` | **NEW** - Authenticated media serving |
| `/internal/router/router.go` | Remove static serving, add media routes |
| `/internal/dto/responses/document_response.go` | Add `MediaURL` field, remove direct file URL |
| `/internal/dto/responses/task_response.go` | Add `MediaURL` to completion images |
| `/internal/services/document_service.go` | Update to generate proxy URLs |
| `/internal/services/task_service.go` | Update to generate proxy URLs for completions |
### iOS (HoneyDueKMM/iosApp)
| File | Change |
|------|--------|
| `iosApp/Components/AuthenticatedImage.swift` | **NEW** - Image view with auth headers |
| `iosApp/Task/TaskCompletionDetailView.swift` | Use AuthenticatedImage |
| `iosApp/Documents/DocumentDetailView.swift` | Use AuthenticatedImage |
### KMM/Android
| File | Change |
|------|--------|
| `network/MediaApi.kt` | **NEW** - Authenticated media fetching |
| `ui/components/AuthenticatedImage.kt` | **NEW** - Compose image with auth |
---
## Migration Strategy
### For Existing Data
Option A: **Update URLs in database** (Recommended)
```sql
-- Strip /uploads prefix from existing URLs
UPDATE documents SET file_url = REPLACE(file_url, '/uploads/', '') WHERE file_url LIKE '/uploads/%';
UPDATE document_images SET image_url = REPLACE(image_url, '/uploads/', '') WHERE image_url LIKE '/uploads/%';
UPDATE task_completion_images SET image_url = REPLACE(image_url, '/uploads/', '') WHERE image_url LIKE '/uploads/%';
```
Option B: **Handle both formats in code** (Temporary)
```go
func (h *MediaHandler) getFilePath(storedURL string) string {
// Handle legacy /uploads/... URLs
if strings.HasPrefix(storedURL, "/uploads/") {
return filepath.Join(h.uploadDir, strings.TrimPrefix(storedURL, "/uploads/"))
}
// Handle new relative paths
return filepath.Join(h.uploadDir, storedURL)
}
```
---
## Security Considerations
### Pros of This Approach
- ✓ All media access requires valid auth token
- ✓ Access control tied to residence membership
- ✓ No URL guessing possible (URLs are opaque IDs)
- ✓ Can add audit logging easily
- ✓ Can add rate limiting per user
### Cons / Trade-offs
- ✗ Every image request hits the API (increased server load)
- ✗ Can't use CDN caching for images
- ✗ Mobile apps need to handle auth for image loading
### Mitigations
1. **Add caching headers** - `Cache-Control: private, max-age=3600`
2. **Consider short-lived signed URLs** later if performance becomes an issue
3. **Add response compression** for images if not already enabled
---
## Implementation Order
1. **Create MediaHandler** with access control logic
2. **Update router** - add media routes, keep static for now
3. **Update response DTOs** - add MediaURL fields
4. **Update services** - generate proxy URLs
5. **Test with Postman** - verify access control works
6. **Update iOS** - AuthenticatedImage component
7. **Update Android/KMM** - AuthenticatedImage component
8. **Run migration** - update existing URLs in database
9. **Remove static serving** - final step after clients updated
10. **Add audit logging** (optional)

View File

@@ -1,247 +0,0 @@
# Subscription Webhook Setup
This document explains how to configure Apple App Store Server Notifications and Google Real-time Developer Notifications for server-to-server subscription status updates.
## Overview
The webhook endpoints allow Apple and Google to notify your server when subscription events occur:
- New subscriptions
- Renewals
- Cancellations
- Refunds
- Expirations
- Grace period events
**Webhook Endpoints:**
- Apple: `POST /api/subscription/webhook/apple/`
- Google: `POST /api/subscription/webhook/google/`
## Apple App Store Server Notifications v2
### 1. Configure in App Store Connect
1. Log in to [App Store Connect](https://appstoreconnect.apple.com)
2. Navigate to **My Apps** > Select your app
3. Go to **App Information** (under General)
4. Scroll to **App Store Server Notifications**
5. Enter your webhook URLs:
- **Production Server URL**: `https://your-domain.com/api/subscription/webhook/apple/`
- **Sandbox Server URL**: `https://your-domain.com/api/subscription/webhook/apple/` (or separate staging URL)
6. Select **Version 2** for the notification version
### 2. Environment Variables
Set these environment variables on your server:
```bash
# Apple IAP Configuration (optional but recommended for validation)
APPLE_IAP_KEY_PATH=/path/to/AuthKey_XXXXX.p8 # Your App Store Connect API key
APPLE_IAP_KEY_ID=XXXXXXXXXX # Key ID from App Store Connect
APPLE_IAP_ISSUER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # Issuer ID from App Store Connect
APPLE_IAP_BUNDLE_ID=com.your.app.bundleid # Your app's bundle ID
APPLE_IAP_SANDBOX=true # Set to false for production
```
### 3. Generate App Store Connect API Key
To enable server-side receipt validation:
1. Go to [App Store Connect > Users and Access > Keys](https://appstoreconnect.apple.com/access/api)
2. Click **Generate API Key**
3. Give it a name (e.g., "honeyDue IAP Server")
4. Select **App Manager** role (minimum required for IAP)
5. Download the `.p8` file - **you can only download it once!**
6. Note the **Key ID** and **Issuer ID**
### 4. Apple Notification Types
Your server will receive these notification types:
| Type | Description | Action |
|------|-------------|--------|
| `SUBSCRIBED` | New subscription | Upgrade user to Pro |
| `DID_RENEW` | Subscription renewed | Extend expiry date |
| `DID_CHANGE_RENEWAL_STATUS` | Auto-renew toggled | Update auto_renew flag |
| `DID_FAIL_TO_RENEW` | Billing failed | User may be in grace period |
| `EXPIRED` | Subscription expired | Downgrade to Free |
| `REFUND` | User got refund | Downgrade to Free |
| `REVOKE` | Access revoked | Downgrade to Free |
| `GRACE_PERIOD_EXPIRED` | Grace period ended | Downgrade to Free |
---
## Google Real-time Developer Notifications
Google uses Cloud Pub/Sub to deliver subscription notifications. Setup requires creating a Pub/Sub topic and subscription.
### 1. Create a Pub/Sub Topic
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Select your project (or create one)
3. Navigate to **Pub/Sub** > **Topics**
4. Click **Create Topic**
5. Name it (e.g., `honeydue-subscriptions`)
6. Note the full topic name: `projects/your-project-id/topics/honeydue-subscriptions`
### 2. Create a Push Subscription
1. In the Pub/Sub Topics list, click on your topic
2. Click **Create Subscription**
3. Configure:
- **Subscription ID**: `honeydue-subscription-webhook`
- **Delivery type**: Push
- **Endpoint URL**: `https://your-domain.com/api/subscription/webhook/google/`
- **Acknowledgment deadline**: 60 seconds
4. Click **Create**
### 3. Link to Google Play Console
1. Go to [Google Play Console](https://play.google.com/console)
2. Select your app
3. Navigate to **Monetization** > **Monetization setup**
4. Under **Real-time developer notifications**:
- Enter your topic name: `projects/your-project-id/topics/honeydue-subscriptions`
5. Click **Save**
6. Click **Send test notification** to verify the connection
### 4. Create a Service Account for Validation
1. Go to [Google Cloud Console > IAM & Admin > Service Accounts](https://console.cloud.google.com/iam-admin/serviceaccounts)
2. Click **Create Service Account**
3. Name it (e.g., `honeydue-iap-validator`)
4. Grant roles:
- `Pub/Sub Subscriber` (for webhook handling)
5. Click **Done**
6. Click on the service account > **Keys** > **Add Key** > **Create new key**
7. Select **JSON** and download the key file
### 5. Link Play Console to Cloud Project
1. Go to **Google Play Console** > **Setup** > **API access**
2. Link your Google Cloud project
3. Under **Service accounts**, grant your service account access:
- `View financial data` (for subscription validation)
### 6. Environment Variables
```bash
# Google IAP Configuration
GOOGLE_IAP_SERVICE_ACCOUNT_PATH=/path/to/service-account.json
GOOGLE_IAP_PACKAGE_NAME=com.your.app.packagename
```
### 7. Google Notification Types
Your server will receive these notification types:
| Type Code | Type | Description | Action |
|-----------|------|-------------|--------|
| 1 | `SUBSCRIPTION_RECOVERED` | Recovered from hold | Re-upgrade to Pro |
| 2 | `SUBSCRIPTION_RENEWED` | Subscription renewed | Extend expiry date |
| 3 | `SUBSCRIPTION_CANCELED` | User canceled | Mark as canceled, will expire |
| 4 | `SUBSCRIPTION_PURCHASED` | New purchase | Upgrade to Pro |
| 5 | `SUBSCRIPTION_ON_HOLD` | Account hold | User may lose access soon |
| 6 | `SUBSCRIPTION_IN_GRACE_PERIOD` | In grace period | User still has access |
| 7 | `SUBSCRIPTION_RESTARTED` | User resubscribed | Re-upgrade to Pro |
| 12 | `SUBSCRIPTION_REVOKED` | Subscription revoked | Downgrade to Free |
| 13 | `SUBSCRIPTION_EXPIRED` | Subscription expired | Downgrade to Free |
---
## Testing
### Test Apple Webhooks
1. Use the **Sandbox** environment in App Store Connect
2. Create a Sandbox tester account
3. Make test purchases on a test device
4. Monitor your server logs for webhook events
### Test Google Webhooks
1. In Google Play Console > **Monetization setup**
2. Click **Send test notification**
3. Your server should receive a test notification
4. Check server logs for: `"Google Webhook: Received test notification"`
### Local Development Testing
For local testing, use a tunnel service like ngrok:
```bash
# Start ngrok
ngrok http 8000
# Use the ngrok URL for webhook configuration
# e.g., https://abc123.ngrok.io/api/subscription/webhook/apple/
```
---
## Security Considerations
### Apple Webhook Verification
The webhook handler includes optional JWS signature verification. Apple signs all notifications using their certificate chain. While the current implementation decodes the payload without full verification, you can enable verification by:
1. Downloading Apple's root certificates from https://www.apple.com/certificateauthority/
2. Calling `VerifyAppleSignature()` before processing
### Google Webhook Security
Options for securing Google webhooks:
1. **IP Whitelisting**: Google publishes [Pub/Sub IP ranges](https://cloud.google.com/pubsub/docs/reference/service_apis_overview#ip-ranges)
2. **Push Authentication**: Configure your Pub/Sub subscription to include an authentication header
3. **OIDC Token**: Have Pub/Sub include an OIDC token that your server verifies
### General Security
- Always use HTTPS for webhook endpoints
- Validate the bundle ID / package name in each notification
- Log all webhook events for debugging and audit
- Return 200 OK even if processing fails (to prevent retries flooding your server)
---
## Troubleshooting
### Apple Webhooks Not Arriving
1. Verify the URL is correct in App Store Connect
2. Check your server is reachable from the internet
3. Ensure you're testing with a Sandbox account
4. Check server logs for any processing errors
### Google Webhooks Not Arriving
1. Verify the topic name format: `projects/PROJECT_ID/topics/TOPIC_NAME`
2. Check the Pub/Sub subscription is active
3. Test with "Send test notification" in Play Console
4. Check Cloud Logging for Pub/Sub delivery errors
### User Not Found for Webhook
This typically means:
- The user made a purchase but the receipt/token wasn't stored
- The transaction ID or purchase token doesn't match any stored data
The handler logs these cases but returns success to prevent Apple/Google from retrying.
---
## Database Schema
Relevant fields in `user_subscriptions` table:
```sql
apple_receipt_data TEXT -- Stores receipt/transaction ID for Apple
google_purchase_token TEXT -- Stores purchase token for Google
cancelled_at TIMESTAMP -- When user canceled (will expire at end of period)
auto_renew BOOLEAN -- Whether subscription will auto-renew
```
These fields are used by webhook handlers to:
1. Find the user associated with a transaction
2. Track cancellation state
3. Update renewal preferences

View File

@@ -1,310 +0,0 @@
# Task Kanban Categorization
This document explains how tasks are categorized into kanban columns in the honeyDue application.
> Note: The categorization chain still computes `cancelled_tasks`, but the kanban board response
> intentionally hides cancelled/archived tasks and returns only 5 visible columns.
## Overview
The task categorization system uses the **Chain of Responsibility** design pattern to determine which kanban column a task belongs to. Each handler in the chain evaluates the task against specific criteria, and if matched, returns the appropriate column. If not matched, the task is passed to the next handler.
## Architecture
### Design Pattern: Chain of Responsibility
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Cancelled │───▶│ Completed │───▶│ In Progress │───▶│ Overdue │───▶│ Due Soon │───▶│ Upcoming │
│ Handler │ │ Handler │ │ Handler │ │ Handler │ │ Handler │ │ Handler │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
"cancelled_tasks" "completed_tasks" "in_progress_tasks" "overdue_tasks" "due_soon_tasks" "upcoming_tasks"
```
### Source Files
- **Chain Implementation**: `internal/task/categorization/chain.go`
- **Tests**: `internal/task/categorization/chain_test.go`
- **Legacy Wrapper**: `internal/dto/responses/task.go` (`DetermineKanbanColumn`)
- **Repository Usage**: `internal/repositories/task_repo.go` (`GetKanbanData`)
## Kanban Columns
The system supports 6 kanban columns, evaluated in strict priority order:
| Priority | Column Name | Display Name | Description |
|----------|-------------|--------------|-------------|
| 1 | `cancelled_tasks` | Cancelled | Tasks that have been cancelled |
| 2 | `completed_tasks` | Completed | One-time tasks that are done |
| 3 | `in_progress_tasks` | In Progress | Tasks currently being worked on |
| 4 | `overdue_tasks` | Overdue | Tasks past their due date |
| 5 | `due_soon_tasks` | Due Soon | Tasks due within the threshold (default: 30 days) |
| 6 | `upcoming_tasks` | Upcoming | All other active tasks |
## Categorization Logic (Step by Step)
### Step 1: Cancelled Check (Highest Priority)
**Handler**: `CancelledHandler`
**Condition**: `task.IsCancelled == true`
**Result**: `cancelled_tasks`
A cancelled task always goes to the cancelled column, regardless of any other attributes. This is the first check because cancellation represents a terminal state that overrides all other considerations.
```go
if task.IsCancelled {
return "cancelled_tasks"
}
```
### Step 2: Completed Check
**Handler**: `CompletedHandler`
**Condition**: `task.NextDueDate == nil && len(task.Completions) > 0`
**Result**: `completed_tasks`
A task is considered "completed" when:
1. It has at least one completion record (someone marked it done)
2. AND it has no `NextDueDate` (meaning it's a one-time task)
**Important**: Recurring tasks with completions but with a `NextDueDate` set are NOT considered completed - they continue in their active cycle.
```go
if task.NextDueDate == nil && len(task.Completions) > 0 {
return "completed_tasks"
}
```
### Step 3: In Progress Check
**Handler**: `InProgressHandler`
**Condition**: `task.InProgress == true`
**Result**: `in_progress_tasks`
Tasks marked as "In Progress" are grouped together regardless of their due date. This allows users to see what's actively being worked on.
```go
if task.InProgress {
return "in_progress_tasks"
}
```
### Step 4: Overdue Check
**Handler**: `OverdueHandler`
**Condition**: `effectiveDate.Before(now)`
**Result**: `overdue_tasks`
The "effective date" is determined as:
1. `NextDueDate` (preferred - used for recurring tasks after completion)
2. `DueDate` (fallback - used for initial scheduling)
If the effective date is in the past, the task is overdue.
```go
effectiveDate := task.NextDueDate
if effectiveDate == nil {
effectiveDate = task.DueDate
}
if effectiveDate != nil && effectiveDate.Before(now) {
return "overdue_tasks"
}
```
### Step 5: Due Soon Check
**Handler**: `DueSoonHandler`
**Condition**: `effectiveDate.Before(threshold)`
**Result**: `due_soon_tasks`
Where `threshold = now + daysThreshold` (default 30 days)
Tasks due within the threshold period are considered "due soon" and need attention.
```go
threshold := now.AddDate(0, 0, daysThreshold)
if effectiveDate != nil && effectiveDate.Before(threshold) {
return "due_soon_tasks"
}
```
### Step 6: Upcoming (Default)
**Handler**: `UpcomingHandler`
**Condition**: None (catches all remaining tasks)
**Result**: `upcoming_tasks`
Any task that doesn't match the above criteria falls into "upcoming". This includes:
- Tasks with due dates beyond the threshold
- Tasks with no due date set
- Future scheduled tasks
```go
return "upcoming_tasks"
```
## Key Concepts
### DueDate vs NextDueDate
| Field | Purpose | When Set |
|-------|---------|----------|
| `DueDate` | Original/initial due date | When task is created |
| `NextDueDate` | Next occurrence for recurring tasks | After each completion |
**For recurring tasks**: `NextDueDate` takes precedence over `DueDate` for categorization. After completing a recurring task:
1. A new completion record is created
2. `NextDueDate` is calculated as `completionDate + frequencyDays`
3. Status is reset to "Pending"
4. Task moves to the appropriate column based on the new `NextDueDate`
### One-Time vs Recurring Tasks
**One-Time Tasks** (frequency = "Once" or no frequency):
- When completed: `NextDueDate` is set to `nil`
- Categorization: Goes to `completed_tasks` column
- Status: Remains as-is (usually "Completed" or last status)
**Recurring Tasks** (weekly, monthly, annually, etc.):
- When completed: `NextDueDate` is set to `completionDate + frequencyDays`
- Categorization: Based on new `NextDueDate` (could be upcoming, due_soon, etc.)
- Status: **Reset to "Pending"** to allow proper column placement
### The "In Progress" Gotcha
**Important**: Tasks with "In Progress" status will stay in the `in_progress_tasks` column even if they're overdue!
This is by design - "In Progress" indicates active work, so the task should be visible there. However, this means:
1. If a recurring task is marked "In Progress" and then completed
2. The `in_progress` flag MUST be reset to `false` after completion
3. Otherwise, the task stays in "In Progress" instead of moving to "Upcoming"
This is handled automatically in `TaskService.CreateCompletion()`:
```go
if isRecurringTask {
task.InProgress = false
}
```
## Configuration
### Days Threshold
The `daysThreshold` parameter controls what counts as "due soon":
- Default: 30 days
- Configurable per-request via query parameter
```go
// Example: Consider tasks due within 7 days as "due soon"
chain.Categorize(task, 7)
```
## Column Metadata
Each column has associated metadata for UI rendering:
```go
{
Name: "overdue_tasks",
DisplayName: "Overdue",
ButtonTypes: []string{"edit", "complete", "cancel", "mark_in_progress"},
Icons: map[string]string{"ios": "exclamationmark.triangle", "android": "Warning"},
Color: "#FF3B30",
}
```
### Button Types by Column
| Column | Available Actions |
|--------|-------------------|
| `overdue_tasks` | edit, complete, cancel, mark_in_progress |
| `in_progress_tasks` | edit, complete, cancel |
| `due_soon_tasks` | edit, complete, cancel, mark_in_progress |
| `upcoming_tasks` | edit, complete, cancel, mark_in_progress |
| `completed_tasks` | (none - read-only) |
| `cancelled_tasks` | uncancel, delete |
## Usage Examples
### Basic Categorization
```go
import "github.com/treytartt/honeydue-api/internal/task/categorization"
task := &models.Task{
DueDate: time.Now().AddDate(0, 0, 15), // 15 days from now
InProgress: false,
}
column := categorization.DetermineKanbanColumn(task, 30)
// Returns: "due_soon_tasks"
```
### Categorize Multiple Tasks
```go
tasks := []models.Task{...}
columns := categorization.CategorizeTasksIntoColumns(tasks, 30)
// Returns map[KanbanColumn][]models.Task with tasks organized by column
```
### Custom Chain (Advanced)
```go
chain := categorization.NewChain()
ctx := categorization.NewContext(task, 14) // 14-day threshold
column := chain.CategorizeWithContext(ctx)
```
## Testing
The categorization logic is thoroughly tested in `chain_test.go`:
```bash
go test ./internal/task/categorization/... -v
```
Key test scenarios:
- Each handler's matching criteria
- Priority order between handlers
- Recurring task lifecycle
- Edge cases (nil dates, empty completions, etc.)
## Troubleshooting
### Task stuck in wrong column?
1. **Check `IsCancelled`**: Cancelled takes highest priority
2. **Check `NextDueDate`**: For recurring tasks, this determines placement
3. **Check `InProgress`**: `true` overrides date-based categorization
4. **Check `Completions`**: Empty + nil NextDueDate = upcoming, not completed
### Recurring task not moving to Upcoming after completion?
Verify that `CreateCompletion` is:
1. Setting `NextDueDate` correctly
2. Resetting `InProgress` to `false`
### Task showing overdue but due date looks correct?
The system uses UTC times. Ensure dates are compared in UTC:
```go
now := time.Now().UTC()
```

View File

@@ -1,318 +0,0 @@
# Task Kanban Board Categorization Logic
This document describes how tasks are categorized into kanban columns for display in the honeyDue mobile app.
> Important: The board intentionally returns **5 visible columns**. Cancelled and archived tasks are
> hidden from board responses (though task-level `kanban_column` may still be `cancelled_tasks`).
## Overview
Tasks are organized into 5 visible kanban columns based on their state and due date. The categorization logic is implemented in `internal/repositories/task_repo.go` in the `GetKanbanData` and `GetKanbanDataForMultipleResidences` functions.
## Columns Summary
| Column | Name | Color | Button Types | Description |
|--------|------|-------|--------------|-------------|
| 1 | **Overdue** | `#FF3B30` (Red) | edit, complete, cancel, mark_in_progress | Tasks past their due date |
| 2 | **Due Soon** | `#FF9500` (Orange) | edit, complete, cancel, mark_in_progress | Tasks due within the threshold (default 30 days) |
| 3 | **Upcoming** | `#007AFF` (Blue) | edit, complete, cancel, mark_in_progress | Tasks due beyond the threshold or with no due date |
| 4 | **In Progress** | `#5856D6` (Purple) | edit, complete, cancel | Tasks with status "In Progress" |
| 5 | **Completed** | `#34C759` (Green) | view | Tasks with at least one completion record |
## Categorization Flow
The categorization follows this priority order (first match wins):
```
┌─────────────────────────────────────────────────────────────────┐
│ START: Process Task │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────┐
│ Is task cancelled? │
│ (is_cancelled = true) │
└─────────────────────────┘
│ │
YES NO
│ │
▼ ▼
┌──────────┐ ┌─────────────────────────┐
│CANCELLED │ │ Has task completions? │
│ column │ │ (len(completions) > 0) │
└──────────┘ └─────────────────────────┘
│ │
YES NO
│ │
▼ ▼
┌──────────┐ ┌─────────────────────────┐
│COMPLETED │ │ Is status "In Progress"?│
│ column │ │ (status.name = "In...) │
└──────────┘ └─────────────────────────┘
│ │
YES NO
│ │
▼ ▼
┌───────────┐ ┌─────────────────┐
│IN PROGRESS│ │ Has due date? │
│ column │ └─────────────────┘
└───────────┘ │ │
YES NO
│ │
▼ ▼
┌──────────────┐ ┌────────┐
│Check due date│ │UPCOMING│
└──────────────┘ │ column │
│ └────────┘
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────────┐ ┌──────────┐ ┌────────────┐
│ due < now │ │due < now │ │due >= now +│
│ │ │+ threshold│ │ threshold │
└─────────────┘ └──────────┘ └────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌────────┐
│ OVERDUE │ │ DUE SOON │ │UPCOMING│
│ column │ │ column │ │ column │
└─────────┘ └──────────┘ └────────┘
```
## Detailed Rules
### 1. Cancelled (highest priority)
```go
if task.IsCancelled {
cancelled = append(cancelled, task)
continue
}
```
- **Condition**: `is_cancelled = true`
- **Button Types**: `uncancel`, `delete`
- **Rationale**: Cancelled tasks can be restored (uncancel) or permanently removed (delete)
### 2. Completed
```go
if len(task.Completions) > 0 {
completed = append(completed, task)
continue
}
```
- **Condition**: Task has at least one `TaskCompletion` record
- **Note**: A task is considered completed based on having completion records, NOT based on status
- **Button Types**: `view`
- **Rationale**: Completed tasks are read-only for historical purposes; users can view completion details and photos
### 3. In Progress
```go
if task.InProgress {
inProgress = append(inProgress, task)
continue
}
```
- **Condition**: Task's `in_progress` boolean is `true`
- **Button Types**: `edit`, `complete`, `cancel`
- **Rationale**: In-progress tasks can be edited, marked complete, or cancelled if work is abandoned
### 4. Due Date Based Categories (only for tasks not cancelled, completed, or in progress)
#### Overdue
```go
if task.DueDate.Before(now) {
overdue = append(overdue, task)
}
```
- **Condition**: `due_date < current_time`
- **Button Types**: `edit`, `complete`, `cancel`, `mark_in_progress`
- **Rationale**: Overdue tasks need urgent attention - can complete immediately, start working on them, edit the due date, or cancel if no longer needed
#### Due Soon
```go
if task.DueDate.Before(threshold) {
dueSoon = append(dueSoon, task)
}
```
- **Condition**: `current_time <= due_date < (current_time + days_threshold)`
- **Default threshold**: 30 days
- **Button Types**: `edit`, `complete`, `cancel`, `mark_in_progress`
- **Rationale**: Due soon tasks are approaching their deadline - users can complete early, start working, reschedule, or cancel
#### Upcoming
```go
upcoming = append(upcoming, task)
```
- **Condition**: `due_date >= (current_time + days_threshold)` OR `due_date IS NULL`
- **Button Types**: `edit`, `complete`, `cancel`, `mark_in_progress`
- **Rationale**: Future tasks may need to be completed early (ahead of schedule), started, rescheduled, or cancelled
## Button Types Reference
| Button Type | Action | Description |
|-------------|--------|-------------|
| `edit` | Update task | Modify task details (title, description, due date, category, etc.) |
| `complete` | Create completion | Mark task as done with optional notes and photos |
| `cancel` | Cancel task | Mark task as cancelled (soft delete, can be uncancelled) |
| `mark_in_progress` | Start work | Change status to "In Progress" to indicate work has begun |
| `view` | View details | View task and completion details (read-only) |
| `uncancel` | Restore task | Restore a cancelled task to its previous state |
| `delete` | Hard delete | Permanently remove task (only for cancelled tasks) |
## Column Metadata
Each column includes metadata for the mobile clients:
```go
{
Name: "overdue_tasks", // Internal identifier
DisplayName: "Overdue", // User-facing label
ButtonTypes: []string{"edit", "complete", "cancel", "mark_in_progress"},
Icons: map[string]string{ // Platform-specific icons
"ios": "exclamationmark.triangle",
"android": "Warning"
},
Color: "#FF3B30", // Display color
Tasks: []Task{...}, // Tasks in this column
Count: int, // Number of tasks
}
```
### Icons by Column
| Column | iOS Icon | Android Icon |
|--------|----------|--------------|
| Overdue | `exclamationmark.triangle` | `Warning` |
| Due Soon | `clock` | `Schedule` |
| Upcoming | `calendar` | `Event` |
| In Progress | `hammer` | `Build` |
| Completed | `checkmark.circle` | `CheckCircle` |
| Cancelled | `xmark.circle` | `Cancel` |
## Days Threshold Parameter
The `daysThreshold` parameter (default: 30) determines the boundary between "Due Soon" and "Upcoming":
- Tasks due within `daysThreshold` days from now → **Due Soon**
- Tasks due beyond `daysThreshold` days from now → **Upcoming**
This can be customized per request via query parameter.
## Sorting
Tasks within each column are sorted by:
1. `due_date ASC NULLS LAST` (earliest due date first, tasks without due dates at end)
2. `priority_id DESC` (higher priority first)
3. `created_at DESC` (newest first)
## Excluded Tasks
The following tasks are excluded from the kanban board entirely:
- **Archived tasks**: `is_archived = true`
## API Response Example
```json
{
"columns": [
{
"name": "overdue_tasks",
"display_name": "Overdue",
"button_types": ["edit", "complete", "cancel", "mark_in_progress"],
"icons": {
"ios": "exclamationmark.triangle",
"android": "Warning"
},
"color": "#FF3B30",
"tasks": [...],
"count": 2
},
{
"name": "due_soon_tasks",
"display_name": "Due Soon",
"button_types": ["edit", "complete", "cancel", "mark_in_progress"],
"icons": {
"ios": "clock",
"android": "Schedule"
},
"color": "#FF9500",
"tasks": [...],
"count": 5
},
{
"name": "upcoming_tasks",
"display_name": "Upcoming",
"button_types": ["edit", "complete", "cancel", "mark_in_progress"],
"icons": {
"ios": "calendar",
"android": "Event"
},
"color": "#007AFF",
"tasks": [...],
"count": 10
},
{
"name": "in_progress_tasks",
"display_name": "In Progress",
"button_types": ["edit", "complete", "cancel"],
"icons": {
"ios": "hammer",
"android": "Build"
},
"color": "#5856D6",
"tasks": [...],
"count": 3
},
{
"name": "completed_tasks",
"display_name": "Completed",
"button_types": ["view"],
"icons": {
"ios": "checkmark.circle",
"android": "CheckCircle"
},
"color": "#34C759",
"tasks": [...],
"count": 15
},
{
"name": "cancelled_tasks",
"display_name": "Cancelled",
"button_types": ["uncancel", "delete"],
"icons": {
"ios": "xmark.circle",
"android": "Cancel"
},
"color": "#8E8E93",
"tasks": [...],
"count": 1
}
],
"days_threshold": 30,
"residence_id": "123"
}
```
## Design Decisions
### Why "In Progress" doesn't have "mark_in_progress"
Tasks already in the "In Progress" column are already marked as in progress, so this action doesn't make sense.
### Why "Completed" only has "view"
Completed tasks represent historical records. Users can view them but shouldn't be able to uncomplete or modify them. If a task was completed incorrectly, the user should delete the completion record through the completion detail view.
### Why "Cancelled" has "delete" but others don't
Hard deletion is only available for cancelled tasks as a final cleanup action. For active tasks, users should cancel first (soft delete), then delete if truly needed. This prevents accidental data loss.
### Why all active columns have "cancel"
Users should always be able to abandon a task they no longer need, regardless of its due date or progress state.
## Code Location
- **Repository**: `internal/repositories/task_repo.go`
- `GetKanbanData()` - Single residence
- `GetKanbanDataForMultipleResidences()` - Multiple residences (all user's properties)
- **Service**: `internal/services/task_service.go`
- `ListTasks()` - All tasks for user
- `GetTasksByResidence()` - Tasks for specific residence
- **Response DTOs**: `internal/dto/responses/task.go`
- `KanbanBoardResponse`
- `KanbanColumnResponse`

View File

@@ -1,252 +0,0 @@
# Competitor Analysis: Home Management Apps
*Last Updated: December 2024*
This document provides a comprehensive comparison of home management apps similar to honeyDue/HoneyDue.
---
## Feature Comparison Table
| App | Task Scheduling | Multi-Property | Document Storage | Contractors | Warranty Tracking | Pricing |
|-----|----------------|----------------|------------------|-------------|-------------------|---------|
| **honeyDue** | Recurring + Kanban | Yes | Yes | Yes | Yes | TBD |
| **HomeZada** | Yes | Yes (Deluxe) | Yes | No | Yes | Free / $59-99/yr |
| **Centriq** | Reminders only | Yes | Yes (Manuals) | No | Yes + Recalls | Free / $18-100/yr |
| **Homer** | Yes | Yes | Yes | Contacts only | Yes | Free / Premium |
| **Dwellin** | Yes | Yes (Pro) | Yes (Pro) | No | Yes | Free / Pro sub |
| **Sweepy** | Cleaning only | No | No | No | No | Free / $15-17/yr |
| **Tody** | Cleaning only | No | No | No | No | Free / $6/yr |
---
## Detailed Competitor Profiles
### HomeZada
**Website:** [homezada.com](https://www.homezada.com/)
**Overview:** Comprehensive home management platform covering finances, inventory, projects, and maintenance.
**Key Features:**
- Customizable maintenance calendar with smart reminders
- Home improvement project tracking with budgeting
- Digital document storage for receipts and important files
- Home inventory system with photo uploads (useful for insurance)
- Property tax and mortgage tracking
- Home value estimation
**Pricing:**
| Plan | Price | Features |
|------|-------|----------|
| Essentials | Free | Inventory, documents, contacts, newsfeed |
| Premium | $79/year or $11.95/month | Full maintenance scheduling, budgeting |
| Deluxe | $149/year | Up to 3 properties, home marketing tools |
**Strengths:** Most comprehensive feature set, strong financial tracking
**Weaknesses:** No contractor management, complex interface
---
### Centriq
**Overview:** Appliance-focused app that excels at product information and maintenance.
**Key Features:**
- Snap a product label to auto-fetch manuals, troubleshooting guides, parts info
- Extensive library of appliance manuals
- Safety recall notifications
- How-to videos for repairs
- Receipt and warranty storage
- Maintenance reminders (filter changes, etc.)
**Pricing:**
| Plan | Price | Notes |
|------|-------|-------|
| Beginner | Free | Limited items |
| Baby | $17.95/year | — |
| Limited | $29.95/year | — |
| Expert | $59.95/year | — |
| Genius | $99.95/year | Most items/properties |
*Alternative pricing: $32/property for deluxe subscription*
**Strengths:** Best appliance database, automatic manual fetching, recall alerts
**Weaknesses:** Not a full task management system, no contractor tracking
---
### Homer
**Website:** [homer.co](https://www.homer.co/)
**Overview:** Beautiful, Apple-featured home management app with unique features.
**Key Features:**
- Built-in receipt scanner and expense tracker
- Floor plan creator (accurate plans in minutes)
- AR (augmented reality) notes - place digital markers in 3D space
- Multi-property support (houses, apartments, condos)
- Task scheduling and reminders
- Contact storage for service providers
**Pricing:**
| Plan | Price | Notes |
|------|-------|-------|
| Free | $0 | Basic features, data always accessible |
| Premium | Unknown | 2-week free trial, no lock-in |
**Recognition:**
- Apple "App of the Day" (2023)
- Apple "Apps We Love" (2022, 2023, 2024)
- Apple "Essential Utility Apps" (2024)
**Strengths:** Best UI/UX, AR features, Apple recognition
**Weaknesses:** Pricing not transparent, no dedicated contractor management
---
### Dwellin
**Website:** [App Store](https://apps.apple.com/us/app/dwellin-easy-home-maintenance/id1566306121)
**Overview:** Eco-conscious home maintenance app with cost estimation.
**Key Features:**
- Annual home maintenance cost estimation
- Carbon footprint tracking for your home
- Maintenance scheduling and reminders
- Dwellin Pro: automated appliance manuals, digital home binder
- Family collaboration (Pro)
- Multi-property management (Pro)
**Pricing:**
| Plan | Price | Notes |
|------|-------|-------|
| Free | $0 | Core features |
| Pro | Subscription | Monthly, quarterly, or annual options |
*Note: Specific Pro pricing not publicly available*
**Strengths:** Unique eco-friendly angle, cost estimation
**Weaknesses:** Pro features behind paywall, pricing not transparent
---
### Sweepy
**Website:** [sweepy.com](https://sweepy.com/)
**Overview:** Dedicated household cleaning schedule app.
**Key Features:**
- Tracks daily, monthly, and yearly cleaning tasks
- Records when each chore was last completed
- Automatically creates daily cleaning schedules
- Family member assignment
- "Dirtiness" indicator for each room
**Pricing:**
| Plan | Price | Notes |
|------|-------|-------|
| Free | $0 | 1 user |
| Premium | $2.50-$3/month | Multi-user, family features |
| Annual | $13-$17/year | Best value |
*3-day trial for premium*
**Strengths:** Laser-focused on cleaning, gamification elements
**Weaknesses:** Only cleaning (not full home management), iOS only
---
### Tody
**Website:** [todyapp.com](https://todyapp.com/)
**Overview:** Smart cleaning app with unique "Tody Method" for routine management.
**Key Features:**
- Visual indicators showing cleaning urgency
- Customizable task difficulty/effort levels
- Vacation mode
- Multi-device sync (Premium)
- Family collaboration
**Pricing:**
| Plan | Price | Notes |
|------|-------|-------|
| Free | $0 | Full Tody Method |
| Premium | ~$6/year (Android) | Sync, vacation mode, effort tiers |
| One-time | $6.99 (some platforms) | — |
**Strengths:** Affordable, effective methodology, good free tier
**Weaknesses:** Only cleaning tasks, no documents or warranties
---
## Competitive Positioning
### Where honeyDue Differentiates
Based on competitor analysis, honeyDue has unique advantages:
1. **Contractor Management** - Most competitors lack this entirely
2. **Kanban Task View** - Unique visual organization (overdue, due soon, upcoming, completed)
3. **Shared Household** - Family collaboration with residence sharing codes
4. **Combined Approach** - Tasks + Documents + Contractors + Warranties in one app
5. **Recurring Task Intelligence** - Smart next-due-date calculation for recurring maintenance
### Competitor Gaps honeyDue Fills
| Gap | Competitors Missing It | honeyDue Solution |
|-----|------------------------|-----------------|
| Contractor database | All except Homer (contacts only) | Full contractor profiles with specialties |
| Kanban visualization | All | Visual task board with columns |
| Task sharing | Most | Residence member collaboration |
| Recurring task logic | Most are basic reminders | Frequency-based auto-scheduling |
---
## Suggested Pricing Strategy
Based on market analysis:
### Recommended Tier Structure
| Tier | Price | Target User | Features |
|------|-------|-------------|----------|
| **Free** | $0 | Casual homeowner | 1 property, 10 tasks, basic features |
| **Plus** | $4.99/mo or $49/yr | Active homeowner | Unlimited tasks, documents, 2-3 properties |
| **Pro** | $9.99/mo or $99/yr | Power user / Landlord | Unlimited everything, contractor management, family sharing, priority support |
### Pricing Rationale
- **Free tier** attracts users and builds habit
- **Plus at ~$50/yr** undercuts HomeZada Premium ($79/yr) while offering more task features
- **Pro at ~$100/yr** matches HomeZada Deluxe but includes contractor management they lack
- Monthly options provide flexibility (important for conversion)
### Competitive Price Points
| Competitor | Annual Price | honeyDue Comparison |
|------------|--------------|-------------------|
| Tody | $6 | honeyDue offers much more (not just cleaning) |
| Sweepy | $15-17 | honeyDue offers full home management |
| Centriq | $18-100 | honeyDue has better task management |
| HomeZada | $59-149 | honeyDue adds contractors, better UX |
---
## Sources
- [HomeZada](https://www.homezada.com/)
- [Centriq - App Store](https://apps.apple.com/us/app/centriq-home-assistant/id1063517498)
- [Homer - The Home Management App](https://www.homer.co/)
- [Dwellin - App Store](https://apps.apple.com/us/app/dwellin-easy-home-maintenance/id1566306121)
- [Sweepy](https://sweepy.com/)
- [Tody](https://todyapp.com/)
- [Best Home Maintenance Apps - Select Home Warranty](https://www.selecthomewarranty.com/blog/best-home-maintenance-apps/)
- [Apps for New Homeowners - Today's Homeowner](https://todayshomeowner.com/blog/guides/best-apps-for-new-homeowners/)
- [Best 6 Apps for New Homeowners - Liberty Home Guard](https://www.libertyhomeguard.com/blog/home-maintenance/best-6-apps-for-new-homeowners/)
- [Home Organization Apps - Family Handyman](https://www.familyhandyman.com/article/home-organization-apps/)

View File

@@ -1,200 +0,0 @@
# Press Release
---
## FOR IMMEDIATE RELEASE
**Contact:**
[Your Name]
[Email]
[Phone]
---
# honeyDue Launches to Help Homeowners Take Control of Home Maintenance
*New mobile app brings task management, document storage, and contractor organization to homeowners tired of forgotten maintenance and lost warranties*
---
**[CITY, STATE] — [DATE]** — honeyDue, a new home maintenance management app, launched today on iOS and Android, offering homeowners a simple yet powerful way to organize every aspect of home upkeep. The app combines recurring task scheduling, document storage, contractor management, and household collaboration in one intuitive platform.
"Homeownership comes with a hidden job: maintenance manager," said [Founder Name], founder of honeyDue. "Most people cobble together sticky notes, spreadsheet reminders, and hope. We built honeyDue because there had to be a better way."
### The Problem honeyDue Solves
The average single-family home requires maintenance on over 40 different systems and components, from HVAC filters to roof inspections. According to HomeAdvisor, homeowners spend an average of $3,192 annually on emergency repairs — many of which could be prevented with regular maintenance.
Yet most homeowners lack any organized system to track these tasks. Warranties get lost in email. Contractor contact information lives in scattered text threads. Important maintenance gets forgotten until something breaks.
### How honeyDue Works
honeyDue provides homeowners with four core capabilities:
**Smart Task Scheduling**
Users can create one-time or recurring maintenance tasks with customizable frequencies — from daily to annually. The app's kanban-style board shows tasks organized by urgency: overdue, due soon, upcoming, and completed. When a recurring task is completed, honeyDue automatically schedules the next occurrence.
**Document Storage**
Warranties, manuals, receipts, and home documents can be uploaded and organized by room or category. When an appliance needs service, the warranty information is instantly accessible.
**Contractor Management**
Homeowners can store contact information, specialties, and notes for trusted service providers. No more scrolling through old texts to find the plumber's number during an emergency.
**Household Collaboration**
Multiple family members can be invited to share a residence, ensuring everyone sees what maintenance is needed. Residents can assign tasks to each other and track completion together.
### Market Opportunity
The home services market is projected to reach $1.2 trillion by 2026, driven by increasing homeownership rates and aging housing stock. The on-demand home services app market alone is expected to grow from $5.29 billion in 2024 to $9.03 billion by 2028.
"We're not competing with handyman apps or smart home devices," said [Founder Name]. "We're replacing the mental load of remembering everything your home needs. That's a problem every homeowner understands."
### Pricing and Availability
honeyDue is available now as a free download on the [App Store](link) and [Google Play](link). The free tier includes core functionality for one property. Premium tiers offering multiple properties, unlimited document storage, and priority support are available starting at $[X]/month.
### About honeyDue
honeyDue is a home maintenance management platform designed to help homeowners stay ahead of maintenance, protect their investment, and reduce the stress of property upkeep. Founded in [YEAR], honeyDue is headquartered in [CITY, STATE].
For more information, visit [www.honeyDue.treytartt.com](https://www.honeyDue.treytartt.com) or follow @honeyDueApp on [Twitter](link) and [Instagram](link).
---
### Media Assets
High-resolution logos, app screenshots, and founder headshots are available at: [MEDIA KIT LINK]
---
### Quick Facts
| Metric | Detail |
|--------|--------|
| **Launch Date** | [DATE] |
| **Platforms** | iOS, Android |
| **Price** | Free with premium tiers |
| **Headquarters** | [CITY, STATE] |
| **Website** | [URL] |
| **Social** | @honeyDueApp |
---
###
---
# ALTERNATIVE VERSIONS
---
## Short Version (for email pitches)
**Subject: New App Helps Homeowners Stop Forgetting Maintenance**
Hi [Name],
Quick pitch: honeyDue is a new app that helps homeowners track recurring maintenance, store warranties, and organize contractor info — all in one place.
**The hook:** The average homeowner spends $3,000+/year on emergency repairs. Most are preventable with basic maintenance. But there's been no good system to track it all — until now.
**Key features:**
- Kanban-style task board (overdue → due soon → upcoming → done)
- Recurring reminders that actually recur (change filter every 90 days, forever)
- Document storage for warranties and manuals
- Contractor rolodex
- Household sharing
We just launched on iOS and Android. Happy to share more, provide access, or set up an interview with our founder.
Best,
[Name]
---
## Tweet-Length Announcement
```
📱 honeyDue just launched — the home maintenance app for homeowners who are tired of forgetting filter changes and losing warranties.
Free on iOS + Android.
Your home runs better when you do. 🏠
```
---
## Founder Quote Options
*Choose the one that fits your story best:*
**Problem-focused:**
> "I built honeyDue after my AC died because I forgot a $150 tune-up. That $4,000 lesson convinced me there had to be a better way to manage home maintenance."
**Vision-focused:**
> "We believe your home deserves the same organizational tools as your work calendar. honeyDue brings that professionalism to home management."
**Market-focused:**
> "Smart home technology has focused on automation and convenience. We're focused on something simpler: helping people remember to take care of their biggest investment."
**User-focused:**
> "Our users tell us the same thing: 'I finally feel on top of my house.' That's exactly what we're going for."
---
## Press Coverage Angles
Journalists can approach this story from multiple angles:
**Personal Finance:**
"App Helps Homeowners Avoid Costly Emergency Repairs Through Preventive Maintenance"
**Productivity/Apps:**
"Trello for Your House: How Kanban Boards Are Coming to Home Maintenance"
**Real Estate:**
"New Tech Aims to Reduce Hidden Costs of Homeownership"
**Lifestyle:**
"The App That Might Finally End the 'Did You Call the Plumber?' Argument"
**First-Time Buyers:**
"Essential Apps for New Homeowners in [YEAR]"
---
## Suggested Interview Questions
1. What's the most commonly forgotten home maintenance task?
2. How much can preventive maintenance actually save homeowners?
3. Why hasn't this problem been solved before?
4. How is honeyDue different from calendar reminders?
5. What's the most surprising thing you've learned from users?
---
## Boilerplate
**About honeyDue (50 words)**
honeyDue is a home maintenance management app that helps homeowners track recurring tasks, store important documents, organize contractor information, and collaborate with household members. Available on iOS and Android, honeyDue brings clarity and organization to the often-overwhelming job of home upkeep.
**About honeyDue (25 words)**
honeyDue is a home maintenance app that helps homeowners track tasks, store warranties, and organize contractor info — all in one place.
**About honeyDue (10 words)**
honeyDue: The organized way to manage home maintenance.
---
## Contact
**Media Inquiries:**
[Name]
[Email]
[Phone]
**General Information:**
Website: [URL]
Email: hello@honeyDue.treytartt.com
Twitter: @honeyDueApp
Instagram: @honeyDueApp

View File

@@ -1,454 +0,0 @@
# honeyDue Social Media Kit
*Ready-to-use posts for launching and promoting honeyDue*
---
## Brand Voice Guidelines
**Tone:** Friendly, helpful, empowering
**Style:** Clear, conversational, action-oriented
**Avoid:** Technical jargon, fear-based messaging, overwhelming lists
**Key Messages:**
1. Home maintenance doesn't have to be overwhelming
2. Stay ahead of problems, not behind them
3. Your home deserves the same organization as your work calendar
---
## Twitter/X Posts
### Launch Announcement
**Post 1 - Main Launch**
```
Introducing honeyDue 🏠
The home maintenance app that actually makes sense.
✓ Smart task scheduling
✓ Never miss another filter change
✓ Track warranties before they expire
✓ Keep contractor info in one place
Your home runs better when you do.
Download free → [link]
```
**Post 2 - Problem/Solution**
```
That "I should really check the HVAC filter" thought you have every 3 months?
honeyDue remembers it for you.
Set it once. Get reminded forever.
Free download → [link]
```
**Post 3 - Feature Focus**
```
POV: You need a plumber at 9pm
❌ Scrolling through old texts
❌ Googling "plumber near me" again
❌ Hoping you saved that business card
✅ Opening honeyDue and finding Mike the Plumber instantly
Your contractor rolodex, always in your pocket.
```
---
### Feature Highlights
**Recurring Tasks**
```
"Change HVAC filter"
"Clean gutters"
"Test smoke detectors"
These aren't one-time tasks. They come back.
honeyDue knows that. Set the frequency once, and we'll remind you every time it's due.
Smart home maintenance → [link]
```
**Kanban Board**
```
Finally, a way to actually SEE your home maintenance:
🔴 Overdue — handle these first
🟡 Due Soon — coming up this month
🟢 Upcoming — you're ahead of the game
✅ Completed — feel that satisfaction
honeyDue's kanban view. Simple. Visual. Effective.
```
**Document Storage**
```
Where's the warranty for your fridge?
A) Filing cabinet somewhere
B) Email from 3 years ago
C) No idea
D) honeyDue — because you uploaded it when you bought it
Be option D.
```
**Shared Household**
```
"Did you call the electrician?"
"I thought YOU were going to"
Sound familiar?
honeyDue lets you share your home with family members. Everyone sees what needs doing. No more crossed wires.
(pun intended)
```
---
### Engagement Posts
**Poll**
```
What's the home task you forget most often?
🔘 Changing air filters
🔘 Cleaning gutters
🔘 Testing smoke detectors
🔘 Scheduling HVAC service
```
**Question**
```
Be honest: Do you know when your roof was last inspected?
Reply with:
✅ = Yes, I'm on top of it
😅 = I should probably check
❓ = Wait, you're supposed to inspect roofs?
```
**Tip Thread**
```
🧵 5 home maintenance tasks most people forget (and when to do them):
1/ Dryer vent cleaning — Once a year. Prevents fires. Seriously.
2/ Water heater flush — Annually. Extends life by years.
3/ Garbage disposal cleaning — Monthly. Ice cubes + lemon.
4/ Refrigerator coils — Twice a year. Saves energy.
5/ Smoke detector batteries — Every 6 months. Life-saving.
Save this. Or better yet, add them all to honeyDue and never think about it again.
```
---
## Instagram Posts
### Launch Carousel (5 slides)
**Slide 1:**
```
MEET HONEYDUE
The home maintenance app you didn't know you needed
(but definitely do)
```
**Slide 2:**
```
THE PROBLEM
You bought a home.
It came with:
• 47 things that need regular maintenance
• Zero system to track them
• That vague anxiety something's overdue
Sound familiar?
```
**Slide 3:**
```
THE SOLUTION
honeyDue organizes everything:
📋 Tasks — recurring reminders that actually recur
📄 Documents — warranties, manuals, receipts
🔧 Contractors — your trusted pros, one tap away
👨‍👩‍👧 Family — shared access, no more "I thought you did it"
```
**Slide 4:**
```
THE VIEW
See everything at a glance:
🔴 Overdue
🟡 Due Soon
🟢 Upcoming
✅ Done
No spreadsheets. No forgotten Post-its.
Just clarity.
```
**Slide 5:**
```
GET STARTED FREE
Your home works hard for you.
Let's return the favor.
Download honeyDue
Link in bio 🏠
```
---
### Single Image Posts
**Post 1 - Lifestyle**
```
Caption:
Sunday morning coffee hits different when you're not mentally running through everything you forgot to maintain.
honeyDue handles the remembering.
You handle the relaxing.
#homeowner #homeownership #adulting #homemaintenance #organizedhome #homeownertips #firsttimehomebuyer
```
**Post 2 - Stat-based**
```
Caption:
The average homeowner spends $3,000+/year on emergency repairs.
Most could be prevented with regular maintenance.
The unsexy truth: Boring scheduled tasks save exciting amounts of money.
honeyDue makes the boring part automatic.
#homeownerhacks #homemaintenance #savemoney #preventivemaintenance #homecare
```
**Post 3 - Testimonial Format**
```
Caption:
"I used to keep track of home stuff in my head.
Then the AC died in July because I forgot the spring tune-up.
$4,200 lesson learned.
Now I use honeyDue."
— Every homeowner, eventually
Don't wait for your expensive lesson.
#homeowner #hvac #homemaintenance #lessonlearned #homeownerlife
```
---
### Reels/Stories Ideas
**Reel 1: "Things I wish I knew as a first-time homeowner"**
```
Hook: "Nobody told me homes need THIS much maintenance"
Content:
- Quick cuts of common maintenance tasks
- Text overlays with frequency
- End with honeyDue app demo
Audio: Trending sound
```
**Reel 2: "POV: You actually maintained your home this year"**
```
Hook: Satisfying montage of checking off tasks
Content:
- Checking HVAC filter ✓
- Cleaning gutters ✓
- Testing smoke detectors ✓
- Organized contractor list
Audio: Satisfying/success sound
```
**Story Series: "Home Maintenance Monday"**
```
Weekly recurring content:
- One maintenance tip per week
- Swipe up to add task to honeyDue
- Build habit + community
```
---
## LinkedIn Posts
### Launch Announcement
```
I'm excited to share something I've been working on: honeyDue.
It started with a simple frustration. As a homeowner, I was juggling:
• Sticky notes for maintenance reminders
• Spreadsheets for warranties
• Text threads to find my plumber's number
• The constant nagging feeling I was forgetting something
There had to be a better way.
honeyDue is that better way.
It's a home maintenance app that brings everything together:
→ Recurring task reminders (that actually recur)
→ Document storage for warranties and manuals
→ Contractor directory at your fingertips
→ Shared access for the whole household
Homeownership is already complicated. Managing it shouldn't be.
We're live on iOS and Android. I'd love for you to try it and share your feedback.
What's the home maintenance task YOU always forget? Drop it in the comments.
#startup #proptech #homeownership #productivity #appdev
```
### Thought Leadership
```
Hot take: Most "smart home" technology solves the wrong problems.
We have refrigerators that tweet and thermostats that learn our schedules.
But ask a homeowner when they last:
- Changed their HVAC filter
- Flushed their water heater
- Cleaned their dryer vent
...and you'll get a blank stare.
The unsexy truth about homeownership:
It's not about automation. It's about organization.
The homes that last aren't the most automated. They're the most maintained.
That's why we built honeyDue — not another smart device, but a smart system for the maintenance that actually matters.
Sometimes the most valuable technology is the simplest.
#realestate #proptech #homeownership #startups #productthinking
```
### Feature Announcement Template
```
New in honeyDue: [FEATURE NAME]
The problem: [One sentence describing pain point]
The solution: [One sentence describing feature]
How it works:
1. [Step one]
2. [Step two]
3. [Step three]
This was our most-requested feature, and I'm thrilled to ship it.
Update your app to try it today.
What feature should we build next?
#productupdate #honeydue #hometech
```
---
## Hashtag Strategy
### Primary Hashtags (use on every post)
```
#honeyDue #HomeMaintenance #HomeOwner #HomeManagement
```
### Secondary Hashtags (rotate based on content)
```
#FirstTimeHomeBuyer #HomeOwnerTips #HomeOwnerLife #Adulting
#HomeImprovement #PropertyMaintenance #OrganizedHome #HomeHacks
#RealEstate #NewHomeOwner #HouseGoals #HomeOwnership
```
### Trending/Seasonal
```
Spring: #SpringCleaning #SpringMaintenance
Summer: #SummerHome #ACMaintenance
Fall: #FallMaintenance #WinterPrep #GutterCleaning
Winter: #WinterHome #HeatingSystem
```
---
## Content Calendar Template
| Day | Platform | Content Type | Topic |
|-----|----------|--------------|-------|
| Monday | Twitter | Tip | "Maintenance Monday" tip |
| Tuesday | Instagram | Carousel | Feature deep-dive |
| Wednesday | LinkedIn | Thought leadership | Industry insight |
| Thursday | Twitter | Engagement | Poll or question |
| Friday | Instagram | Lifestyle | Weekend home project |
| Saturday | Stories | Behind-the-scenes | App updates, team |
| Sunday | Twitter | Thread | Educational content |
---
## Response Templates
### Positive Feedback
```
Thanks so much! 🏠 We're glad honeyDue is helping you stay on top of things. Let us know if there's anything we can do better!
```
### Feature Request
```
Love this idea! We're adding it to our list. Thanks for helping us make honeyDue better! 🙏
```
### Support Issue
```
Sorry you're running into trouble! DM us the details and we'll get it sorted out ASAP. 🔧
```
### Competitor Mention
```
Great question! We focus on [specific differentiator]. Happy to share more about how honeyDue might fit your needs — DM us anytime!
```