Common Workflows
Learn the most common development workflows in HiveForge.
User Management Workflows
Register a New User
// apps/web/src/lib/auth.ts
import { createClient } from '@/lib/supabase/client'
export async function signUp(email: string, password: string) {
const supabase = createClient()
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
},
})
if (error) throw error
return data
}Update User Profile
export async function updateProfile(userId: string, profile: {
full_name?: string
avatar_url?: string
bio?: string
}) {
const supabase = createClient()
const { data, error } = await supabase
.from('profiles')
.update(profile)
.eq('id', userId)
.select()
.single()
if (error) throw error
return data
}Organization Workflows
Create Organization
// Frontend
import { createOrganization } from '@/lib/api/organizations'
const org = await createOrganization({
name: 'Acme Corp',
slug: 'acme-corp',
tier: 'pro',
})# Backend
from app.services.organizations import OrganizationService
org = await OrganizationService.create(
name="Acme Corp",
slug="acme-corp",
owner_id=user_id,
tier="pro"
)Add Members to Organization
// Invite member
await inviteMember({
organizationId: org.id,
email: 'member@example.com',
role: 'member',
})
// Accept invitation
await acceptInvitation({
invitationId: invitation.id,
})Switch Active Organization
// Set active organization in session
import { setActiveOrganization } from '@/lib/organizations'
await setActiveOrganization(orgId)
// This updates the user's session and redirects to org dashboardBilling Workflows
Subscribe to Plan
import { createCheckoutSession } from '@/lib/stripe'
// Create Stripe checkout session
const session = await createCheckoutSession({
organizationId: org.id,
priceId: 'price_pro_monthly',
successUrl: '/dashboard/billing/success',
cancelUrl: '/dashboard/billing',
})
// Redirect to Stripe Checkout
window.location.href = session.urlHandle Webhook Events
# apps/api/app/routers/billing.py
from fastapi import Request
import stripe
@router.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
payload = await request.body()
sig_header = request.headers.get("stripe-signature")
try:
event = stripe.Webhook.construct_event(
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
)
except ValueError:
raise HTTPException(status_code=400)
# Handle different event types
if event.type == "customer.subscription.created":
await handle_subscription_created(event.data.object)
elif event.type == "customer.subscription.updated":
await handle_subscription_updated(event.data.object)
elif event.type == "customer.subscription.deleted":
await handle_subscription_deleted(event.data.object)
return {"status": "success"}Update Subscription
// Change plan
await updateSubscription({
organizationId: org.id,
newPriceId: 'price_enterprise_monthly',
})
// Cancel subscription
await cancelSubscription({
organizationId: org.id,
cancelAtPeriodEnd: true,
})
// Reactivate subscription
await reactivateSubscription({
organizationId: org.id,
})Permission Workflows
Check User Permissions
// Frontend
import { usePermissions } from '@/hooks/usePermissions'
function MyComponent() {
const { hasPermission, can } = usePermissions()
// Check single permission
const canEdit = hasPermission('organizations.update')
// Check multiple permissions (OR)
const canManage = can(['organizations.update', 'organizations.delete'])
return (
<div>
{canEdit && <EditButton />}
{canManage && <DeleteButton />}
</div>
)
}# Backend
from app.services.permissions import check_permission
@router.put("/organizations/{org_id}")
async def update_organization(
org_id: str,
data: OrganizationUpdate,
user: User = Depends(get_current_user)
):
# Check permission
has_permission = await check_permission(
user.id, org_id, "organizations.update"
)
if not has_permission:
raise HTTPException(status_code=403, detail="Permission denied")
# Update organization
org = await OrganizationService.update(org_id, data)
return orgAssign Roles
// Change user role in organization
await updateMemberRole({
organizationId: org.id,
userId: user.id,
role: 'admin', // owner, admin, member, viewer
})API Key Workflows
Generate API Key
const apiKey = await createAPIKey({
organizationId: org.id,
name: 'Production API Key',
scopes: ['read', 'write'],
expiresAt: new Date('2025-12-31'),
})
// Save the key securely - it's only shown once!
console.log(apiKey.key) // "hf_live_..."Use API Key
# Make authenticated requests
curl http://localhost:8000/api/users/me \
-H "X-API-Key: hf_live_..."Revoke API Key
await revokeAPIKey({
organizationId: org.id,
keyId: apiKey.id,
})Email Workflows
Send Transactional Email
# Backend
from app.services.email import EmailService
await EmailService.send(
to="user@example.com",
template="welcome",
data={
"user_name": "John Doe",
"verify_url": "https://app.hiveforge.dev/verify/..."
}
)Create Custom Email Template
// apps/web/emails/custom-notification.tsx
import {
Html,
Head,
Body,
Container,
Section,
Text,
Button,
Hr,
} from '@react-email/components'
interface CustomNotificationProps {
userName: string
message: string
actionUrl: string
actionText: string
}
export default function CustomNotification({
userName,
message,
actionUrl,
actionText,
}: CustomNotificationProps) {
return (
<Html>
<Head />
<Body style={main}>
<Container style={container}>
<Section>
<Text style={heading}>Hi {userName},</Text>
<Text style={paragraph}>{message}</Text>
<Button style={button} href={actionUrl}>
{actionText}
</Button>
</Section>
<Hr style={hr} />
<Text style={footer}>
HiveForge - Your SaaS Platform
</Text>
</Container>
</Body>
</Html>
)
}
const main = { backgroundColor: '#f6f9fc', fontFamily: 'sans-serif' }
const container = { backgroundColor: '#ffffff', margin: '0 auto', padding: '20px' }
const heading = { fontSize: '24px', fontWeight: 'bold' }
const paragraph = { fontSize: '16px', lineHeight: '26px' }
const button = { backgroundColor: '#5469d4', color: '#fff', padding: '12px 20px' }
const hr = { borderColor: '#e6ebf1', margin: '20px 0' }
const footer = { color: '#8898aa', fontSize: '12px' }Database Workflows
Create Migration
# Create new migration file
pnpm db:migration:create add_custom_field
# Edit the migration in supabase/migrations/
# Apply migration
pnpm db:migrateQuery with RLS
// Data automatically filtered by RLS policies
const supabase = createClient()
// Only returns data user has access to
const { data: orgs } = await supabase
.from('organizations')
.select('*')
// With specific filters
const { data: members } = await supabase
.from('organization_members')
.select('*, profiles(*)')
.eq('organization_id', orgId)
.in('role', ['admin', 'owner'])Seed Development Data
// supabase/seed.sql or custom seed script
import { createClient } from '@supabase/supabase-js'
async function seed() {
const supabase = createClient(url, serviceKey)
// Create test organization
const { data: org } = await supabase
.from('organizations')
.insert({
name: 'Test Org',
slug: 'test-org',
tier: 'pro',
})
.select()
.single()
// Create test users
for (let i = 1; i <= 5; i++) {
await supabase.auth.admin.createUser({
email: `user${i}@test.com`,
password: 'test123',
email_confirm: true,
})
}
}AI Integration Workflows
Generate Content
import { generateText } from '@/lib/ai'
const description = await generateText({
prompt: 'Write a product description for a project management tool',
maxTokens: 200,
temperature: 0.7,
})# Backend
from app.services.ai import AIService
result = await AIService.generate(
prompt="Write a product description for a project management tool",
max_tokens=200,
temperature=0.7
)Stream AI Response
// Frontend with streaming
const stream = await fetch('/api/ai/generate', {
method: 'POST',
body: JSON.stringify({ prompt, stream: true }),
})
const reader = stream.body.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value)
// Update UI with chunk
setGeneratedText(prev => prev + chunk)
}Webhook Workflows
Register Webhook
const webhook = await createWebhook({
organizationId: org.id,
url: 'https://api.example.com/webhooks',
events: [
'organization.member.added',
'subscription.updated',
'api_key.created',
],
secret: 'whsec_...',
})Receive Webhook
// Your webhook endpoint
import crypto from 'crypto'
export async function POST(request: Request) {
const payload = await request.text()
const signature = request.headers.get('x-hiveforge-signature')
// Verify signature
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex')
if (signature !== expectedSignature) {
return new Response('Invalid signature', { status: 401 })
}
// Process event
const event = JSON.parse(payload)
switch (event.type) {
case 'organization.member.added':
await handleMemberAdded(event.data)
break
case 'subscription.updated':
await handleSubscriptionUpdated(event.data)
break
}
return new Response('OK')
}Testing Workflows
Write Component Tests
// apps/web/src/components/__tests__/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from '../Button'
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>)
expect(screen.getByText('Click me')).toBeInTheDocument()
})
it('calls onClick when clicked', () => {
const onClick = jest.fn()
render(<Button onClick={onClick}>Click me</Button>)
fireEvent.click(screen.getByText('Click me'))
expect(onClick).toHaveBeenCalledTimes(1)
})
})Write API Tests
# apps/api/tests/test_organizations.py
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
async def test_create_organization(client: AsyncClient, auth_headers):
response = await client.post(
"/api/organizations",
headers=auth_headers,
json={
"name": "Test Org",
"slug": "test-org",
"tier": "pro"
}
)
assert response.status_code == 201
data = response.json()
assert data["name"] == "Test Org"
assert data["slug"] == "test-org"
@pytest.mark.asyncio
async def test_list_organizations(client: AsyncClient, auth_headers):
response = await client.get(
"/api/organizations",
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)E2E Tests
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test'
test('user can sign up and login', async ({ page }) => {
// Sign up
await page.goto('/auth/signup')
await page.fill('[name="email"]', 'test@example.com')
await page.fill('[name="password"]', 'password123')
await page.click('button[type="submit"]')
// Verify redirect
await expect(page).toHaveURL('/dashboard')
// Check user is logged in
await expect(page.locator('text=Dashboard')).toBeVisible()
})Deployment Workflows
Deploy Frontend
# Build and deploy to Netlify
pnpm build:web
netlify deploy --prod --dir=apps/web/out
# Or use Git integration for auto-deploy
git push origin mainDeploy Backend
# Deploy to Railway
railway up
# Or use Docker
docker build -t hiveforge-api -f apps/api/Dockerfile .
docker push your-registry/hiveforge-api:latestRun Migrations in Production
# Using Supabase CLI
supabase db push --project-id your-project-id
# Or via API migration endpoint
curl -X POST https://api.hiveforge.dev/admin/migrate \
-H "Authorization: Bearer $ADMIN_TOKEN"Monitoring Workflows
View Logs
# Frontend logs (Netlify)
netlify logs --prod
# Backend logs (Railway)
railway logs
# Or use monitoring dashboardTrack Errors
// apps/web/src/lib/sentry.ts
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
// Capture custom error
try {
await riskyOperation()
} catch (error) {
Sentry.captureException(error, {
tags: { section: 'billing' },
extra: { organizationId: org.id },
})
}