chore: update SQL script, dev script, and add zxcvbnjs
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
|
|||||||
|
const { zxcvbn, zxcvbnOptions } = require('@zxcvbn-ts/core');
|
||||||
|
const { dictionary: commonDictionary, adjacencyGraphs } = require('@zxcvbn-ts/language-common');
|
||||||
|
const { dictionary: enDictionary, translations } = require('@zxcvbn-ts/language-en');
|
||||||
|
|
||||||
|
zxcvbnOptions.setOptions({
|
||||||
|
dictionary: {
|
||||||
|
...commonDictionary,
|
||||||
|
...enDictionary,
|
||||||
|
},
|
||||||
|
graphs: adjacencyGraphs,
|
||||||
|
translations,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Called from Go via goja
|
||||||
|
globalThis.zxcvbnScore = function (password) {
|
||||||
|
return zxcvbn(password).score;
|
||||||
|
};
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// Package zxcvbnjs wraps @zxcvbn-ts/core via goja (ExecJS-style) for exact
|
||||||
|
// parity between frontend and backend password strength scoring.
|
||||||
|
//
|
||||||
|
// The bundled JS contains the same @zxcvbn-ts/core library used in the
|
||||||
|
// SvelteKit frontend, so scores are identical for any given password.
|
||||||
|
package zxcvbnjs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed bundle.js
|
||||||
|
var bundleFS embed.FS
|
||||||
|
|
||||||
|
var (
|
||||||
|
vm *goja.Runtime
|
||||||
|
vmOnce sync.Once
|
||||||
|
vmErr error
|
||||||
|
)
|
||||||
|
|
||||||
|
func getVM() (*goja.Runtime, error) {
|
||||||
|
vmOnce.Do(func() {
|
||||||
|
vm = goja.New()
|
||||||
|
bundleData, err := bundleFS.ReadFile("bundle.js")
|
||||||
|
if err != nil {
|
||||||
|
vmErr = fmt.Errorf("failed to read zxcvbn bundle: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = vm.RunScript("zxcvbn-bundle.js", string(bundleData))
|
||||||
|
if err != nil {
|
||||||
|
vmErr = fmt.Errorf("failed to evaluate zxcvbn bundle: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return vm, vmErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score returns the zxcvbn-ts score (0-4) for the given password.
|
||||||
|
// It uses the exact same @zxcvbn-ts/core library as the frontend,
|
||||||
|
// guaranteeing identical results for the same input.
|
||||||
|
func Score(password string) (int, error) {
|
||||||
|
runtime, err := getVM()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("zxcvbnjs init error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zxcvbnScore, ok := goja.AssertFunction(runtime.Get("zxcvbnScore"))
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("zxcvbnScore not found in JS runtime")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := zxcvbnScore(goja.Undefined(), runtime.ToValue(password))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("zxcvbnjs: eval error: %v", err)
|
||||||
|
return 0, fmt.Errorf("zxcvbnScore eval failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
score := int(result.ToInteger())
|
||||||
|
if score < 0 || score > 4 {
|
||||||
|
return 0, fmt.Errorf("unexpected zxcvbn score: %d", score)
|
||||||
|
}
|
||||||
|
return score, nil
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ CREATE TYPE account_type AS ENUM ('email', 'google', 'microsoft', 'facebook', 'g
|
|||||||
CREATE TYPE payment_type AS ENUM ('deposit', 'full', 'tip', 'balance', 'partial');
|
CREATE TYPE payment_type AS ENUM ('deposit', 'full', 'tip', 'balance', 'partial');
|
||||||
CREATE TYPE payment_method AS ENUM ('online_square', 'in_person_card', 'cash', 'giftcard', 'discount', 'on_the_house');
|
CREATE TYPE payment_method AS ENUM ('online_square', 'in_person_card', 'cash', 'giftcard', 'discount', 'on_the_house');
|
||||||
CREATE TYPE payment_status AS ENUM ('pending', 'completed', 'failed', 'refunded');
|
CREATE TYPE payment_status AS ENUM ('pending', 'completed', 'failed', 'refunded');
|
||||||
CREATE TYPE booking_status AS ENUM ('pending', 'confirmed', 'in_progress', 'completed', 'client_cancelled', 'we_cancelled', 're-schedule', 'no_show', 'no_deposit');
|
CREATE TYPE booking_status AS ENUM ('pending', 'confirmed', 'in_progress', 'completed', 'client_cancelled', 'we_cancelled', 'no_show', 'pending_release', 'deposit_lapsed');
|
||||||
CREATE TYPE campaign_type AS ENUM ('time_based', 'milestone');
|
CREATE TYPE campaign_type AS ENUM ('time_based', 'milestone');
|
||||||
CREATE TYPE milestone_type AS ENUM ('per_user_booking_count', 'global_booking_count', 'anniversary');
|
CREATE TYPE milestone_type AS ENUM ('per_user_booking_count', 'global_booking_count', 'anniversary');
|
||||||
CREATE TYPE milestone_unit AS ENUM ('bookings', 'months', 'years');
|
CREATE TYPE milestone_unit AS ENUM ('bookings', 'months', 'years');
|
||||||
@@ -76,6 +76,14 @@ CREATE OR REPLACE FUNCTION generate_square_deposit_id() RETURNS CHAR(12) AS $$
|
|||||||
SELECT generate_short_id('square_deposits');
|
SELECT generate_short_id('square_deposits');
|
||||||
$$ LANGUAGE sql;
|
$$ LANGUAGE sql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION generate_patch_test_id() RETURNS CHAR(12) AS $$
|
||||||
|
SELECT generate_short_id('patch_tests');
|
||||||
|
$$ LANGUAGE sql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION generate_user_patch_test_id() RETURNS CHAR(12) AS $$
|
||||||
|
SELECT generate_short_id('user_patch_tests');
|
||||||
|
$$ LANGUAGE sql;
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION generate_verification_code() RETURNS CHAR(12) AS $$ SELECT substr(encode(gen_random_bytes(6), 'hex'), 1, 12); $$ LANGUAGE sql;
|
CREATE OR REPLACE FUNCTION generate_verification_code() RETURNS CHAR(12) AS $$ SELECT substr(encode(gen_random_bytes(6), 'hex'), 1, 12); $$ LANGUAGE sql;
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION generate_referral_code()
|
CREATE OR REPLACE FUNCTION generate_referral_code()
|
||||||
@@ -124,6 +132,9 @@ CREATE TABLE users (
|
|||||||
-- Security fields
|
-- Security fields
|
||||||
password_hash TEXT, -- NULL for pure social logins
|
password_hash TEXT, -- NULL for pure social logins
|
||||||
last_login_at TIMESTAMPTZ DEFAULT NOW(),
|
last_login_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
-- Account lockout fields
|
||||||
|
failed_attempts INT NOT NULL DEFAULT 0,
|
||||||
|
locked_until TIMESTAMPTZ,
|
||||||
-- Loyalty fields
|
-- Loyalty fields
|
||||||
loyalty_stamps INT NOT NULL DEFAULT 0,
|
loyalty_stamps INT NOT NULL DEFAULT 0,
|
||||||
referral_code CHAR(12) UNIQUE DEFAULT generate_referral_code(),
|
referral_code CHAR(12) UNIQUE DEFAULT generate_referral_code(),
|
||||||
@@ -156,6 +167,10 @@ CREATE TABLE user_social_logins (
|
|||||||
|
|
||||||
CREATE INDEX idx_users_email_lower ON users (LOWER(email));
|
CREATE INDEX idx_users_email_lower ON users (LOWER(email));
|
||||||
CREATE INDEX idx_users_account_role ON users (account_role);
|
CREATE INDEX idx_users_account_role ON users (account_role);
|
||||||
|
-- pg_trgm GIN indexes for ILIKE search on user-facing columns
|
||||||
|
CREATE INDEX idx_users_fn_trgm ON users USING GIN (fn gin_trgm_ops);
|
||||||
|
CREATE INDEX idx_users_email_trgm ON users USING GIN (email gin_trgm_ops);
|
||||||
|
CREATE INDEX idx_users_phone_trgm ON users USING GIN (phone gin_trgm_ops);
|
||||||
|
|
||||||
-- Enforce unique email only for registered (non-guest) users
|
-- Enforce unique email only for registered (non-guest) users
|
||||||
-- Guest accounts can share emails; registered accounts cannot
|
-- Guest accounts can share emails; registered accounts cannot
|
||||||
@@ -185,7 +200,7 @@ CREATE INDEX idx_verification_codes_expires ON verification_codes (expires_at) W
|
|||||||
-- =======================================
|
-- =======================================
|
||||||
|
|
||||||
CREATE TABLE patch_tests (
|
CREATE TABLE patch_tests (
|
||||||
id CHAR(12) PRIMARY KEY DEFAULT generate_service_id(),
|
id CHAR(12) PRIMARY KEY DEFAULT generate_patch_test_id(),
|
||||||
name VARCHAR(100) NOT NULL,
|
name VARCHAR(100) NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
notice_duration_hours INT NOT NULL DEFAULT 24,
|
notice_duration_hours INT NOT NULL DEFAULT 24,
|
||||||
@@ -200,7 +215,7 @@ CREATE INDEX idx_patch_tests_name ON patch_tests(name);
|
|||||||
-- =======================================
|
-- =======================================
|
||||||
|
|
||||||
CREATE TABLE user_patch_tests (
|
CREATE TABLE user_patch_tests (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id CHAR(12) PRIMARY KEY DEFAULT generate_user_patch_test_id(),
|
||||||
user_id CHAR(12) REFERENCES users(id) ON DELETE SET NULL,
|
user_id CHAR(12) REFERENCES users(id) ON DELETE SET NULL,
|
||||||
patch_test_id CHAR(12) NOT NULL REFERENCES patch_tests(id) ON DELETE CASCADE,
|
patch_test_id CHAR(12) NOT NULL REFERENCES patch_tests(id) ON DELETE CASCADE,
|
||||||
tested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
tested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
@@ -247,6 +262,8 @@ CREATE TABLE custom_services (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_custom_services_name ON custom_services(name);
|
CREATE INDEX idx_custom_services_name ON custom_services(name);
|
||||||
|
CREATE INDEX idx_custom_services_name_trgm ON custom_services USING GIN (name gin_trgm_ops);
|
||||||
|
CREATE INDEX idx_custom_services_desc_trgm ON custom_services USING GIN (description gin_trgm_ops);
|
||||||
CREATE INDEX idx_custom_services_usage ON custom_services(usage_count DESC);
|
CREATE INDEX idx_custom_services_usage ON custom_services(usage_count DESC);
|
||||||
|
|
||||||
-- =======================================
|
-- =======================================
|
||||||
@@ -405,6 +422,7 @@ CREATE INDEX idx_time_blockers_cron ON time_blockers(cron_expression) WHERE cron
|
|||||||
CREATE TABLE forgiven_no_shows (
|
CREATE TABLE forgiven_no_shows (
|
||||||
id CHAR(12) PRIMARY KEY DEFAULT generate_short_id('forgiven_no_shows'),
|
id CHAR(12) PRIMARY KEY DEFAULT generate_short_id('forgiven_no_shows'),
|
||||||
booking_id CHAR(12) NOT NULL UNIQUE REFERENCES bookings(id) ON DELETE CASCADE,
|
booking_id CHAR(12) NOT NULL UNIQUE REFERENCES bookings(id) ON DELETE CASCADE,
|
||||||
|
forgiven_by CHAR(12),
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -586,7 +604,7 @@ INSERT INTO business_settings (
|
|||||||
'https://www.website.co.uk'
|
'https://www.website.co.uk'
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TYPE admin_notification_reason AS ENUM ('pending_booking', 'cancelled_booking', 'rescheduled_booking', '1_week_no_pay', '1_month_no_pay', 'affiliate_claim', 'late_cancellation', 'no_deposit', 'deposit_paid', 'edit_request', 'new_booking', 'edit_requested');
|
CREATE TYPE admin_notification_reason AS ENUM ('pending_booking', 'cancelled_booking', 'rescheduled_booking', '1_week_no_pay', '1_month_no_pay', 'affiliate_claim', 'late_cancellation', 'deposit_paid', 'edit_request', 'edit_requested', 'new_booking', 'deposit_not_paid_by_deadline');
|
||||||
|
|
||||||
CREATE TABLE admin_notifications (
|
CREATE TABLE admin_notifications (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
@@ -635,7 +653,9 @@ create table tags (
|
|||||||
|
|
||||||
-- Indexes
|
-- Indexes
|
||||||
create index idx_images_tag_names on images using gin(tag_names);
|
create index idx_images_tag_names on images using gin(tag_names);
|
||||||
-- create index idx_images_tag_names_trgm on images using gin ((tag_names::text[]) gin_trgm_ops);
|
-- pg_trgm GIN index not applicable to tag_names (text[] array).
|
||||||
|
-- For ILIKE search on unnested tags at scale, normalize into a separate image_tags table
|
||||||
|
-- and add: CREATE INDEX idx_image_tags_name_trgm ON image_tags USING GIN (name gin_trgm_ops);
|
||||||
create index idx_images_created_at on images(created_at desc);
|
create index idx_images_created_at on images(created_at desc);
|
||||||
create index idx_tags_name_trgm on tags using gin (name gin_trgm_ops);
|
create index idx_tags_name_trgm on tags using gin (name gin_trgm_ops);
|
||||||
|
|
||||||
@@ -778,7 +798,9 @@ BEGIN
|
|||||||
'data_retention_consent', data_retention_consent,
|
'data_retention_consent', data_retention_consent,
|
||||||
'data_consent_updated_at', data_consent_updated_at,
|
'data_consent_updated_at', data_consent_updated_at,
|
||||||
'created_at', created_at,
|
'created_at', created_at,
|
||||||
'updated_at', updated_at
|
'updated_at', updated_at,
|
||||||
|
'failed_attempts', failed_attempts,
|
||||||
|
'locked_until', locked_until
|
||||||
)
|
)
|
||||||
FROM users WHERE id = target_user_id
|
FROM users WHERE id = target_user_id
|
||||||
),
|
),
|
||||||
@@ -1029,11 +1051,51 @@ BEGIN
|
|||||||
JOIN bookings b ON fns.booking_id = b.id
|
JOIN bookings b ON fns.booking_id = b.id
|
||||||
WHERE b.user_id = target_user_id
|
WHERE b.user_id = target_user_id
|
||||||
),
|
),
|
||||||
|
'gift_card_balance', (
|
||||||
|
SELECT COALESCE(
|
||||||
|
(SELECT json_build_object(
|
||||||
|
'balance', COALESCE(ugb.balance, 0),
|
||||||
|
'updated_at', ugb.updated_at
|
||||||
|
) FROM user_giftcard_balances ugb WHERE ugb.user_id = target_user_id),
|
||||||
|
json_build_object('balance', 0, 'updated_at', NULL)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'gift_card_transactions', (
|
||||||
|
SELECT COALESCE(json_agg(json_build_object(
|
||||||
|
'id', gct.id,
|
||||||
|
'transaction_type', gct.transaction_type,
|
||||||
|
'amount', gct.amount,
|
||||||
|
'reference_type', gct.reference_type,
|
||||||
|
'reference_id', gct.reference_id,
|
||||||
|
'notes', gct.notes,
|
||||||
|
'created_at', gct.created_at
|
||||||
|
) ORDER BY gct.created_at DESC), '[]'::json)
|
||||||
|
FROM gift_card_transactions gct
|
||||||
|
WHERE gct.user_id = target_user_id
|
||||||
|
),
|
||||||
|
'login_audit', (
|
||||||
|
(SELECT json_agg(json_build_object(
|
||||||
|
'attempt_type', la.attempt_type,
|
||||||
|
'ip_address', la.ip_address::text,
|
||||||
|
'success', la.success,
|
||||||
|
'created_at', la.created_at
|
||||||
|
) ORDER BY la.created_at DESC)
|
||||||
|
FROM login_audit la WHERE la.user_id = target_user_id)
|
||||||
|
),
|
||||||
|
'refresh_tokens', (
|
||||||
|
(SELECT json_agg(json_build_object(
|
||||||
|
'role', rt.role,
|
||||||
|
'revoked', rt.revoked,
|
||||||
|
'created_at', rt.created_at,
|
||||||
|
'expires_at', rt.expires_at
|
||||||
|
) ORDER BY rt.created_at DESC)
|
||||||
|
FROM refresh_tokens rt WHERE rt.user_id = target_user_id)
|
||||||
|
),
|
||||||
'export_metadata', json_build_object(
|
'export_metadata', json_build_object(
|
||||||
'exported_at', NOW(),
|
'exported_at', NOW(),
|
||||||
'exported_by', 'system',
|
'exported_by', 'system',
|
||||||
'user_id', target_user_id,
|
'user_id', target_user_id,
|
||||||
'format_version', '0.9'
|
'format_version', '1.0'
|
||||||
)
|
)
|
||||||
) INTO result;
|
) INTO result;
|
||||||
|
|
||||||
@@ -1966,6 +2028,44 @@ INSERT INTO dav_calendars (principaluri, displayname, uri, description, componen
|
|||||||
SELECT 'principals/default', 'Default Calendar', 'default', 'Default calendar', 'VEVENT,VTODO', false
|
SELECT 'principals/default', 'Default Calendar', 'default', 'Default calendar', 'VEVENT,VTODO', false
|
||||||
WHERE NOT EXISTS (SELECT 1 FROM dav_calendars WHERE uri = 'default');
|
WHERE NOT EXISTS (SELECT 1 FROM dav_calendars WHERE uri = 'default');
|
||||||
|
|
||||||
|
-- =======================================
|
||||||
|
-- PG_TRGM GIN INDEXES FOR ILIKE SEARCH WITH LEADING WILDCARDS
|
||||||
|
-- These indexes enable efficient ILIKE search patterns like '%search%'
|
||||||
|
-- by using trigram matching via the pg_trgm extension (already enabled).
|
||||||
|
-- =======================================
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_bookings_notes_trgm ON bookings USING GIN (notes gin_trgm_ops);
|
||||||
|
|
||||||
|
-- users: n_first_name and n_last_name for admin user search
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_users_n_first_name_trgm ON users USING GIN (n_first_name gin_trgm_ops);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_users_n_last_name_trgm ON users USING GIN (n_last_name gin_trgm_ops);
|
||||||
|
|
||||||
|
-- services: name for booking search (ILIKE with leading wildcard; B-tree idx_services_name is useless for that)
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_services_name_trgm ON services USING GIN (name gin_trgm_ops);
|
||||||
|
|
||||||
|
-- images: url and thumbnail_url for portfolio image lookup
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_images_url_trgm ON images USING GIN (url gin_trgm_ops);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_images_thumbnail_url_trgm ON images USING GIN (thumbnail_url gin_trgm_ops);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- time_blockers: description for ILIKE ANY search
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_time_blockers_desc_trgm ON time_blockers USING GIN (description gin_trgm_ops);
|
||||||
|
|
||||||
|
-- Account lockout audit log
|
||||||
|
CREATE TABLE IF NOT EXISTS login_audit (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_id CHAR(12) REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
ip_address INET NOT NULL,
|
||||||
|
attempt_type TEXT NOT NULL, -- 'login' | 'register' | 'refresh'
|
||||||
|
success BOOLEAN NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_login_audit_user_id ON login_audit (user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_login_audit_ip ON login_audit (ip_address);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_login_audit_created_at ON login_audit (created_at);
|
||||||
|
|
||||||
-- =======================================
|
-- =======================================
|
||||||
-- FUNCTION USAGE SUMMARY
|
-- FUNCTION USAGE SUMMARY
|
||||||
-- =======================================
|
-- =======================================
|
||||||
@@ -2031,3 +2131,30 @@ even after consent withdrawal. Only optional/marketing data is consent-governed.
|
|||||||
• VAT registration day → enable_vat_registration() once, then apply_vat_to_payment()
|
• VAT registration day → enable_vat_registration() once, then apply_vat_to_payment()
|
||||||
• Dashboard load → get_sales_totals() + get_monthly_business_summary()
|
• Dashboard load → get_sales_totals() + get_monthly_business_summary()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- Auth tables
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Revoked JTIs for JWT token revocation
|
||||||
|
CREATE TABLE IF NOT EXISTS revoked_jtis (
|
||||||
|
jti TEXT PRIMARY KEY,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
expires_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_revoked_jtis_expires_at ON revoked_jtis (expires_at);
|
||||||
|
|
||||||
|
-- Refresh tokens with rotation tracking
|
||||||
|
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
token_hash TEXT NOT NULL UNIQUE,
|
||||||
|
role TEXT NOT NULL,
|
||||||
|
revoked BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
expires_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens (user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_expires_at ON refresh_tokens (expires_at);
|
||||||
|
|||||||
+347
-133
@@ -138,6 +138,7 @@ tmux set-environment -t $SESSION_NAME SQUARE_ACCESS_TOKEN "${SQUARE_ACCESS_TOKEN
|
|||||||
tmux set-environment -t $SESSION_NAME SQUARE_LOCATION_ID "${SQUARE_LOCATION_ID:-}"
|
tmux set-environment -t $SESSION_NAME SQUARE_LOCATION_ID "${SQUARE_LOCATION_ID:-}"
|
||||||
tmux set-environment -t $SESSION_NAME SQUARE_ENVIRONMENT "${SQUARE_ENVIRONMENT:-mock}"
|
tmux set-environment -t $SESSION_NAME SQUARE_ENVIRONMENT "${SQUARE_ENVIRONMENT:-mock}"
|
||||||
tmux set-environment -t $SESSION_NAME SQUARE_WEBHOOK_SIGNATURE_KEY "${SQUARE_WEBHOOK_SIGNATURE_KEY:-}"
|
tmux set-environment -t $SESSION_NAME SQUARE_WEBHOOK_SIGNATURE_KEY "${SQUARE_WEBHOOK_SIGNATURE_KEY:-}"
|
||||||
|
tmux set-environment -t $SESSION_NAME GO_TESTING "1"
|
||||||
|
|
||||||
# Pane 0: Database
|
# Pane 0: Database
|
||||||
tmux send-keys -t $SESSION_NAME 'docker exec -it postgres psql -U myuser -d mydb -c "SELECT relname AS table_name, n_live_tup AS row_count FROM pg_stat_user_tables ORDER BY table_name;"'
|
tmux send-keys -t $SESSION_NAME 'docker exec -it postgres psql -U myuser -d mydb -c "SELECT relname AS table_name, n_live_tup AS row_count FROM pg_stat_user_tables ORDER BY table_name;"'
|
||||||
@@ -353,6 +354,7 @@ POPPY_ID=$(get_user_id "poppy.thompson@example.com")
|
|||||||
MIA_ID=$(get_user_id "mia.white@example.com")
|
MIA_ID=$(get_user_id "mia.white@example.com")
|
||||||
GRACE_ID=$(get_user_id "grace.fletcher@example.com")
|
GRACE_ID=$(get_user_id "grace.fletcher@example.com")
|
||||||
LIAM_ID=$(get_user_id "liam.caldwell@example.com")
|
LIAM_ID=$(get_user_id "liam.caldwell@example.com")
|
||||||
|
THOMAS_ID=$(get_user_id "thomas.evans@example.com")
|
||||||
|
|
||||||
echo "${C_GREEN}✅ Authentication successful${C_RESET}"
|
echo "${C_GREEN}✅ Authentication successful${C_RESET}"
|
||||||
sleep 1
|
sleep 1
|
||||||
@@ -439,35 +441,167 @@ else
|
|||||||
echo "${C_YELLOW}⚠️ Patch test seeding failed (check DB connection or patch_tests table)${C_RESET}"
|
echo "${C_YELLOW}⚠️ Patch test seeding failed (check DB connection or patch_tests table)${C_RESET}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Seed-day shorthand helpers (used by time-blockers, bookings, and beyond)
|
||||||
|
format_london_time() {
|
||||||
|
TZ=Europe/London date -d "$1 $2" +"%Y-%m-%dT%H:%M:%S%:z"
|
||||||
|
}
|
||||||
|
# Returns the nearest OPEN business day at or after the given date.
|
||||||
|
# Business days: Tue(2)-Sat(6). Closed: Sun(0), Mon(1). (GNU date %w)
|
||||||
|
open_day() {
|
||||||
|
local d="$1"
|
||||||
|
local max=7
|
||||||
|
for ((i=0; i<max; i++)); do
|
||||||
|
local dow
|
||||||
|
dow=$(TZ=Europe/London date -d "$d" +%w)
|
||||||
|
if [[ "$dow" == "0" || "$dow" == "1" ]]; then
|
||||||
|
d=$(TZ=Europe/London date -d "$d +1 day" +%Y-%m-%d)
|
||||||
|
else
|
||||||
|
echo "$d"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "$d"
|
||||||
|
}
|
||||||
|
# Same but shifts BACKWARDS to find an open day (for past bookings)
|
||||||
|
open_day_past() {
|
||||||
|
local d="$1"
|
||||||
|
local max=7
|
||||||
|
for ((i=0; i<max; i++)); do
|
||||||
|
local dow
|
||||||
|
dow=$(TZ=Europe/London date -d "$d" +%w)
|
||||||
|
if [[ "$dow" == "0" || "$dow" == "1" ]]; then
|
||||||
|
d=$(TZ=Europe/London date -d "$d -1 day" +%Y-%m-%d)
|
||||||
|
else
|
||||||
|
echo "$d"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "$d"
|
||||||
|
}
|
||||||
|
TODAY=$(TZ=Europe/London date +%Y-%m-%d)
|
||||||
|
TOMORROW=$(open_day "$(TZ=Europe/London date -d "tomorrow" +%Y-%m-%d)")
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 7. TIME BLOCKERS (admin-blocked time)
|
||||||
|
# ===========================================================================
|
||||||
|
echo -e "\n${C_BLUE}🚫 Creating Time Blockers...${C_RESET}"
|
||||||
|
count_blockers=0
|
||||||
|
|
||||||
|
tb() {
|
||||||
|
local time="$1" dur="$2" desc="$3"
|
||||||
|
local json="{\"start_time\":\"$time\",\"duration_minutes\":$dur,\"description\":\"$desc\"}"
|
||||||
|
local resp=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -H "Authorization: Bearer $ADMIN_TOKEN" -d "$json" "$BASE_URL/admin/time-blockers")
|
||||||
|
local code=$(echo "$resp" | tail -n1)
|
||||||
|
[[ "$code" =~ ^2 ]] && count_blockers=$((count_blockers+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
tb "$(format_london_time "$TOMORROW" "14:00:00")" 60 "Staff meeting"
|
||||||
|
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +7 days" +%Y-%m-%d)")" "$SLOT_A")" 120 "Holiday — closed morning"
|
||||||
|
tb "$(format_london_time "$TOMORROW" "09:00:00")" 120 "Late start — closed until 11am"
|
||||||
|
|
||||||
|
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +3 days" +%Y-%m-%d)")" "$SLOT_B")" 90 "Equipment maintenance"
|
||||||
|
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +10 days" +%Y-%m-%d)")" "$SLOT_C")" 60 "Training session"
|
||||||
|
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +14 days" +%Y-%m-%d)")" "09:00:00")" 60 "Opening delay"
|
||||||
|
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +5 days" +%Y-%m-%d)")" "$SLOT_D")" 45 "Supplier visit"
|
||||||
|
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +8 days" +%Y-%m-%d)")" "$SLOT_A")" 120 "Deep clean — morning closed"
|
||||||
|
|
||||||
|
echo "${C_GREEN}✅ Created $count_blockers Time Blockers${C_RESET}"
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# 4. BOOKINGS
|
# 4. BOOKINGS
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
echo -e "\n${C_BLUE}📅 Creating Bookings...${C_RESET}"
|
echo -e "\n${C_BLUE}📅 Creating Bookings...${C_RESET}"
|
||||||
|
|
||||||
format_london_time() {
|
# Calculate total duration for a services JSON array by looking up each
|
||||||
TZ=Europe/London date -d "$1 $2" +"%Y-%m-%dT%H:%M:%S%:z"
|
# service index in the SERVICES array and summing duration_minutes.
|
||||||
|
calc_duration() {
|
||||||
|
local services_json="$1"
|
||||||
|
local total=0
|
||||||
|
local ids
|
||||||
|
ids=$(echo "$services_json" | tr -d '[]"' | tr ',' '\n')
|
||||||
|
while IFS= read -r sid; do
|
||||||
|
[[ -z "$sid" ]] && continue
|
||||||
|
for idx in "${!SERVICE_IDS[@]}"; do
|
||||||
|
if [[ "${SERVICE_IDS[$idx]}" == "$sid" ]]; then
|
||||||
|
local raw="${SERVICES[$idx]}"
|
||||||
|
local dur
|
||||||
|
dur=$(echo "$raw" | grep -o '"duration_minutes":[0-9]*' | cut -d: -f2)
|
||||||
|
total=$((total + dur))
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done <<< "$ids"
|
||||||
|
echo "$total"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a booking as a regular user, capture LAST_BOOKING_ID
|
# Query /api/available-hours for a date and pick a valid start time at
|
||||||
create_booking() {
|
# 15-minute intervals (00/15/30/45) that fits the given duration.
|
||||||
local token=$1 time=$2 services=$3 notes=$4 name=$5
|
# Prints "HH:MM:SS" on success, empty string on failure.
|
||||||
local json="{\"start_time\":\"$time\",\"service_ids\":$services"
|
pick_avail_slot() {
|
||||||
[[ -n "$notes" ]] && json="$json,\"notes\":\"$notes\""
|
local date="$1" duration_minutes="$2"
|
||||||
json="$json}"
|
curl -s "$BASE_URL/scheduling/available-hours?start=$date&end=$date" 2>/dev/null | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
duration = int(sys.argv[1])
|
||||||
|
target = sys.argv[2]
|
||||||
|
for day in data:
|
||||||
|
if day.get('date') == target and day.get('isOpen'):
|
||||||
|
for slot in day.get('slots', []):
|
||||||
|
start_parts = slot.get('startTime', '00:00').split(':')
|
||||||
|
end_parts = slot.get('endTime', '00:00').split(':')
|
||||||
|
start_min = int(start_parts[0]) * 60 + int(start_parts[1])
|
||||||
|
end_min = int(end_parts[0]) * 60 + int(end_parts[1])
|
||||||
|
slot_start = ((start_min + 14) // 15) * 15
|
||||||
|
if slot_start + duration <= end_min:
|
||||||
|
h = slot_start // 60
|
||||||
|
m = slot_start % 60
|
||||||
|
print(f'{h:02d}:{m:02d}:00')
|
||||||
|
sys.exit(0)
|
||||||
|
sys.exit(1)
|
||||||
|
" "$duration_minutes" "$date" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
local id=$(api_post "$BASE_URL/bookings" "$json" "Book: $name" "$token")
|
# Create a booking as a regular user. Uses available-hours to pick a
|
||||||
if [[ -n "$id" && "$id" =~ ^[0-9a-f-]{8,}$ ]]; then
|
# valid start time within a 15-min increment that fits the services.
|
||||||
LAST_BOOKING_ID="$id"
|
create_booking() {
|
||||||
return 0
|
local token=$1 date=$2 services=$3 notes=$4 name=$5
|
||||||
else
|
# Calculate total duration for slot selection
|
||||||
|
local duration
|
||||||
|
duration=$(calc_duration "$services")
|
||||||
|
local slot
|
||||||
|
slot=$(pick_avail_slot "$date" "$duration")
|
||||||
|
if [[ -z "$slot" ]]; then
|
||||||
LAST_BOOKING_ID=""
|
LAST_BOOKING_ID=""
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
local time
|
||||||
|
time=$(format_london_time "$date" "$slot")
|
||||||
|
local json="{\"start_time\":\"$time\",\"service_ids\":$services"
|
||||||
|
[[ -n "$notes" ]] && json="$json,\"notes\":\"$notes\""
|
||||||
|
json="$json}"
|
||||||
|
local id
|
||||||
|
id=$(api_post "$BASE_URL/bookings" "$json" "$name" "$token")
|
||||||
|
if [[ -n "$id" && "$id" =~ ^[0-9a-f-]{8,}$ ]]; then
|
||||||
|
LAST_BOOKING_ID="$id"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
LAST_BOOKING_ID=""
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a booking as admin (no advance restriction, always confirmed)
|
# Create a booking as admin, using available-hours to pick a valid slot.
|
||||||
create_admin_booking() {
|
create_admin_booking() {
|
||||||
local user_id=$1 time=$2 services=$3 notes=$4 name=$5
|
local user_id=$1 date=$2 services=$3 notes=$4 name=$5
|
||||||
|
local duration
|
||||||
|
duration=$(calc_duration "$services")
|
||||||
|
local slot
|
||||||
|
slot=$(pick_avail_slot "$date" "$duration")
|
||||||
|
if [[ -z "$slot" ]]; then
|
||||||
|
LAST_BOOKING_ID=""
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
local time
|
||||||
|
time=$(format_london_time "$date" "$slot")
|
||||||
local json="{\"user_id\":\"$user_id\",\"start_time\":\"$time\",\"service_ids\":$services,\"service_overrides\":[],\"enforce_deposits\":false"
|
local json="{\"user_id\":\"$user_id\",\"start_time\":\"$time\",\"service_ids\":$services,\"service_overrides\":[],\"enforce_deposits\":false"
|
||||||
[[ -n "$notes" ]] && json="$json,\"notes\":\"$notes\""
|
[[ -n "$notes" ]] && json="$json,\"notes\":\"$notes\""
|
||||||
json="$json}"
|
json="$json}"
|
||||||
@@ -482,44 +616,6 @@ create_admin_booking() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Returns the nearest OPEN business day at or after the given date.
|
|
||||||
# dow: date's day-of-week as 0=Sun,...,6=Sat (GNU date %w)
|
|
||||||
open_day() {
|
|
||||||
local d="$1"
|
|
||||||
local max=7
|
|
||||||
for ((i=0; i<max; i++)); do
|
|
||||||
local dow
|
|
||||||
dow=$(TZ=Europe/London date -d "$d" +%w)
|
|
||||||
if [[ "$dow" == "0" || "$dow" == "6" ]]; then
|
|
||||||
d=$(TZ=Europe/London date -d "$d +1 day" +%Y-%m-%d)
|
|
||||||
else
|
|
||||||
echo "$d"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "$d"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Same but shifts BACKWARDS to find an open day (for past bookings)
|
|
||||||
open_day_past() {
|
|
||||||
local d="$1"
|
|
||||||
local max=7
|
|
||||||
for ((i=0; i<max; i++)); do
|
|
||||||
local dow
|
|
||||||
dow=$(TZ=Europe/London date -d "$d" +%w)
|
|
||||||
if [[ "$dow" == "0" || "$dow" == "6" ]]; then
|
|
||||||
d=$(TZ=Europe/London date -d "$d -1 day" +%Y-%m-%d)
|
|
||||||
else
|
|
||||||
echo "$d"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "$d"
|
|
||||||
}
|
|
||||||
|
|
||||||
TODAY=$(TZ=Europe/London date +%Y-%m-%d)
|
|
||||||
TOMORROW=$(open_day "$(TZ=Europe/London date -d "tomorrow" +%Y-%m-%d)")
|
|
||||||
|
|
||||||
# Arrays to collect upcoming booking IDs that need notes confirmed
|
# Arrays to collect upcoming booking IDs that need notes confirmed
|
||||||
PENDING_BOOKING_IDS=()
|
PENDING_BOOKING_IDS=()
|
||||||
PENDING_BOOKING_NAMES=()
|
PENDING_BOOKING_NAMES=()
|
||||||
@@ -533,53 +629,53 @@ count_past=0
|
|||||||
# Emma — loyal weekly gel client
|
# Emma — loyal weekly gel client
|
||||||
for day_offset in 7 14 21 28; do
|
for day_offset in 7 14 21 28; do
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$EMMA_ID" "$(format_london_time "$D" "$SLOT_A")" "[\"$(get_svc 1)\"]" "" "Emma - Gel Manicure $D"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$EMMA_ID" "$D" "[\"$(get_svc 1)\"]" "" "Emma - Gel Manicure $D"; then count_past=$((count_past+1)); fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Sophie — pedicure every few weeks
|
# Sophie — pedicure every few weeks
|
||||||
for day_offset in 6 20; do
|
for day_offset in 6 20; do
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$SOPHIE_ID" "$(format_london_time "$D" "$SLOT_C")" "[\"$(get_svc 2)\"]" "" "Sophie - Luxury Pedicure $D"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$SOPHIE_ID" ""$D"" "[\"$(get_svc 2)\"]" "" "Sophie - Luxury Pedicure $D"; then count_past=$((count_past+1)); fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Amelia — classic manicure regular
|
# Amelia — classic manicure regular
|
||||||
for day_offset in 5 19; do
|
for day_offset in 5 19; do
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$AMELIA_ID" "$(format_london_time "$D" "$SLOT_B")" "[\"$(get_svc 0)\"]" "" "Amelia - Classic Manicure $D"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$AMELIA_ID" "$D" "[\"$(get_svc 0)\"]" "" "Amelia - Classic Manicure $D"; then count_past=$((count_past+1)); fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Isla — mixed services
|
# Isla — mixed services
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -3 days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -3 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$ISLA_ID" "$(format_london_time "$D" "$SLOT_A")" "[\"$(get_svc 3)\"]" "" "Isla - Express Mani & Pedi"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$ISLA_ID" "$D" "[\"$(get_svc 3)\"]" "" "Isla - Express Mani & Pedi"; then count_past=$((count_past+1)); fi
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -17 days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -17 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$ISLA_ID" "$(format_london_time "$D" "$SLOT_C")" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "Isla - Classic + Nail Art"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$ISLA_ID" ""$D"" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "Isla - Classic + Nail Art"; then count_past=$((count_past+1)); fi
|
||||||
|
|
||||||
# Lily — gel with nail art combo
|
# Lily — gel with nail art combo
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -10 days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -10 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$LILY_ID" "$(format_london_time "$D" "$SLOT_D")" "[\"$(get_svc 1)\",\"$(get_svc 5)\"]" "" "Lily - Gel + Nail Art"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$LILY_ID" ""$D"" "[\"$(get_svc 1)\",\"$(get_svc 5)\"]" "" "Lily - Gel + Nail Art"; then count_past=$((count_past+1)); fi
|
||||||
|
|
||||||
# Ava — occasional pedicure
|
# Ava — occasional pedicure
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -12 days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -12 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$AVA_ID" "$(format_london_time "$D" "$SLOT_B")" "[\"$(get_svc 2)\"]" "" "Ava - Luxury Pedicure"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$AVA_ID" "$D" "[\"$(get_svc 2)\"]" "" "Ava - Luxury Pedicure"; then count_past=$((count_past+1)); fi
|
||||||
|
|
||||||
# Grace — gel client (patch test already recorded)
|
# Grace — gel client (patch test already recorded)
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -9 days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -9 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$GRACE_ID" "$(format_london_time "$D" "$SLOT_A")" "[\"$(get_svc 6)\"]" "" "Grace - Gel Full Set"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$GRACE_ID" "$D" "[\"$(get_svc 6)\"]" "" "Grace - Gel Full Set"; then count_past=$((count_past+1)); fi
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -23 days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -23 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$GRACE_ID" "$(format_london_time "$D" "$SLOT_A")" "[\"$(get_svc 7)\"]" "" "Grace - Luxury Gel Manicure"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$GRACE_ID" "$D" "[\"$(get_svc 7)\"]" "" "Grace - Luxury Gel Manicure"; then count_past=$((count_past+1)); fi
|
||||||
|
|
||||||
# Primary test user — variety of past bookings
|
# Primary test user — variety of past bookings
|
||||||
for day_offset in 2 4 8 11 15 18 20 22 24 26 28; do
|
for day_offset in 2 4 8 11 15 18 20 22 24 26 28; do
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -$day_offset days" +%Y-%m-%d)")
|
||||||
SVC_IDX=$(( (day_offset % 4) ))
|
SVC_IDX=$(( (day_offset % 4) ))
|
||||||
if create_admin_booking "$USER_USER_ID" "$(format_london_time "$D" "$SLOT_C")" "[\"$(get_svc $SVC_IDX)\"]" "" "User - Past booking $D"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$USER_USER_ID" ""$D"" "[\"$(get_svc $SVC_IDX)\"]" "" "User - Past booking $D"; then count_past=$((count_past+1)); fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Poppy / Mia — deposit-required past visits
|
# Poppy / Mia — deposit-required past visits
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -5 days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -5 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$POPPY_ID" "$(format_london_time "$D" "$SLOT_C")" "[\"$(get_svc 0)\"]" "" "Poppy - Past (deposit snapshotted)"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$POPPY_ID" ""$D"" "[\"$(get_svc 0)\"]" "" "Poppy - Past (deposit snapshotted)"; then count_past=$((count_past+1)); fi
|
||||||
D=$(open_day_past "$(TZ=Europe/London date -d "today -13 days" +%Y-%m-%d)")
|
D=$(open_day_past "$(TZ=Europe/London date -d "today -13 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$MIA_ID" "$(format_london_time "$D" "$SLOT_D")" "[\"$(get_svc 2)\"]" "" "Mia - Past"; then count_past=$((count_past+1)); fi
|
if create_admin_booking "$MIA_ID" ""$D"" "[\"$(get_svc 2)\"]" "" "Mia - Past"; then count_past=$((count_past+1)); fi
|
||||||
|
|
||||||
echo "${C_GREEN}✅ Created $count_past Past Bookings${C_RESET}"
|
echo "${C_GREEN}✅ Created $count_past Past Bookings${C_RESET}"
|
||||||
|
|
||||||
@@ -589,13 +685,13 @@ echo "${C_GREEN}✅ Created $count_past Past Bookings${C_RESET}"
|
|||||||
echo -e "\n${C_YELLOW}📅 Creating Today's Bookings...${C_RESET}"
|
echo -e "\n${C_YELLOW}📅 Creating Today's Bookings...${C_RESET}"
|
||||||
count_today=0
|
count_today=0
|
||||||
|
|
||||||
if create_admin_booking "$EMMA_ID" "$(format_london_time "$TODAY" "$SLOT_A")" "[\"$(get_svc 1)\"]" "" "Emma - Gel Manicure (today)"; then count_today=$((count_today+1)); fi
|
if create_admin_booking "$EMMA_ID" ""$TODAY"" "[\"$(get_svc 1)\"]" "" "Emma - Gel Manicure (today)"; then count_today=$((count_today+1)); fi
|
||||||
if create_admin_booking "$SOPHIE_ID" "$(format_london_time "$TODAY" "$SLOT_B")" "[\"$(get_svc 2)\"]" "" "Sophie - Luxury Pedicure (today)"; then count_today=$((count_today+1)); fi
|
if create_admin_booking "$SOPHIE_ID" ""$TODAY"" "[\"$(get_svc 2)\"]" "" "Sophie - Luxury Pedicure (today)"; then count_today=$((count_today+1)); fi
|
||||||
if create_admin_booking "$AMELIA_ID" "$(format_london_time "$TODAY" "$SLOT_C")" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "Amelia - Classic + Nail Art (today)"; then count_today=$((count_today+1)); fi
|
if create_admin_booking "$AMELIA_ID" ""$TODAY"" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "Amelia - Classic + Nail Art (today)"; then count_today=$((count_today+1)); fi
|
||||||
if create_admin_booking "$USER_USER_ID" "$(format_london_time "$TODAY" "$SLOT_D")" "[\"$(get_svc 3)\"]" "" "User - Express Mani & Pedi (today)"; then count_today=$((count_today+1)); fi
|
if create_admin_booking "$USER_USER_ID" ""$TODAY"" "[\"$(get_svc 3)\"]" "" "User - Express Mani & Pedi (today)"; then count_today=$((count_today+1)); fi
|
||||||
if create_admin_booking "$ISLA_ID" "$(format_london_time "$TODAY" "$SLOT_A")" "[\"$(get_svc 1)\"]" "" "Isla - Gel Manicure (today)"; then count_today=$((count_today+1)); fi
|
if create_admin_booking "$ISLA_ID" ""$TODAY"" "[\"$(get_svc 1)\"]" "" "Isla - Gel Manicure (today)"; then count_today=$((count_today+1)); fi
|
||||||
if create_admin_booking "$GRACE_ID" "$(format_london_time "$TODAY" "$SLOT_D")" "[\"$(get_svc 6)\"]" "" "Grace - Gel Full Set (today)"; then count_today=$((count_today+1)); fi
|
if create_admin_booking "$GRACE_ID" ""$TODAY"" "[\"$(get_svc 6)\"]" "" "Grace - Gel Full Set (today)"; then count_today=$((count_today+1)); fi
|
||||||
if create_admin_booking "$LILY_ID" "$(format_london_time "$TODAY" "$SLOT_C")" "[\"$(get_svc 0)\"]" "" "Lily - Classic Manicure (today)"; then count_today=$((count_today+1)); fi
|
if create_admin_booking "$LILY_ID" ""$TODAY"" "[\"$(get_svc 0)\"]" "" "Lily - Classic Manicure (today)"; then count_today=$((count_today+1)); fi
|
||||||
|
|
||||||
echo "${C_GREEN}✅ Created $count_today Today's Bookings${C_RESET}"
|
echo "${C_GREEN}✅ Created $count_today Today's Bookings${C_RESET}"
|
||||||
|
|
||||||
@@ -609,22 +705,22 @@ echo -e "\n${C_YELLOW}📅 Creating Tomorrow's Bookings...${C_RESET}"
|
|||||||
count_tomorrow=0
|
count_tomorrow=0
|
||||||
|
|
||||||
# Bulk of tomorrow's bookings — admin-created, all confirmed
|
# Bulk of tomorrow's bookings — admin-created, all confirmed
|
||||||
if create_admin_booking "$EMMA_ID" "$(format_london_time "$TOMORROW" "$SLOT_A")" "[\"$(get_svc 1)\"]" "" "Emma - Gel Manicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
if create_admin_booking "$EMMA_ID" ""$TOMORROW"" "[\"$(get_svc 1)\"]" "" "Emma - Gel Manicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
||||||
if create_admin_booking "$SOPHIE_ID" "$(format_london_time "$TOMORROW" "$SLOT_C")" "[\"$(get_svc 2)\"]" "" "Sophie - Luxury Pedicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
if create_admin_booking "$SOPHIE_ID" ""$TOMORROW"" "[\"$(get_svc 2)\"]" "" "Sophie - Luxury Pedicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
||||||
if create_admin_booking "$AMELIA_ID" "$(format_london_time "$TOMORROW" "$SLOT_D")" "[\"$(get_svc 3)\"]" "" "Amelia - Express Mani & Pedi (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
if create_admin_booking "$AMELIA_ID" ""$TOMORROW"" "[\"$(get_svc 3)\"]" "" "Amelia - Express Mani & Pedi (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
||||||
if create_admin_booking "$USER_USER_ID" "$(format_london_time "$TOMORROW" "$SLOT_B")" "[\"$(get_svc 0)\"]" "" "User - Classic Manicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
if create_admin_booking "$USER_USER_ID" ""$TOMORROW"" "[\"$(get_svc 0)\"]" "" "User - Classic Manicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
||||||
if create_admin_booking "$LILY_ID" "$(format_london_time "$TOMORROW" "$SLOT_C")" "[\"$(get_svc 2)\"]" "" "Lily - Luxury Pedicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
if create_admin_booking "$LILY_ID" ""$TOMORROW"" "[\"$(get_svc 2)\"]" "" "Lily - Luxury Pedicure (tomorrow)"; then count_tomorrow=$((count_tomorrow+1)); fi
|
||||||
|
|
||||||
# Pending bookings — admin-created (avoids enum bug), then force to pending via DB.
|
# Pending bookings — admin-created (avoids enum bug), then force to pending via DB.
|
||||||
# Notes are included so the admin UI shows the request context.
|
# Notes are included so the admin UI shows the request context.
|
||||||
if create_admin_booking "$ISLA_ID" "$(format_london_time "$TOMORROW" "$SLOT_E")" "[\"$(get_svc 1)\",\"$(get_svc 5)\"]" "Would love a French tip look with some floral art on the ring fingers if possible?" "Isla - Gel + Nail Art (tomorrow, pending)"; then
|
if create_admin_booking "$ISLA_ID" ""$TOMORROW"" "[\"$(get_svc 1)\",\"$(get_svc 5)\"]" "Would love a French tip look with some floral art on the ring fingers if possible?" "Isla - Gel + Nail Art (tomorrow, pending)"; then
|
||||||
count_tomorrow=$((count_tomorrow+1))
|
count_tomorrow=$((count_tomorrow+1))
|
||||||
if [[ -n "$LAST_BOOKING_ID" ]]; then
|
if [[ -n "$LAST_BOOKING_ID" ]]; then
|
||||||
docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1
|
||||||
PENDING_BOOKING_IDS+=("$LAST_BOOKING_ID"); PENDING_BOOKING_NAMES+=("Isla - Gel + Nail Art (pending)")
|
PENDING_BOOKING_IDS+=("$LAST_BOOKING_ID"); PENDING_BOOKING_NAMES+=("Isla - Gel + Nail Art (pending)")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if create_admin_booking "$AVA_ID" "$(format_london_time "$TOMORROW" "$SLOT_E")" "[\"$(get_svc 0)\"]" "Could I get a specific nail shape — stiletto if possible?" "Ava - Classic Manicure (tomorrow, pending)"; then
|
if create_admin_booking "$AVA_ID" ""$TOMORROW"" "[\"$(get_svc 0)\"]" "Could I get a specific nail shape — stiletto if possible?" "Ava - Classic Manicure (tomorrow, pending)"; then
|
||||||
count_tomorrow=$((count_tomorrow+1))
|
count_tomorrow=$((count_tomorrow+1))
|
||||||
if [[ -n "$LAST_BOOKING_ID" ]]; then
|
if [[ -n "$LAST_BOOKING_ID" ]]; then
|
||||||
docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1
|
||||||
@@ -655,7 +751,7 @@ for day_offset in {2..15}; do
|
|||||||
slot="${UP_SLOTS[$idx]}"
|
slot="${UP_SLOTS[$idx]}"
|
||||||
label="${UP_NAMES[$idx]}"
|
label="${UP_NAMES[$idx]}"
|
||||||
|
|
||||||
if create_admin_booking "$uid" "$(format_london_time "$FUTURE_DATE" "$slot")" "[\"$(get_svc $svc_idx)\"]" "" "$label ($FUTURE_DATE)"; then
|
if create_admin_booking "$uid" ""$FUTURE_DATE"" "[\"$(get_svc $svc_idx)\"]" "" "$label ($FUTURE_DATE)"; then
|
||||||
count_future=$((count_future+1))
|
count_future=$((count_future+1))
|
||||||
# NOTE: do NOT add admin bookings to PENDING_BOOKING_IDS — they're already confirmed
|
# NOTE: do NOT add admin bookings to PENDING_BOOKING_IDS — they're already confirmed
|
||||||
fi
|
fi
|
||||||
@@ -663,18 +759,18 @@ done
|
|||||||
|
|
||||||
# Multi-service admin bookings for variety
|
# Multi-service admin bookings for variety
|
||||||
D2=$(open_day "$(TZ=Europe/London date -d "$TODAY +3 days" +%Y-%m-%d)")
|
D2=$(open_day "$(TZ=Europe/London date -d "$TODAY +3 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$USER_USER_ID" "$(format_london_time "$D2" "$SLOT_B")" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "User - Classic + Nail Art (+3 days)"; then
|
if create_admin_booking "$USER_USER_ID" ""$D2"" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "User - Classic + Nail Art (+3 days)"; then
|
||||||
count_future=$((count_future+1))
|
count_future=$((count_future+1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
D3=$(open_day "$(TZ=Europe/London date -d "$TODAY +6 days" +%Y-%m-%d)")
|
D3=$(open_day "$(TZ=Europe/London date -d "$TODAY +6 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$EMMA_ID" "$(format_london_time "$D3" "$SLOT_A")" "[\"$(get_svc 1)\",\"$(get_svc 4)\"]" "" "Emma - Gel Manicure + Removal (+6 days)"; then
|
if create_admin_booking "$EMMA_ID" ""$D3"" "[\"$(get_svc 1)\",\"$(get_svc 4)\"]" "" "Emma - Gel Manicure + Removal (+6 days)"; then
|
||||||
count_future=$((count_future+1))
|
count_future=$((count_future+1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Bridal package — admin-created with notes, then forced to pending via DB.
|
# Bridal package — admin-created with notes, then forced to pending via DB.
|
||||||
D4=$(open_day "$(TZ=Europe/London date -d "$TODAY +12 days" +%Y-%m-%d)")
|
D4=$(open_day "$(TZ=Europe/London date -d "$TODAY +12 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$SOPHIE_ID" "$(format_london_time "$D4" "$SLOT_D")" "[\"$(get_svc 11)\"]" "Bride-to-be — please can we discuss nail art options beforehand?" "Sophie - Bridal Package (+12 days, pending)"; then
|
if create_admin_booking "$SOPHIE_ID" ""$D4"" "[\"$(get_svc 11)\"]" "Bride-to-be — please can we discuss nail art options beforehand?" "Sophie - Bridal Package (+12 days, pending)"; then
|
||||||
count_future=$((count_future+1))
|
count_future=$((count_future+1))
|
||||||
if [[ -n "$LAST_BOOKING_ID" ]]; then
|
if [[ -n "$LAST_BOOKING_ID" ]]; then
|
||||||
docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1
|
docker exec postgres psql -U myuser -d mydb -c "UPDATE bookings SET status = 'pending' WHERE id = '${LAST_BOOKING_ID}'" > /dev/null 2>&1
|
||||||
@@ -684,7 +780,7 @@ fi
|
|||||||
|
|
||||||
# Deposit-required user — admin-created to bypass deposit check
|
# Deposit-required user — admin-created to bypass deposit check
|
||||||
D5=$(open_day "$(TZ=Europe/London date -d "$TODAY +4 days" +%Y-%m-%d)")
|
D5=$(open_day "$(TZ=Europe/London date -d "$TODAY +4 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$POPPY_ID" "$(format_london_time "$D5" "$SLOT_C")" "[\"$(get_svc 0)\"]" "" "Poppy - Future (deposit snapshotted)"; then
|
if create_admin_booking "$POPPY_ID" ""$D5"" "[\"$(get_svc 0)\"]" "" "Poppy - Future (deposit snapshotted)"; then
|
||||||
count_future=$((count_future+1))
|
count_future=$((count_future+1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -721,24 +817,118 @@ echo "${C_GREEN}✅ Confirmed $confirmed_count bookings, left $skipped_count pen
|
|||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# 5b. USER BOOKINGS WITH NOTES (triggers pending_booking notifications)
|
# 5b. USER BOOKINGS WITH NOTES (triggers pending_booking notifications)
|
||||||
# These use the public endpoint so notifications are created.
|
# These use the public endpoint so notifications are created.
|
||||||
|
# The create_booking function now retries across 5 time slots (A-E) to find
|
||||||
|
# one that satisfies: within hours, open day, non-overlapping, no blocker.
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
echo -e "\n${C_BLUE}📝 Creating User Bookings with Notes (notification triggers)..."
|
echo -e "\n${C_BLUE}📝 Creating User Bookings with Notes (notification triggers)..."
|
||||||
|
|
||||||
USER_NOTE_COUNT=0
|
USER_NOTE_COUNT=0
|
||||||
|
|
||||||
# Emma — booking with special request notes (triggers new_booking + pending_booking)
|
# --- NOTE BOOKINGS (existing, now with retry) ---
|
||||||
|
|
||||||
|
# Emma — booking with special request notes
|
||||||
D_NOTE1=$(open_day "$(TZ=Europe/London date -d "$TODAY +8 days" +%Y-%m-%d)")
|
D_NOTE1=$(open_day "$(TZ=Europe/London date -d "$TODAY +8 days" +%Y-%m-%d)")
|
||||||
if create_booking "$EMMA_TOKEN" "$(format_london_time "$D_NOTE1" "$SLOT_E")" "[\"$(get_svc 1)\"]" "Hi! Could I please have a nude base with white french tips and a single gold foil accent on the ring finger? Also I have a slight nail ridge on my left thumb - nothing major but worth noting." "Emma - French tips + gold foil (+8 days)"; then
|
if create_booking "$EMMA_TOKEN" ""$D_NOTE1"" "[\"$(get_svc 1)\"]" "Hi! Could I please have a nude base with white french tips and a single gold foil accent on the ring finger? Also I have a slight nail ridge on my left thumb - nothing major but worth noting." "Emma - French tips + gold foil (+8 days)"; then
|
||||||
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Isla — booking with notes for a different day (triggers new_booking + pending_booking)
|
# Isla — booking with notes for a different day (multi-service)
|
||||||
D_NOTE2=$(open_day "$(TZ=Europe/London date -d "$TODAY +10 days" +%Y-%m-%d)")
|
D_NOTE2=$(open_day "$(TZ=Europe/London date -d "$TODAY +10 days" +%Y-%m-%d)")
|
||||||
if create_booking "$ISLA_TOKEN" "$(format_london_time "$D_NOTE2" "$SLOT_E")" "[\"$(get_svc 1)\",\"$(get_svc 5)\"]" "I'd like a chrome/mirror-ball effect on all nails if possible. Also I'm thinking of getting married soon so want to trial a bridal look - can we discuss options?" "Isla - Chrome trial (+10 days)"; then
|
if create_booking "$ISLA_TOKEN" ""$D_NOTE2"" "[\"$(get_svc 1)\",\"$(get_svc 5)\"]" "I'd like a chrome/mirror-ball effect on all nails if possible. Also I'm thinking of getting married soon so want to trial a bridal look - can we discuss options?" "Isla - Chrome trial (+10 days)"; then
|
||||||
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "${C_GREEN}✅ Created $USER_NOTE_COUNT User Bookings with Notes (pending notifications)${C_RESET}"
|
# --- NEW: THIS WEEK REMAINING (user-facing, no notes but triggers normal flow) ---
|
||||||
|
echo -e "\n${C_YELLOW}📝 Adding more user bookings across this week...${C_RESET}"
|
||||||
|
|
||||||
|
# Sophie — Luxury Pedicure + Paraffin Wax (multi-service, +3 days)
|
||||||
|
D_W1=$(open_day "$(TZ=Europe/London date -d "$TODAY +3 days" +%Y-%m-%d)")
|
||||||
|
if create_booking "$SOPHIE_TOKEN" ""$D_W1"" "[\"$(get_svc 2)\",\"$(get_svc 10)\"]" "" "Sophie - Pedicure + Paraffin (+3 days)"; then
|
||||||
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Amelia — Classic Manicure (single, +4 days)
|
||||||
|
D_W2=$(open_day "$(TZ=Europe/London date -d "$TODAY +4 days" +%Y-%m-%d)")
|
||||||
|
if create_booking "$AMELIA_TOKEN" ""$D_W2"" "[\"$(get_svc 0)\"]" "" "Amelia - Classic Manicure (+4 days)"; then
|
||||||
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Lily — Gel BIAB + Nail Art (multi-service, +5 days — patch test passes: >24h notice)
|
||||||
|
D_W3=$(open_day "$(TZ=Europe/London date -d "$TODAY +5 days" +%Y-%m-%d)")
|
||||||
|
if create_booking "$LILY_TOKEN" ""$D_W3"" "[\"$(get_svc 1)\",\"$(get_svc 5)\"]" "" "Lily - Gel + Nail Art (+5 days)"; then
|
||||||
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ava — Luxury Pedicure (single, +6 days)
|
||||||
|
D_W4=$(open_day "$(TZ=Europe/London date -d "$TODAY +6 days" +%Y-%m-%d)")
|
||||||
|
if create_booking "$AVA_TOKEN" ""$D_W4"" "[\"$(get_svc 2)\"]" "" "Ava - Luxury Pedicure (+6 days)"; then
|
||||||
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- PAST-WEEK BOOKINGS (admin-created, bypasses past-time rejection) ---
|
||||||
|
echo -e "\n${C_YELLOW}📝 Adding past-week bookings (admin-created)...${C_RESET}"
|
||||||
|
count_past_extra=0
|
||||||
|
|
||||||
|
# Oliver — Classic Manicure + Nail Art (multi-service, 3 days ago)
|
||||||
|
D_P1=$(open_day_past "$(TZ=Europe/London date -d "today -3 days" +%Y-%m-%d)")
|
||||||
|
if create_admin_booking "$OLIVER_ID" ""$D_P1"" "[\"$(get_svc 0)\",\"$(get_svc 5)\"]" "" "Oliver - Classic + Nail Art (past)"; then count_past_extra=$((count_past_extra+1)); fi
|
||||||
|
|
||||||
|
# Harry — Luxury Pedicure + Paraffin Wax (multi-service, 4 days ago)
|
||||||
|
D_P2=$(open_day_past "$(TZ=Europe/London date -d "today -4 days" +%Y-%m-%d)")
|
||||||
|
if create_admin_booking "$HARRY_ID" ""$D_P2"" "[\"$(get_svc 2)\",\"$(get_svc 10)\"]" "" "Harry - Pedicure + Paraffin (past)"; then count_past_extra=$((count_past_extra+1)); fi
|
||||||
|
|
||||||
|
# Charlie — Gel Manicure BIAB (single, 2 days ago — will FAIL patch test 24h notice)
|
||||||
|
D_P3=$(open_day_past "$(TZ=Europe/London date -d "today -2 days" +%Y-%m-%d)")
|
||||||
|
if create_admin_booking "$CHARLIE_ID" ""$D_P3"" "[\"$(get_svc 1)\"]" "" "Charlie - Gel BIAB (past, no patch test)"; then count_past_extra=$((count_past_extra+1)); fi
|
||||||
|
|
||||||
|
# Noah — Express Mani & Pedi + Nail Art (multi-service, 1 day ago)
|
||||||
|
D_P4=$(open_day_past "$(TZ=Europe/London date -d "today -1 day" +%Y-%m-%d)")
|
||||||
|
if create_admin_booking "$NOAH_ID" ""$D_P4"" "[\"$(get_svc 3)\",\"$(get_svc 5)\"]" "" "Noah - Express + Nail Art (past)"; then count_past_extra=$((count_past_extra+1)); fi
|
||||||
|
|
||||||
|
# Thomas — Classic Manicure + Paraffin Wax (multi-service, 6 days ago)
|
||||||
|
D_P5=$(open_day_past "$(TZ=Europe/London date -d "today -6 days" +%Y-%m-%d)")
|
||||||
|
if create_admin_booking "$THOMAS_ID" ""$D_P5"" "[\"$(get_svc 0)\",\"$(get_svc 10)\"]" "" "Thomas - Classic + Paraffin (past)"; then count_past_extra=$((count_past_extra+1)); fi
|
||||||
|
|
||||||
|
echo "${C_GREEN}✅ Created $count_past_extra past-week admin bookings${C_RESET}"
|
||||||
|
count_past=$((count_past + count_past_extra))
|
||||||
|
|
||||||
|
# --- UPCOMING MONTH (user-facing, spread across next 14-30 days) ---
|
||||||
|
echo -e "\n${C_YELLOW}📝 Adding upcoming month user bookings...${C_RESET}"
|
||||||
|
|
||||||
|
# Emma — Gel BIAB + Nail Art + Paraffin (multi, +14 days — patch test passes)
|
||||||
|
D_U1=$(open_day "$(TZ=Europe/London date -d "$TODAY +14 days" +%Y-%m-%d)")
|
||||||
|
if create_booking "$EMMA_TOKEN" ""$D_U1"" "[\"$(get_svc 1)\",\"$(get_svc 5)\",\"$(get_svc 10)\"]" "" "Emma - Gel + Nail Art + Wax (+14 days)"; then
|
||||||
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Grace — Luxury Gel Manicure + Nail Art (multi, +18 days — patch test passes)
|
||||||
|
D_U2=$(open_day "$(TZ=Europe/London date -d "$TODAY +18 days" +%Y-%m-%d)")
|
||||||
|
if create_booking "$GRACE_TOKEN" ""$D_U2"" "[\"$(get_svc 7)\",\"$(get_svc 5)\"]" "" "Grace - Luxury Gel + Nail Art (+18 days)"; then
|
||||||
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Poppy — already has a confirmed admin-created booking (deposit demo user),
|
||||||
|
# so skip the user-facing call (deposit constraint: 1 active booking max).
|
||||||
|
|
||||||
|
# Sophie — Bridal Package (multi, +25 days)
|
||||||
|
D_U4=$(open_day "$(TZ=Europe/London date -d "$TODAY +25 days" +%Y-%m-%d)")
|
||||||
|
if create_booking "$SOPHIE_TOKEN" ""$D_U4"" "[\"$(get_svc 11)\"]" "" "Sophie - Bridal Package (+25 days)"; then
|
||||||
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# User — Express Mani & Pedi + Gel Polish Removal (multi, +28 days)
|
||||||
|
D_U5=$(open_day "$(TZ=Europe/London date -d "$TODAY +28 days" +%Y-%m-%d)")
|
||||||
|
if create_booking "$USER_TOKEN" ""$D_U5"" "[\"$(get_svc 3)\",\"$(get_svc 4)\"]" "" "User - Express + Removal (+28 days)"; then
|
||||||
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ava — Luxury Pedicure (single, +30 days)
|
||||||
|
D_U6=$(open_day "$(TZ=Europe/London date -d "$TODAY +30 days" +%Y-%m-%d)")
|
||||||
|
if create_booking "$AVA_TOKEN" ""$D_U6"" "[\"$(get_svc 2)\"]" "" "Ava - Luxury Pedicure (+30 days)"; then
|
||||||
|
USER_NOTE_COUNT=$((USER_NOTE_COUNT+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${C_GREEN}✅ Created $USER_NOTE_COUNT User Bookings (incl. past-week + upcoming month)${C_RESET}"
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# 6. GUEST BOOKINGS
|
# 6. GUEST BOOKINGS
|
||||||
@@ -763,43 +953,36 @@ GUEST5_ID=$(create_guest "Evil" "$USER_EMAIL" "+447000000024")
|
|||||||
count_guest=0
|
count_guest=0
|
||||||
|
|
||||||
guest_book() {
|
guest_book() {
|
||||||
local gid="$1" time="$2" svc_idx="$3"
|
local gid="$1" date="$2" svc_idx="$3"
|
||||||
[[ -z "$gid" ]] && return
|
[[ -z "$gid" ]] && return
|
||||||
local reserve_json="{\"start_time\":\"$time\",\"service_ids\":[\"$(get_svc $svc_idx)\"]}"
|
local services="[\"$(get_svc $svc_idx)\"]"
|
||||||
|
local duration
|
||||||
|
duration=$(calc_duration "$services")
|
||||||
|
local slot
|
||||||
|
slot=$(pick_avail_slot "$date" "$duration")
|
||||||
|
[[ -z "$slot" ]] && return
|
||||||
|
local time
|
||||||
|
time=$(format_london_time "$date" "$slot")
|
||||||
|
local reserve_json="{\"start_time\":\"$time\",\"service_ids\":$services}"
|
||||||
local res_resp=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -d "$reserve_json" "$BASE_URL/bookings/reserve")
|
local res_resp=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -d "$reserve_json" "$BASE_URL/bookings/reserve")
|
||||||
local res_code=$(echo "$res_resp" | tail -n1)
|
local res_code=$(echo "$res_resp" | tail -n1)
|
||||||
[[ ! "$res_code" =~ ^2 ]] && return
|
[[ ! "$res_code" =~ ^2 ]] && return
|
||||||
local book_json="{\"user_id\":\"$gid\",\"start_time\":\"$time\",\"service_ids\":[\"$(get_svc $svc_idx)\"]}"
|
local book_json="{\"user_id\":\"$gid\",\"start_time\":\"$time\",\"service_ids\":$services}"
|
||||||
local book_resp=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -d "$book_json" "$BASE_URL/bookings")
|
local book_resp=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -d "$book_json" "$BASE_URL/bookings")
|
||||||
local book_code=$(echo "$book_resp" | tail -n1)
|
local book_code=$(echo "$book_resp" | tail -n1)
|
||||||
[[ "$book_code" =~ ^2 ]] && count_guest=$((count_guest+1))
|
[[ "$book_code" =~ ^2 ]] && count_guest=$((count_guest+1))
|
||||||
}
|
}
|
||||||
|
|
||||||
[[ -n "$GUEST1_ID" ]] && guest_book "$GUEST1_ID" "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +16 days" +%Y-%m-%d)")" "$SLOT_B")" 0
|
G1_DATE=$(open_day "$(TZ=Europe/London date -d "$TODAY +16 days" +%Y-%m-%d)")
|
||||||
[[ -n "$GUEST2_ID" ]] && guest_book "$GUEST2_ID" "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +20 days" +%Y-%m-%d)")" "$SLOT_C")" 3
|
G2_DATE=$(open_day "$(TZ=Europe/London date -d "$TODAY +20 days" +%Y-%m-%d)")
|
||||||
[[ -n "$GUEST3_ID" ]] && guest_book "$GUEST3_ID" "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +22 days" +%Y-%m-%d)")" "$SLOT_D")" 1
|
G3_DATE=$(open_day "$(TZ=Europe/London date -d "$TODAY +22 days" +%Y-%m-%d)")
|
||||||
|
[[ -n "$GUEST1_ID" ]] && guest_book "$GUEST1_ID" "$G1_DATE" 0
|
||||||
|
[[ -n "$GUEST2_ID" ]] && guest_book "$GUEST2_ID" "$G2_DATE" 3
|
||||||
|
[[ -n "$GUEST3_ID" ]] && guest_book "$GUEST3_ID" "$G3_DATE" 1
|
||||||
|
|
||||||
echo "${C_GREEN}✅ Created $count_guest Guest Bookings${C_RESET}"
|
echo "${C_GREEN}✅ Created $count_guest Guest Bookings${C_RESET}"
|
||||||
|
|
||||||
# ===========================================================================
|
|
||||||
# 7. TIME BLOCKERS (admin-blocked time)
|
|
||||||
# ===========================================================================
|
|
||||||
echo -e "\n${C_BLUE}🚫 Creating Time Blockers...${C_RESET}"
|
|
||||||
count_blockers=0
|
|
||||||
|
|
||||||
tb() {
|
|
||||||
local time="$1" dur="$2" desc="$3"
|
|
||||||
local json="{\"start_time\":\"$time\",\"duration_minutes\":$dur,\"description\":\"$desc\"}"
|
|
||||||
local resp=$(curl -s -w "\n%{http_code}" -X POST -H 'Content-Type: application/json' -H "Authorization: Bearer $ADMIN_TOKEN" -d "$json" "$BASE_URL/admin/time-blockers")
|
|
||||||
local code=$(echo "$resp" | tail -n1)
|
|
||||||
[[ "$code" =~ ^2 ]] && count_blockers=$((count_blockers+1))
|
|
||||||
}
|
|
||||||
|
|
||||||
tb "$(format_london_time "$TOMORROW" "14:00:00")" 60 "Staff meeting"
|
|
||||||
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +7 days" +%Y-%m-%d)")" "$SLOT_A")" 120 "Holiday — closed morning"
|
|
||||||
tb "$(format_london_time "$TOMORROW" "09:00:00")" 120 "Late start — closed until 11am"
|
|
||||||
|
|
||||||
echo "${C_GREEN}✅ Created $count_blockers Time Blockers${C_RESET}"
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# CANCELLATIONS
|
# CANCELLATIONS
|
||||||
@@ -846,7 +1029,7 @@ db_cancel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
D_CANCEL=$(open_day "$(TZ=Europe/London date -d "$TODAY +5 days" +%Y-%m-%d)")
|
D_CANCEL=$(open_day "$(TZ=Europe/London date -d "$TODAY +5 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$AVA_ID" "$(format_london_time "$D_CANCEL" "$SLOT_B")" "[\"$(get_svc 0)\"]" "" "Ava - to be cancelled"; then
|
if create_admin_booking "$AVA_ID" ""$D_CANCEL"" "[\"$(get_svc 0)\"]" "" "Ava - to be cancelled"; then
|
||||||
if db_cancel "$LAST_BOOKING_ID" "$CLIENT_CANCEL_STATUS" "Something came up, really sorry!"; then
|
if db_cancel "$LAST_BOOKING_ID" "$CLIENT_CANCEL_STATUS" "Something came up, really sorry!"; then
|
||||||
cancel_count=$((cancel_count+1))
|
cancel_count=$((cancel_count+1))
|
||||||
else
|
else
|
||||||
@@ -855,7 +1038,7 @@ if create_admin_booking "$AVA_ID" "$(format_london_time "$D_CANCEL" "$SLOT_B")"
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
D_CANCEL2=$(open_day "$(TZ=Europe/London date -d "$TODAY +7 days" +%Y-%m-%d)")
|
D_CANCEL2=$(open_day "$(TZ=Europe/London date -d "$TODAY +7 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$USER_USER_ID" "$(format_london_time "$D_CANCEL2" "$SLOT_C")" "[\"$(get_svc 2)\"]" "" "User - to be cancelled"; then
|
if create_admin_booking "$USER_USER_ID" ""$D_CANCEL2"" "[\"$(get_svc 2)\"]" "" "User - to be cancelled"; then
|
||||||
if db_cancel "$LAST_BOOKING_ID" "$CLIENT_CANCEL_STATUS" "Plans changed, apologies for the late notice."; then
|
if db_cancel "$LAST_BOOKING_ID" "$CLIENT_CANCEL_STATUS" "Plans changed, apologies for the late notice."; then
|
||||||
cancel_count=$((cancel_count+1))
|
cancel_count=$((cancel_count+1))
|
||||||
else
|
else
|
||||||
@@ -864,7 +1047,7 @@ if create_admin_booking "$USER_USER_ID" "$(format_london_time "$D_CANCEL2" "$SLO
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
D_ADMIN_CANCEL=$(open_day "$(TZ=Europe/London date -d "$TODAY +9 days" +%Y-%m-%d)")
|
D_ADMIN_CANCEL=$(open_day "$(TZ=Europe/London date -d "$TODAY +9 days" +%Y-%m-%d)")
|
||||||
if create_admin_booking "$NOAH_ID" "$(format_london_time "$D_ADMIN_CANCEL" "$SLOT_B")" "[\"$(get_svc 1)\"]" "" "Noah - to be admin-cancelled"; then
|
if create_admin_booking "$NOAH_ID" ""$D_ADMIN_CANCEL"" "[\"$(get_svc 1)\"]" "" "Noah - to be admin-cancelled"; then
|
||||||
if db_cancel "$LAST_BOOKING_ID" "$ADMIN_CANCEL_STATUS" "Slot no longer available due to schedule change."; then
|
if db_cancel "$LAST_BOOKING_ID" "$ADMIN_CANCEL_STATUS" "Slot no longer available due to schedule change."; then
|
||||||
cancel_count=$((cancel_count+1))
|
cancel_count=$((cancel_count+1))
|
||||||
else
|
else
|
||||||
@@ -1084,6 +1267,44 @@ payment_count=$(docker exec postgres psql -U myuser -d mydb -tAc \
|
|||||||
"SELECT COUNT(DISTINCT booking_id) FROM payments" 2>/dev/null)
|
"SELECT COUNT(DISTINCT booking_id) FROM payments" 2>/dev/null)
|
||||||
echo "${C_GREEN}✅ Created payments for $payment_count completed bookings${C_RESET}"
|
echo "${C_GREEN}✅ Created payments for $payment_count completed bookings${C_RESET}"
|
||||||
|
|
||||||
|
# --- Seeding refunds for user@example.com ---
|
||||||
|
echo -e "\n${C_BLUE}🔄 Creating Refunds...${C_RESET}"
|
||||||
|
docker exec -i postgres psql -U myuser -d mydb << 'REFUND_SQL' > /dev/null 2>&1
|
||||||
|
INSERT INTO refunds (payment_id, booking_id, amount, status, reason, created_by, created_at)
|
||||||
|
SELECT p.id, p.booking_id, p.amount, 'completed', 'Client cancelled – over 72h notice, full refund',
|
||||||
|
u.id, p.created_at + INTERVAL '1 day'
|
||||||
|
FROM payments p
|
||||||
|
JOIN bookings b ON p.booking_id = b.id
|
||||||
|
JOIN users u ON b.user_id = u.id
|
||||||
|
WHERE u.email = 'user@example.com'
|
||||||
|
AND p.payment_method = 'in_person_card'
|
||||||
|
AND p.status = 'completed'
|
||||||
|
AND p.payment_type = 'balance'
|
||||||
|
ORDER BY p.created_at ASC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
INSERT INTO refunds (payment_id, booking_id, amount, status, reason, created_by, created_at)
|
||||||
|
SELECT p.id, p.booking_id, ROUND(p.amount * 0.5, 2), 'completed',
|
||||||
|
'Client cancelled – 24-72h notice, protected deposit retained',
|
||||||
|
u.id, p.created_at + INTERVAL '7 days'
|
||||||
|
FROM payments p
|
||||||
|
JOIN bookings b ON p.booking_id = b.id
|
||||||
|
JOIN users u ON b.user_id = u.id
|
||||||
|
WHERE u.email = 'user@example.com'
|
||||||
|
AND p.payment_method = 'in_person_card'
|
||||||
|
AND p.status = 'completed'
|
||||||
|
AND p.payment_type = 'deposit'
|
||||||
|
ORDER BY p.created_at ASC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- Clear the LAST_BOOKING_ID that may have been set by the refund operations
|
||||||
|
REFUND_ID=""
|
||||||
|
REFUND_SQL
|
||||||
|
|
||||||
|
refund_count=$(docker exec postgres psql -U myuser -d mydb -tAc \
|
||||||
|
"SELECT COUNT(*) FROM refunds r JOIN payments p ON r.payment_id = p.id JOIN bookings b ON p.booking_id = b.id WHERE b.user_id = (SELECT id FROM users WHERE email = 'user@example.com')" 2>/dev/null)
|
||||||
|
echo "${C_GREEN}✅ Created $refund_count refunds for user@example.com${C_RESET}"
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# 5. EXCEPTIONAL SCHEDULING GROUPS
|
# 5. EXCEPTIONAL SCHEDULING GROUPS
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
@@ -1205,24 +1426,11 @@ fi
|
|||||||
|
|
||||||
echo "${C_GREEN}✅ Created $edit_req_count Edit Requests${C_RESET}"
|
echo "${C_GREEN}✅ Created $edit_req_count Edit Requests${C_RESET}"
|
||||||
|
|
||||||
# ===========================================================================
|
|
||||||
# 7c. MORE TIME BLOCKERS (for variety)
|
|
||||||
# ===========================================================================
|
|
||||||
echo -e "\n${C_BLUE}🚫 Creating Additional Time Blockers...${C_RESET}"
|
|
||||||
extra_blockers=0
|
|
||||||
|
|
||||||
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +3 days" +%Y-%m-%d)")" "$SLOT_B")" 90 "Equipment maintenance"
|
|
||||||
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +10 days" +%Y-%m-%d)")" "$SLOT_C")" 60 "Training session"
|
|
||||||
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +14 days" +%Y-%m-%d)")" "09:00:00")" 60 "Opening delay"
|
|
||||||
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +5 days" +%Y-%m-%d)")" "$SLOT_D")" 45 "Supplier visit"
|
|
||||||
tb "$(format_london_time "$(open_day "$(TZ=Europe/London date -d "$TODAY +8 days" +%Y-%m-%d)")" "$SLOT_A")" 120 "Deep clean — morning closed"
|
|
||||||
|
|
||||||
echo "${C_GREEN}✅ Created $extra_blockers Additional Time Blockers${C_RESET}"
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# SUMMARY
|
# SUMMARY
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
TOTAL_BOOKINGS=$((count_past + count_today + count_tomorrow + count_future))
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${C_GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}"
|
echo -e "${C_GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C_RESET}"
|
||||||
echo -e "${C_GREEN}🎉 Seeding Complete!${C_RESET}"
|
echo -e "${C_GREEN}🎉 Seeding Complete!${C_RESET}"
|
||||||
@@ -1238,13 +1446,13 @@ echo -e " Bookings — past : $count_past"
|
|||||||
echo -e " Bookings — today : $count_today"
|
echo -e " Bookings — today : $count_today"
|
||||||
echo -e " Bookings — tomorrow: $count_tomorrow"
|
echo -e " Bookings — tomorrow: $count_tomorrow"
|
||||||
echo -e " Bookings — future : $count_future"
|
echo -e " Bookings — future : $count_future"
|
||||||
echo -e " Bookings — total : $TOTAL_BOOKINGS"
|
echo -e " Bookings — user : $USER_NOTE_COUNT (public endpoint)"
|
||||||
|
echo -e " Bookings — guest : $count_guest"
|
||||||
|
echo -e " Bookings — total : $((count_past + count_today + count_tomorrow + count_future + count_guest))"
|
||||||
echo -e " Cancellations : $cancel_count"
|
echo -e " Cancellations : $cancel_count"
|
||||||
echo -e " Confirmed : $confirmed_count | Still pending: $skipped_count"
|
echo -e " Confirmed : $confirmed_count | Still pending: $skipped_count"
|
||||||
echo -e " Bookings — guest : $count_guest"
|
|
||||||
echo -e " Bookings — w/ notes: $USER_NOTE_COUNT (pending notifications)"
|
|
||||||
echo -e " Payments : $payment_count completed bookings"
|
echo -e " Payments : $payment_count completed bookings"
|
||||||
echo -e " Time blockers : $((count_blockers + extra_blockers))"
|
echo -e " Time blockers : $count_blockers"
|
||||||
echo -e " Edit requests : $edit_req_count"
|
echo -e " Edit requests : $edit_req_count"
|
||||||
echo -e " Schedule groups : $sched_success/3"
|
echo -e " Schedule groups : $sched_success/3"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -1262,7 +1470,12 @@ echo -e "${C_GREEN}⏳ Running tests...${C_RESET}"
|
|||||||
cd /home/popertots/Crussell/backend
|
cd /home/popertots/Crussell/backend
|
||||||
export POSTGRES_USER POSTGRES_PASSWORD POSTGRES_HOST POSTGRES_DB GO_TESTING=1
|
export POSTGRES_USER POSTGRES_PASSWORD POSTGRES_HOST POSTGRES_DB GO_TESTING=1
|
||||||
TEST_OUTPUT_FILE=$(mktemp)
|
TEST_OUTPUT_FILE=$(mktemp)
|
||||||
|
START_TIME=$(date +%s)
|
||||||
go test -tags "test,dev" -v -p 1 -count=1 ./... 2>&1 | tee "$TEST_OUTPUT_FILE" || true
|
go test -tags "test,dev" -v -p 1 -count=1 ./... 2>&1 | tee "$TEST_OUTPUT_FILE" || true
|
||||||
|
END_TIME=$(date +%s)
|
||||||
|
DURATION=$((END_TIME - START_TIME))
|
||||||
|
MINUTES=$((DURATION / 60))
|
||||||
|
SECONDS=$((DURATION % 60))
|
||||||
TEST_OUTPUT=$(cat "$TEST_OUTPUT_FILE")
|
TEST_OUTPUT=$(cat "$TEST_OUTPUT_FILE")
|
||||||
rm -f "$TEST_OUTPUT_FILE"
|
rm -f "$TEST_OUTPUT_FILE"
|
||||||
cd ..
|
cd ..
|
||||||
@@ -1277,6 +1490,7 @@ FAILED_TESTS=${FAILED_TESTS:-0}
|
|||||||
SKIPPED_TESTS=${SKIPPED_TESTS:-0}
|
SKIPPED_TESTS=${SKIPPED_TESTS:-0}
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
echo -e "${C_YELLOW}⏱️ Duration: ${MINUTES}m ${SECONDS}s${C_RESET}"
|
||||||
if [ "$SKIPPED_TESTS" -gt 0 ] 2>/dev/null; then
|
if [ "$SKIPPED_TESTS" -gt 0 ] 2>/dev/null; then
|
||||||
echo -e "${C_YELLOW}⚠️ Tests Skipped: $SKIPPED_TESTS${C_RESET}"
|
echo -e "${C_YELLOW}⚠️ Tests Skipped: $SKIPPED_TESTS${C_RESET}"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user