Initial commit: MyCrib API in Go

Complete rewrite of Django REST API to Go with:
- Gin web framework for HTTP routing
- GORM for database operations
- GoAdmin for admin panel
- Gorush integration for push notifications
- Redis for caching and job queues

Features implemented:
- User authentication (login, register, logout, password reset)
- Residence management (CRUD, sharing, share codes)
- Task management (CRUD, kanban board, completions)
- Contractor management (CRUD, specialties)
- Document management (CRUD, warranties)
- Notifications (preferences, push notifications)
- Subscription management (tiers, limits)

Infrastructure:
- Docker Compose for local development
- Database migrations and seed data
- Admin panel for data management

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-26 20:07:16 -06:00
commit 1f12f3f62a
78 changed files with 13821 additions and 0 deletions

166
seeds/001_lookups.sql Normal file
View File

@@ -0,0 +1,166 @@
-- Seed lookup data for MyCrib
-- Run with: ./dev.sh seed
-- Residence Types (only has: id, created_at, updated_at, name)
INSERT INTO residence_residencetype (id, created_at, updated_at, name)
VALUES
(1, NOW(), NOW(), 'House'),
(2, NOW(), NOW(), 'Apartment'),
(3, NOW(), NOW(), 'Condo'),
(4, NOW(), NOW(), 'Townhouse'),
(5, NOW(), NOW(), 'Duplex'),
(6, NOW(), NOW(), 'Mobile Home'),
(7, NOW(), NOW(), 'Vacation Home'),
(8, NOW(), NOW(), 'Other')
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
updated_at = NOW();
-- Task Categories (has: name, description, icon, color, display_order)
INSERT INTO task_taskcategory (id, created_at, updated_at, name, description, icon, color, display_order)
VALUES
(1, NOW(), NOW(), 'Plumbing', 'Plumbing related tasks', 'wrench', '#3498db', 1),
(2, NOW(), NOW(), 'Electrical', 'Electrical work and repairs', 'bolt', '#f1c40f', 2),
(3, NOW(), NOW(), 'HVAC', 'Heating, ventilation, and air conditioning', 'thermometer', '#e74c3c', 3),
(4, NOW(), NOW(), 'Appliances', 'Appliance maintenance and repair', 'cog', '#9b59b6', 4),
(5, NOW(), NOW(), 'Exterior', 'Exterior maintenance and landscaping', 'tree', '#27ae60', 5),
(6, NOW(), NOW(), 'Interior', 'Interior maintenance and repairs', 'home', '#e67e22', 6),
(7, NOW(), NOW(), 'Safety', 'Safety and security tasks', 'shield', '#c0392b', 7),
(8, NOW(), NOW(), 'Cleaning', 'Cleaning and sanitation', 'broom', '#1abc9c', 8),
(9, NOW(), NOW(), 'Pest Control', 'Pest prevention and control', 'bug', '#8e44ad', 9),
(10, NOW(), NOW(), 'General', 'General maintenance tasks', 'tools', '#7f8c8d', 99)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
description = EXCLUDED.description,
icon = EXCLUDED.icon,
color = EXCLUDED.color,
display_order = EXCLUDED.display_order,
updated_at = NOW();
-- Task Priorities (has: name, level, color, display_order - NO description)
INSERT INTO task_taskpriority (id, created_at, updated_at, name, level, color, display_order)
VALUES
(1, NOW(), NOW(), 'Low', 1, '#27ae60', 1),
(2, NOW(), NOW(), 'Medium', 2, '#f39c12', 2),
(3, NOW(), NOW(), 'High', 3, '#e74c3c', 3),
(4, NOW(), NOW(), 'Urgent', 4, '#c0392b', 4)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
level = EXCLUDED.level,
color = EXCLUDED.color,
display_order = EXCLUDED.display_order,
updated_at = NOW();
-- Task Statuses (has: name, description, color, display_order - NO is_terminal)
INSERT INTO task_taskstatus (id, created_at, updated_at, name, description, color, display_order)
VALUES
(1, NOW(), NOW(), 'Pending', 'Task has not been started', '#95a5a6', 1),
(2, NOW(), NOW(), 'In Progress', 'Task is currently being worked on', '#3498db', 2),
(3, NOW(), NOW(), 'Completed', 'Task has been completed', '#27ae60', 3),
(4, NOW(), NOW(), 'Cancelled', 'Task has been cancelled', '#e74c3c', 4),
(5, NOW(), NOW(), 'On Hold', 'Task is on hold', '#f39c12', 5)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
description = EXCLUDED.description,
color = EXCLUDED.color,
display_order = EXCLUDED.display_order,
updated_at = NOW();
-- Task Frequencies (has: name, days, display_order)
INSERT INTO task_taskfrequency (id, created_at, updated_at, name, days, display_order)
VALUES
(1, NOW(), NOW(), 'Once', NULL, 1),
(2, NOW(), NOW(), 'Daily', 1, 2),
(3, NOW(), NOW(), 'Weekly', 7, 3),
(4, NOW(), NOW(), 'Bi-Weekly', 14, 4),
(5, NOW(), NOW(), 'Monthly', 30, 5),
(6, NOW(), NOW(), 'Quarterly', 90, 6),
(7, NOW(), NOW(), 'Semi-Annually', 180, 7),
(8, NOW(), NOW(), 'Annually', 365, 8)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
days = EXCLUDED.days,
display_order = EXCLUDED.display_order,
updated_at = NOW();
-- Contractor Specialties (check actual columns)
INSERT INTO task_contractorspecialty (id, created_at, updated_at, name)
VALUES
(1, NOW(), NOW(), 'Plumber'),
(2, NOW(), NOW(), 'Electrician'),
(3, NOW(), NOW(), 'HVAC Technician'),
(4, NOW(), NOW(), 'Handyman'),
(5, NOW(), NOW(), 'Landscaper'),
(6, NOW(), NOW(), 'Painter'),
(7, NOW(), NOW(), 'Roofer'),
(8, NOW(), NOW(), 'Carpenter'),
(9, NOW(), NOW(), 'Appliance Repair'),
(10, NOW(), NOW(), 'Pest Control'),
(11, NOW(), NOW(), 'Cleaner'),
(12, NOW(), NOW(), 'Pool Service'),
(13, NOW(), NOW(), 'Locksmith'),
(14, NOW(), NOW(), 'General Contractor')
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
updated_at = NOW();
-- Subscription Settings (singleton)
INSERT INTO subscription_subscriptionsettings (id, enable_limitations)
VALUES (1, false)
ON CONFLICT (id) DO NOTHING;
-- Tier Limits
INSERT INTO subscription_tierlimits (id, created_at, updated_at, tier, properties_limit, tasks_limit, contractors_limit, documents_limit)
VALUES
(1, NOW(), NOW(), 'free', 1, 10, 0, 0),
(2, NOW(), NOW(), 'pro', NULL, NULL, NULL, NULL)
ON CONFLICT (id) DO UPDATE SET
tier = EXCLUDED.tier,
properties_limit = EXCLUDED.properties_limit,
tasks_limit = EXCLUDED.tasks_limit,
contractors_limit = EXCLUDED.contractors_limit,
documents_limit = EXCLUDED.documents_limit,
updated_at = NOW();
-- Feature Benefits
INSERT INTO subscription_featurebenefit (id, created_at, updated_at, feature_name, free_tier_text, pro_tier_text, display_order, is_active)
VALUES
(1, NOW(), NOW(), 'Properties', '1 property', 'Unlimited properties', 1, true),
(2, NOW(), NOW(), 'Tasks', '10 tasks', 'Unlimited tasks', 2, true),
(3, NOW(), NOW(), 'Contractors', 'Not available', 'Unlimited contractors', 3, true),
(4, NOW(), NOW(), 'Documents', 'Not available', 'Unlimited documents', 4, true),
(5, NOW(), NOW(), 'PDF Reports', 'Not available', 'Generate PDF reports', 5, true),
(6, NOW(), NOW(), 'Priority Support', 'Community support', 'Priority email support', 6, true)
ON CONFLICT (id) DO UPDATE SET
feature_name = EXCLUDED.feature_name,
free_tier_text = EXCLUDED.free_tier_text,
pro_tier_text = EXCLUDED.pro_tier_text,
display_order = EXCLUDED.display_order,
is_active = EXCLUDED.is_active,
updated_at = NOW();
-- Upgrade Triggers
INSERT INTO subscription_upgradetrigger (id, created_at, updated_at, trigger_key, title, message, button_text, is_active)
VALUES
(1, NOW(), NOW(), 'property_limit', 'Upgrade to Add More Properties', 'You''ve reached the free tier limit of 1 property. Upgrade to Pro to add unlimited properties.', 'Upgrade to Pro', true),
(2, NOW(), NOW(), 'task_limit', 'Upgrade for More Tasks', 'You''ve reached the free tier limit of 10 tasks. Upgrade to Pro for unlimited tasks.', 'Upgrade to Pro', true),
(3, NOW(), NOW(), 'contractor_access', 'Unlock Contractor Management', 'Contractor management is a Pro feature. Upgrade to keep track of your service providers.', 'Upgrade to Pro', true),
(4, NOW(), NOW(), 'document_access', 'Unlock Document Storage', 'Document storage is a Pro feature. Upgrade to store warranties, manuals, and more.', 'Upgrade to Pro', true)
ON CONFLICT (id) DO UPDATE SET
trigger_key = EXCLUDED.trigger_key,
title = EXCLUDED.title,
message = EXCLUDED.message,
button_text = EXCLUDED.button_text,
is_active = EXCLUDED.is_active,
updated_at = NOW();
-- Reset sequences to max id + 1
SELECT setval('residence_residencetype_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM residence_residencetype), false);
SELECT setval('task_taskcategory_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_taskcategory), false);
SELECT setval('task_taskpriority_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_taskpriority), false);
SELECT setval('task_taskstatus_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_taskstatus), false);
SELECT setval('task_taskfrequency_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_taskfrequency), false);
SELECT setval('task_contractorspecialty_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_contractorspecialty), false);
SELECT setval('subscription_tierlimits_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM subscription_tierlimits), false);
SELECT setval('subscription_featurebenefit_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM subscription_featurebenefit), false);
SELECT setval('subscription_upgradetrigger_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM subscription_upgradetrigger), false);

157
seeds/002_test_data.sql Normal file
View File

@@ -0,0 +1,157 @@
-- Seed test data for MyCrib
-- Run with: ./dev.sh seed-test
-- Note: Run ./dev.sh seed first to populate lookup tables
-- Test Users (password is 'password123' hashed with bcrypt)
-- bcrypt hash for 'password123': $2a$10$rQEY6fXqPmGd5L5o5vJXt.Nk7NqKvHJBJFk5QbF1wqQKw1Z5K3X2a
INSERT INTO auth_user (id, username, password, email, first_name, last_name, is_active, is_staff, is_superuser, date_joined)
VALUES
(1, 'admin', '$2a$10$rQEY6fXqPmGd5L5o5vJXt.Nk7NqKvHJBJFk5QbF1wqQKw1Z5K3X2a', 'admin@example.com', 'Admin', 'User', true, true, true, NOW()),
(2, 'john', '$2a$10$rQEY6fXqPmGd5L5o5vJXt.Nk7NqKvHJBJFk5QbF1wqQKw1Z5K3X2a', 'john@example.com', 'John', 'Doe', true, false, false, NOW()),
(3, 'jane', '$2a$10$rQEY6fXqPmGd5L5o5vJXt.Nk7NqKvHJBJFk5QbF1wqQKw1Z5K3X2a', 'jane@example.com', 'Jane', 'Smith', true, false, false, NOW()),
(4, 'bob', '$2a$10$rQEY6fXqPmGd5L5o5vJXt.Nk7NqKvHJBJFk5QbF1wqQKw1Z5K3X2a', 'bob@example.com', 'Bob', 'Wilson', true, false, false, NOW())
ON CONFLICT (id) DO UPDATE SET
username = EXCLUDED.username,
email = EXCLUDED.email,
first_name = EXCLUDED.first_name,
last_name = EXCLUDED.last_name;
-- User Subscriptions
INSERT INTO subscription_usersubscription (id, created_at, updated_at, user_id, tier, subscribed_at, expires_at, auto_renew, platform)
VALUES
(1, NOW(), NOW(), 1, 'pro', NOW(), NOW() + INTERVAL '1 year', true, 'ios'),
(2, NOW(), NOW(), 2, 'pro', NOW(), NOW() + INTERVAL '1 year', true, 'android'),
(3, NOW(), NOW(), 3, 'free', NULL, NULL, false, NULL),
(4, NOW(), NOW(), 4, 'free', NULL, NULL, false, NULL)
ON CONFLICT (id) DO UPDATE SET
tier = EXCLUDED.tier,
updated_at = NOW();
-- Test Residences (using Go/GORM schema: street_address, state_province, postal_code)
INSERT INTO residence_residence (id, created_at, updated_at, owner_id, property_type_id, name, street_address, city, state_province, postal_code, country, is_active, is_primary)
VALUES
(1, NOW(), NOW(), 2, 1, 'Main House', '123 Main Street', 'Springfield', 'IL', '62701', 'USA', true, true),
(2, NOW(), NOW(), 2, 7, 'Beach House', '456 Ocean Drive', 'Miami', 'FL', '33139', 'USA', true, false),
(3, NOW(), NOW(), 3, 2, 'Downtown Apartment', '789 City Center', 'Los Angeles', 'CA', '90012', 'USA', true, true),
(4, NOW(), NOW(), 4, 3, 'Mountain Condo', '321 Peak View', 'Denver', 'CO', '80202', 'USA', true, true)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
street_address = EXCLUDED.street_address,
updated_at = NOW();
-- Share residence 1 with user 3
INSERT INTO residence_residence_users (residence_id, user_id)
VALUES (1, 3)
ON CONFLICT DO NOTHING;
-- Test Contractors
INSERT INTO task_contractor (id, created_at, updated_at, residence_id, created_by_id, name, company, phone, email, website, notes, is_favorite, is_active)
VALUES
(1, NOW(), NOW(), 1, 2, 'Mike the Plumber', 'Mike''s Plumbing Co.', '+1-555-1001', 'mike@plumbing.com', 'https://mikesplumbing.com', 'Great service, always on time', true, true),
(2, NOW(), NOW(), 1, 2, 'Sparky Electric', 'Sparky Electrical Services', '+1-555-1002', 'info@sparky.com', NULL, 'Licensed and insured', false, true),
(3, NOW(), NOW(), 1, 2, 'Cool Air HVAC', 'Cool Air Heating & Cooling', '+1-555-1003', 'service@coolair.com', 'https://coolair.com', '24/7 emergency service', true, true),
(4, NOW(), NOW(), 3, 3, 'Handy Andy', NULL, '+1-555-1004', 'andy@handyman.com', NULL, 'General repairs', false, true)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
company = EXCLUDED.company,
updated_at = NOW();
-- Contractor Specialties (many-to-many)
INSERT INTO task_contractor_specialties (contractor_id, contractor_specialty_id)
VALUES
(1, 1), -- Mike: Plumber
(2, 2), -- Sparky: Electrician
(3, 3), -- Cool Air: HVAC
(4, 4), -- Andy: Handyman
(4, 6) -- Andy: Also Painter
ON CONFLICT DO NOTHING;
-- Test Tasks
INSERT INTO task_task (id, created_at, updated_at, residence_id, created_by_id, assigned_to_id, title, description, category_id, priority_id, status_id, frequency_id, due_date, estimated_cost, contractor_id, is_cancelled, is_archived)
VALUES
-- Residence 1 tasks
(1, NOW(), NOW(), 1, 2, 2, 'Fix leaky faucet', 'Kitchen faucet is dripping', 1, 2, 1, 1, CURRENT_DATE + INTERVAL '7 days', 150.00, 1, false, false),
(2, NOW(), NOW(), 1, 2, NULL, 'Replace smoke detector batteries', 'Annual battery replacement', 7, 3, 1, 8, CURRENT_DATE + INTERVAL '30 days', 25.00, NULL, false, false),
(3, NOW(), NOW(), 1, 2, 2, 'HVAC filter replacement', 'Replace air filters', 3, 2, 2, 5, CURRENT_DATE + INTERVAL '14 days', 50.00, 3, false, false),
(4, NOW(), NOW(), 1, 2, 3, 'Mow lawn', 'Weekly lawn maintenance', 5, 1, 3, 3, CURRENT_DATE - INTERVAL '2 days', NULL, NULL, false, false),
(5, NOW(), NOW(), 1, 2, NULL, 'Clean gutters', 'Remove leaves and debris', 5, 2, 1, 7, CURRENT_DATE + INTERVAL '60 days', 200.00, NULL, false, false),
-- Residence 2 tasks
(6, NOW(), NOW(), 2, 2, 2, 'Check pool chemicals', 'Test and balance pool water', 10, 2, 1, 3, CURRENT_DATE + INTERVAL '3 days', NULL, NULL, false, false),
(7, NOW(), NOW(), 2, 2, NULL, 'Hurricane shutters inspection', 'Annual inspection before hurricane season', 7, 3, 1, 8, CURRENT_DATE + INTERVAL '90 days', NULL, NULL, false, false),
-- Residence 3 tasks
(8, NOW(), NOW(), 3, 3, 3, 'Fix garbage disposal', 'Disposal is jammed', 4, 3, 1, 1, CURRENT_DATE + INTERVAL '2 days', 100.00, NULL, false, false),
(9, NOW(), NOW(), 3, 3, NULL, 'Deep clean apartment', 'Quarterly deep cleaning', 8, 1, 1, 6, CURRENT_DATE + INTERVAL '45 days', 300.00, NULL, false, false),
-- Residence 4 tasks
(10, NOW(), NOW(), 4, 4, 4, 'Winterize pipes', 'Prepare plumbing for winter', 1, 3, 1, 8, CURRENT_DATE + INTERVAL '120 days', 250.00, NULL, false, false)
ON CONFLICT (id) DO UPDATE SET
title = EXCLUDED.title,
description = EXCLUDED.description,
updated_at = NOW();
-- Test Task Completions
INSERT INTO task_taskcompletion (id, created_at, updated_at, task_id, completed_by_id, completed_at, notes, actual_cost)
VALUES
(1, NOW() - INTERVAL '2 days', NOW() - INTERVAL '2 days', 4, 3, NOW() - INTERVAL '2 days', 'Lawn looks great!', NULL)
ON CONFLICT (id) DO UPDATE SET
notes = EXCLUDED.notes,
updated_at = NOW();
-- Test Documents (using Go/GORM schema)
INSERT INTO task_document (id, created_at, updated_at, residence_id, created_by_id, title, description, document_type, file_url, file_name, purchase_date, expiry_date, purchase_price, vendor, serial_number, is_active)
VALUES
(1, NOW(), NOW(), 1, 2, 'HVAC Warranty', 'Warranty for central air system', 'warranty', '/uploads/docs/hvac_warranty.pdf', 'hvac_warranty.pdf', '2023-06-15', '2028-06-15', 5000.00, 'Cool Air HVAC', 'HVAC-2023-001', true),
(2, NOW(), NOW(), 1, 2, 'Home Insurance Policy', 'Annual home insurance', 'insurance', '/uploads/docs/insurance.pdf', 'insurance.pdf', '2024-01-01', '2025-01-01', 1200.00, 'State Farm', NULL, true),
(3, NOW(), NOW(), 1, 2, 'Refrigerator Manual', 'User manual for kitchen fridge', 'manual', '/uploads/docs/fridge_manual.pdf', 'fridge_manual.pdf', '2022-03-20', NULL, 1500.00, 'Best Buy', 'LG-RF-2022', true),
(4, NOW(), NOW(), 3, 3, 'Lease Agreement', 'Apartment lease contract', 'contract', '/uploads/docs/lease.pdf', 'lease.pdf', '2024-01-01', '2025-01-01', NULL, 'Downtown Properties LLC', NULL, true)
ON CONFLICT (id) DO UPDATE SET
title = EXCLUDED.title,
description = EXCLUDED.description,
updated_at = NOW();
-- Test Notifications
INSERT INTO notifications_notification (id, created_at, updated_at, user_id, notification_type, title, body, task_id, sent, read)
VALUES
(1, NOW() - INTERVAL '1 day', NOW() - INTERVAL '1 day', 2, 'task_due_soon', 'Task Due Soon', 'Fix leaky faucet is due in 7 days', 1, true, false),
(2, NOW() - INTERVAL '2 days', NOW() - INTERVAL '2 days', 2, 'task_completed', 'Task Completed', 'Mow lawn has been marked as completed', 4, true, true),
(3, NOW() - INTERVAL '3 days', NOW() - INTERVAL '3 days', 3, 'task_assigned', 'Task Assigned', 'You have been assigned to: Mow lawn', 4, true, true)
ON CONFLICT (id) DO UPDATE SET
title = EXCLUDED.title,
body = EXCLUDED.body,
updated_at = NOW();
-- Notification Preferences
INSERT INTO notifications_notificationpreference (id, created_at, updated_at, user_id, task_due_soon, task_overdue, task_completed, task_assigned, residence_shared, warranty_expiring)
VALUES
(1, NOW(), NOW(), 1, true, true, true, true, true, true),
(2, NOW(), NOW(), 2, true, true, true, true, true, true),
(3, NOW(), NOW(), 3, true, true, false, true, true, false),
(4, NOW(), NOW(), 4, false, false, false, false, false, false)
ON CONFLICT (id) DO UPDATE SET
task_due_soon = EXCLUDED.task_due_soon,
task_overdue = EXCLUDED.task_overdue,
updated_at = NOW();
-- Reset sequences
SELECT setval('auth_user_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM auth_user), false);
SELECT setval('subscription_usersubscription_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM subscription_usersubscription), false);
SELECT setval('residence_residence_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM residence_residence), false);
SELECT setval('task_contractor_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_contractor), false);
SELECT setval('task_task_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_task), false);
SELECT setval('task_taskcompletion_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_taskcompletion), false);
SELECT setval('task_document_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM task_document), false);
SELECT setval('notifications_notification_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM notifications_notification), false);
SELECT setval('notifications_notificationpreference_id_seq', (SELECT COALESCE(MAX(id), 0) + 1 FROM notifications_notificationpreference), false);
-- Summary
DO $$
BEGIN
RAISE NOTICE 'Test data seeded successfully!';
RAISE NOTICE 'Test Users (password: password123):';
RAISE NOTICE ' - admin (admin@example.com) - Admin, Pro tier';
RAISE NOTICE ' - john (john@example.com) - Pro tier, owns 2 residences';
RAISE NOTICE ' - jane (jane@example.com) - Free tier, owns 1 residence, shared access to residence 1';
RAISE NOTICE ' - bob (bob@example.com) - Free tier';
END $$;