mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 15:51:15 +00:00
GET /v1/public/calendar/{token} serves an RFC 5545 calendar under a
token-as-secret URL (private Google/Outlook link model). Staff feeds
contain only that member's bookings; the tenant feed tags each event
with the staff member. Window is -30d to +180d; cancelled bookings map
to STATUS:CANCELLED, waitlisted/pending to TENTATIVE + TRANSPARENT.
Summaries resolve service or class-session titles, descriptions carry
localized contact labels, locations resolve to names.
Feed tokens live on tenants/staff (migration 00015) and are managed via
GET /v1/calendar/feed plus POST /v1/calendar/feed/rotate — rotating a
scope invalidates the old URL. ICS rendering is hand-rolled: TEXT
escaping, CRLF, 75-octet folding on rune boundaries.
Settings gains a "Kalendář (ICS)" card with copyable URLs and two-step
rotation per scope; demo mode points at the real seeded tokens.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
85 lines
2.4 KiB
Go
85 lines
2.4 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
)
|
|
|
|
const tenantColumns = "id, slug, name, preset, locale, timezone, plan_code, subscription_status, billing_provider, billing_customer_id, billing_subscription_id"
|
|
|
|
func (r *PGRepository) GetTenantByFeedToken(ctx context.Context, token string) (TenantRecord, error) {
|
|
var record TenantRecord
|
|
err := r.pool.QueryRow(ctx, `
|
|
SELECT `+tenantColumns+`
|
|
FROM tenants
|
|
WHERE feed_token = $1
|
|
`, token).Scan(
|
|
&record.ID, &record.Slug, &record.Name, &record.Preset, &record.Locale, &record.Timezone,
|
|
&record.PlanCode, &record.SubscriptionStatus, &record.BillingProvider,
|
|
&record.BillingCustomerID, &record.BillingSubscription,
|
|
)
|
|
return record, err
|
|
}
|
|
|
|
func (r *PGRepository) GetStaffByFeedToken(ctx context.Context, token string) (StaffRecord, error) {
|
|
var rec StaffRecord
|
|
err := r.pool.QueryRow(ctx, `
|
|
SELECT `+staffColumns+`
|
|
FROM staff
|
|
WHERE feed_token = $1
|
|
`, token).Scan(&rec.ID, &rec.TenantID, &rec.LocationID, &rec.DisplayName, &rec.Active, &rec.CreatedAt)
|
|
return rec, err
|
|
}
|
|
|
|
func (r *PGRepository) GetTenantFeedToken(ctx context.Context, tenantID string) (string, error) {
|
|
var token string
|
|
err := r.pool.QueryRow(ctx, `
|
|
SELECT feed_token FROM tenants WHERE id = $1
|
|
`, tenantID).Scan(&token)
|
|
return token, err
|
|
}
|
|
|
|
func (r *PGRepository) ListStaffFeedTokens(ctx context.Context, tenantID string) ([]StaffFeedTokenRecord, error) {
|
|
rows, err := r.pool.Query(ctx, `
|
|
SELECT id, display_name, feed_token
|
|
FROM staff
|
|
WHERE tenant_id = $1
|
|
ORDER BY created_at ASC
|
|
`, tenantID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var records []StaffFeedTokenRecord
|
|
for rows.Next() {
|
|
var rec StaffFeedTokenRecord
|
|
if err := rows.Scan(&rec.StaffID, &rec.DisplayName, &rec.FeedToken); err != nil {
|
|
return nil, err
|
|
}
|
|
records = append(records, rec)
|
|
}
|
|
return records, rows.Err()
|
|
}
|
|
|
|
func (r *PGRepository) RotateTenantFeedToken(ctx context.Context, tenantID string) (string, error) {
|
|
var token string
|
|
err := r.pool.QueryRow(ctx, `
|
|
UPDATE tenants
|
|
SET feed_token = encode(gen_random_bytes(16), 'hex'), updated_at = now()
|
|
WHERE id = $1
|
|
RETURNING feed_token
|
|
`, tenantID).Scan(&token)
|
|
return token, err
|
|
}
|
|
|
|
func (r *PGRepository) RotateStaffFeedToken(ctx context.Context, staffID string) (string, error) {
|
|
var token string
|
|
err := r.pool.QueryRow(ctx, `
|
|
UPDATE staff
|
|
SET feed_token = encode(gen_random_bytes(16), 'hex')
|
|
WHERE id = $1
|
|
RETURNING feed_token
|
|
`, staffID).Scan(&token)
|
|
return token, err
|
|
}
|