@tummycrypt/acuity-middleware 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/acuity-scraper.d.ts +8 -0
- package/dist/adapters/acuity-scraper.d.ts.map +1 -0
- package/dist/adapters/acuity-scraper.js +8 -0
- package/dist/adapters/acuity-scraper.js.map +1 -0
- package/dist/adapters/types.d.ts +8 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +8 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/acuity-wizard.d.ts +49 -0
- package/dist/middleware/acuity-wizard.d.ts.map +1 -0
- package/dist/middleware/acuity-wizard.js +265 -0
- package/dist/middleware/acuity-wizard.js.map +1 -0
- package/dist/middleware/browser-service.d.ts +53 -0
- package/dist/middleware/browser-service.d.ts.map +1 -0
- package/dist/middleware/browser-service.js +105 -0
- package/dist/middleware/browser-service.js.map +1 -0
- package/dist/middleware/errors.d.ts +58 -0
- package/dist/middleware/errors.d.ts.map +1 -0
- package/dist/middleware/errors.js +43 -0
- package/dist/middleware/errors.js.map +1 -0
- package/{src/middleware/index.ts → dist/middleware/index.d.ts} +5 -52
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +38 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/logger.d.ts +26 -0
- package/dist/middleware/logger.d.ts.map +1 -0
- package/dist/middleware/logger.js +65 -0
- package/dist/middleware/logger.js.map +1 -0
- package/dist/middleware/remote-adapter.d.ts +45 -0
- package/dist/middleware/remote-adapter.d.ts.map +1 -0
- package/dist/middleware/remote-adapter.js +178 -0
- package/dist/middleware/remote-adapter.js.map +1 -0
- package/dist/middleware/selector-health.d.ts +44 -0
- package/dist/middleware/selector-health.d.ts.map +1 -0
- package/dist/middleware/selector-health.js +144 -0
- package/dist/middleware/selector-health.js.map +1 -0
- package/dist/middleware/selectors.d.ts +108 -0
- package/dist/middleware/selectors.d.ts.map +1 -0
- package/dist/middleware/selectors.js +249 -0
- package/dist/middleware/selectors.js.map +1 -0
- package/dist/middleware/server.d.ts +34 -0
- package/dist/middleware/server.d.ts.map +1 -0
- package/dist/middleware/server.js +377 -0
- package/dist/middleware/server.js.map +1 -0
- package/dist/middleware/service-resolver.d.ts +46 -0
- package/dist/middleware/service-resolver.d.ts.map +1 -0
- package/dist/middleware/service-resolver.js +274 -0
- package/dist/middleware/service-resolver.js.map +1 -0
- package/dist/middleware/slot-parser.d.ts +29 -0
- package/dist/middleware/slot-parser.d.ts.map +1 -0
- package/dist/middleware/slot-parser.js +50 -0
- package/dist/middleware/slot-parser.js.map +1 -0
- package/dist/middleware/steps/__tests__/fixtures.d.ts +14 -0
- package/dist/middleware/steps/__tests__/fixtures.d.ts.map +1 -0
- package/dist/middleware/steps/__tests__/fixtures.js +204 -0
- package/dist/middleware/steps/__tests__/fixtures.js.map +1 -0
- package/dist/middleware/steps/bypass-payment.d.ts +54 -0
- package/dist/middleware/steps/bypass-payment.d.ts.map +1 -0
- package/dist/middleware/steps/bypass-payment.js +164 -0
- package/dist/middleware/steps/bypass-payment.js.map +1 -0
- package/dist/middleware/steps/extract-business.d.ts +93 -0
- package/dist/middleware/steps/extract-business.d.ts.map +1 -0
- package/dist/middleware/steps/extract-business.js +170 -0
- package/dist/middleware/steps/extract-business.js.map +1 -0
- package/dist/middleware/steps/extract.d.ts +41 -0
- package/dist/middleware/steps/extract.d.ts.map +1 -0
- package/dist/middleware/steps/extract.js +128 -0
- package/dist/middleware/steps/extract.js.map +1 -0
- package/dist/middleware/steps/fill-form.d.ts +45 -0
- package/dist/middleware/steps/fill-form.d.ts.map +1 -0
- package/dist/middleware/steps/fill-form.js +262 -0
- package/dist/middleware/steps/fill-form.js.map +1 -0
- package/dist/middleware/steps/index.d.ts +12 -0
- package/dist/middleware/steps/index.d.ts.map +1 -0
- package/dist/middleware/steps/index.js +12 -0
- package/dist/middleware/steps/index.js.map +1 -0
- package/dist/middleware/steps/navigate.d.ts +51 -0
- package/dist/middleware/steps/navigate.d.ts.map +1 -0
- package/dist/middleware/steps/navigate.js +391 -0
- package/dist/middleware/steps/navigate.js.map +1 -0
- package/dist/middleware/steps/read-availability.d.ts +37 -0
- package/dist/middleware/steps/read-availability.d.ts.map +1 -0
- package/dist/middleware/steps/read-availability.js +298 -0
- package/dist/middleware/steps/read-availability.js.map +1 -0
- package/dist/middleware/steps/read-slots.d.ts +33 -0
- package/dist/middleware/steps/read-slots.d.ts.map +1 -0
- package/dist/middleware/steps/read-slots.js +295 -0
- package/dist/middleware/steps/read-slots.js.map +1 -0
- package/dist/middleware/steps/read-via-url.d.ts +39 -0
- package/dist/middleware/steps/read-via-url.d.ts.map +1 -0
- package/dist/middleware/steps/read-via-url.js +141 -0
- package/dist/middleware/steps/read-via-url.js.map +1 -0
- package/dist/middleware/steps/submit.d.ts +22 -0
- package/dist/middleware/steps/submit.d.ts.map +1 -0
- package/dist/middleware/steps/submit.js +112 -0
- package/dist/middleware/steps/submit.js.map +1 -0
- package/dist/middleware/wizard-calendar.d.ts +37 -0
- package/dist/middleware/wizard-calendar.d.ts.map +1 -0
- package/dist/middleware/wizard-calendar.js +177 -0
- package/dist/middleware/wizard-calendar.js.map +1 -0
- package/dist/middleware/wizard-service.d.ts +30 -0
- package/dist/middleware/wizard-service.d.ts.map +1 -0
- package/dist/middleware/wizard-service.js +89 -0
- package/dist/middleware/wizard-service.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/{src/server.ts → dist/server.js} +1 -0
- package/dist/server.js.map +1 -0
- package/package.json +16 -4
- package/.github/workflows/build-paper.yml +0 -39
- package/.github/workflows/ci.yml +0 -37
- package/Dockerfile +0 -53
- package/docs/blog-post.mdx +0 -240
- package/docs/paper/IEEEtran.bst +0 -2409
- package/docs/paper/IEEEtran.cls +0 -6347
- package/docs/paper/acuity-middleware-paper.tex +0 -375
- package/docs/paper/balance.sty +0 -87
- package/docs/paper/references.bib +0 -231
- package/docs/paper.md +0 -400
- package/flake.nix +0 -32
- package/modal-app.py +0 -82
- package/src/adapters/acuity-scraper.ts +0 -543
- package/src/adapters/types.ts +0 -193
- package/src/core/types.ts +0 -325
- package/src/index.ts +0 -75
- package/src/middleware/acuity-wizard.ts +0 -456
- package/src/middleware/browser-service.ts +0 -183
- package/src/middleware/errors.ts +0 -70
- package/src/middleware/remote-adapter.ts +0 -246
- package/src/middleware/selectors.ts +0 -308
- package/src/middleware/server.ts +0 -372
- package/src/middleware/steps/bypass-payment.ts +0 -226
- package/src/middleware/steps/extract.ts +0 -174
- package/src/middleware/steps/fill-form.ts +0 -359
- package/src/middleware/steps/index.ts +0 -27
- package/src/middleware/steps/navigate.ts +0 -537
- package/src/middleware/steps/read-availability.ts +0 -399
- package/src/middleware/steps/read-slots.ts +0 -405
- package/src/middleware/steps/submit.ts +0 -168
- package/tsconfig.json +0 -25
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware HTTP Server
|
|
3
|
+
*
|
|
4
|
+
* Standalone Node.js HTTP server wrapping the Effect TS wizard programs.
|
|
5
|
+
* Designed to run inside a Docker container with Playwright + Chromium
|
|
6
|
+
* on Modal Labs, Fly.io, or any host.
|
|
7
|
+
*
|
|
8
|
+
* Endpoints:
|
|
9
|
+
* GET /health - Health check
|
|
10
|
+
* GET /services - List services (scraper)
|
|
11
|
+
* GET /services/:id - Get service by ID
|
|
12
|
+
* POST /availability/dates - Available dates for a service
|
|
13
|
+
* POST /availability/slots - Time slots for a date
|
|
14
|
+
* POST /availability/check - Check if a slot is available
|
|
15
|
+
* POST /booking/create - Create booking (standard)
|
|
16
|
+
* POST /booking/create-with-payment - Create booking with payment ref (coupon bypass)
|
|
17
|
+
*
|
|
18
|
+
* Environment variables:
|
|
19
|
+
* PORT - Server port (default: 3001)
|
|
20
|
+
* ACUITY_BASE_URL - Acuity scheduling URL
|
|
21
|
+
* ACUITY_BYPASS_COUPON - 100% coupon code
|
|
22
|
+
* AUTH_TOKEN - Required Bearer token for all endpoints
|
|
23
|
+
* PLAYWRIGHT_HEADLESS - Browser headless mode (default: true)
|
|
24
|
+
* PLAYWRIGHT_TIMEOUT - Page timeout in ms (default: 30000)
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* node --import tsx/esm src/middleware/server.ts
|
|
28
|
+
* # or after build:
|
|
29
|
+
* node dist/middleware/server.js
|
|
30
|
+
*/
|
|
31
|
+
import { createServer } from 'node:http';
|
|
32
|
+
import { Effect, Exit, Cause } from 'effect';
|
|
33
|
+
import { createScraperAdapter } from '../adapters/acuity-scraper.js';
|
|
34
|
+
import { BrowserServiceLive, defaultBrowserConfig } from './browser-service.js';
|
|
35
|
+
import { toSchedulingError } from './errors.js';
|
|
36
|
+
import { navigateToBooking, fillFormFields, bypassPayment, submitBooking, extractConfirmation, toBooking, readAvailableDates, readTimeSlots, fetchBusinessData, businessToServices, } from './steps/index.js';
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// CONFIGURATION
|
|
39
|
+
// =============================================================================
|
|
40
|
+
const PORT = Number(process.env.PORT ?? 3001);
|
|
41
|
+
const AUTH_TOKEN = process.env.AUTH_TOKEN;
|
|
42
|
+
const ACUITY_BASE_URL = process.env.ACUITY_BASE_URL ?? 'https://MassageIthaca.as.me';
|
|
43
|
+
const COUPON_CODE = process.env.ACUITY_BYPASS_COUPON;
|
|
44
|
+
const browserConfig = {
|
|
45
|
+
...defaultBrowserConfig,
|
|
46
|
+
baseUrl: ACUITY_BASE_URL,
|
|
47
|
+
headless: process.env.PLAYWRIGHT_HEADLESS !== 'false',
|
|
48
|
+
timeout: Number(process.env.PLAYWRIGHT_TIMEOUT ?? 30000),
|
|
49
|
+
executablePath: process.env.CHROMIUM_EXECUTABLE_PATH,
|
|
50
|
+
launchArgs: process.env.CHROMIUM_LAUNCH_ARGS?.split(','),
|
|
51
|
+
};
|
|
52
|
+
const scraperConfig = {
|
|
53
|
+
baseUrl: ACUITY_BASE_URL,
|
|
54
|
+
headless: browserConfig.headless,
|
|
55
|
+
timeout: browserConfig.timeout,
|
|
56
|
+
userAgent: browserConfig.userAgent,
|
|
57
|
+
executablePath: browserConfig.executablePath,
|
|
58
|
+
launchArgs: browserConfig.launchArgs ? [...browserConfig.launchArgs] : undefined,
|
|
59
|
+
};
|
|
60
|
+
const sendJson = (res, status, body) => {
|
|
61
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
62
|
+
res.end(JSON.stringify(body));
|
|
63
|
+
};
|
|
64
|
+
const sendSuccess = (res, data) => sendJson(res, 200, { success: true, data });
|
|
65
|
+
const sendError = (res, status, err) => sendJson(res, status, {
|
|
66
|
+
success: false,
|
|
67
|
+
error: {
|
|
68
|
+
tag: err._tag,
|
|
69
|
+
code: 'code' in err ? err.code : err._tag,
|
|
70
|
+
message: 'message' in err ? err.message : 'Unknown error',
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
const parseBody = async (req) => {
|
|
74
|
+
const chunks = [];
|
|
75
|
+
for await (const chunk of req) {
|
|
76
|
+
chunks.push(chunk);
|
|
77
|
+
}
|
|
78
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
79
|
+
return raw ? JSON.parse(raw) : {};
|
|
80
|
+
};
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// EFFECT RUNNER
|
|
83
|
+
// =============================================================================
|
|
84
|
+
const layer = BrowserServiceLive(browserConfig);
|
|
85
|
+
const runEffect = async (effect) => {
|
|
86
|
+
const exit = await Effect.runPromiseExit(Effect.scoped(effect.pipe(Effect.provide(layer))));
|
|
87
|
+
if (Exit.isSuccess(exit)) {
|
|
88
|
+
return { ok: true, value: exit.value };
|
|
89
|
+
}
|
|
90
|
+
const failure = Cause.failureOption(exit.cause);
|
|
91
|
+
if (failure._tag === 'Some') {
|
|
92
|
+
return { ok: false, error: toSchedulingError(failure.value) };
|
|
93
|
+
}
|
|
94
|
+
return { ok: false, error: { _tag: 'InfrastructureError', code: 'UNKNOWN', message: Cause.pretty(exit.cause) } };
|
|
95
|
+
};
|
|
96
|
+
/** Run a SchedulingResult (Effect) and return Result */
|
|
97
|
+
const runSchedulingEffect = async (effect) => {
|
|
98
|
+
const exit = await Effect.runPromiseExit(effect);
|
|
99
|
+
if (Exit.isSuccess(exit)) {
|
|
100
|
+
return { ok: true, value: exit.value };
|
|
101
|
+
}
|
|
102
|
+
const failure = Cause.failureOption(exit.cause);
|
|
103
|
+
if (failure._tag === 'Some') {
|
|
104
|
+
return { ok: false, error: failure.value };
|
|
105
|
+
}
|
|
106
|
+
return { ok: false, error: { _tag: 'InfrastructureError', code: 'UNKNOWN', message: Cause.pretty(exit.cause) } };
|
|
107
|
+
};
|
|
108
|
+
// =============================================================================
|
|
109
|
+
// SCRAPER (cached)
|
|
110
|
+
// =============================================================================
|
|
111
|
+
let scraper = null;
|
|
112
|
+
const getScraper = () => {
|
|
113
|
+
if (!scraper) {
|
|
114
|
+
scraper = createScraperAdapter(scraperConfig);
|
|
115
|
+
}
|
|
116
|
+
return scraper;
|
|
117
|
+
};
|
|
118
|
+
let cachedServices = null;
|
|
119
|
+
// Static services from SERVICES_JSON env var (avoids scraper dependency)
|
|
120
|
+
const STATIC_SERVICES = (() => {
|
|
121
|
+
const raw = process.env.SERVICES_JSON;
|
|
122
|
+
if (!raw)
|
|
123
|
+
return null;
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(raw);
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
console.error('[middleware-server] Failed to parse SERVICES_JSON:', e);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
if (STATIC_SERVICES) {
|
|
133
|
+
cachedServices = STATIC_SERVICES;
|
|
134
|
+
console.log(`[middleware-server] Loaded ${STATIC_SERVICES.length} static services from SERVICES_JSON`);
|
|
135
|
+
}
|
|
136
|
+
// =============================================================================
|
|
137
|
+
// ROUTE HANDLERS
|
|
138
|
+
// =============================================================================
|
|
139
|
+
const handleHealth = (_req, res) => {
|
|
140
|
+
sendSuccess(res, {
|
|
141
|
+
status: 'ok',
|
|
142
|
+
baseUrl: ACUITY_BASE_URL,
|
|
143
|
+
hasCoupon: !!COUPON_CODE,
|
|
144
|
+
headless: browserConfig.headless,
|
|
145
|
+
staticServices: STATIC_SERVICES ? STATIC_SERVICES.length : 0,
|
|
146
|
+
timestamp: new Date().toISOString(),
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
const handleGetServices = async (_req, res) => {
|
|
150
|
+
// 1. Prefer static services from env (always reliable, no network needed)
|
|
151
|
+
if (STATIC_SERVICES) {
|
|
152
|
+
console.log('[services] source: SERVICES_JSON env');
|
|
153
|
+
cachedServices = STATIC_SERVICES;
|
|
154
|
+
return sendSuccess(res, STATIC_SERVICES);
|
|
155
|
+
}
|
|
156
|
+
// 2. Extract from BUSINESS object (HTTP fetch + regex, no browser)
|
|
157
|
+
try {
|
|
158
|
+
const business = await fetchBusinessData(ACUITY_BASE_URL);
|
|
159
|
+
if (business) {
|
|
160
|
+
const services = businessToServices(business);
|
|
161
|
+
if (services.length > 0) {
|
|
162
|
+
console.log(`[services] source: BUSINESS object (${services.length} services)`);
|
|
163
|
+
cachedServices = services;
|
|
164
|
+
return sendSuccess(res, services);
|
|
165
|
+
}
|
|
166
|
+
console.warn('[services] BUSINESS object found but 0 active services');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
console.warn('[services] BUSINESS extraction failed:', e instanceof Error ? e.message : e);
|
|
171
|
+
}
|
|
172
|
+
// 3. Deprecated fallback: DOM scraper (may return [] — Acuity is React SPA)
|
|
173
|
+
console.warn('[services] falling back to DOM scraper (deprecated)');
|
|
174
|
+
const result = await runSchedulingEffect(getScraper().getServices());
|
|
175
|
+
if (!result.ok)
|
|
176
|
+
return sendError(res, 500, result.error);
|
|
177
|
+
if (result.value.length === 0) {
|
|
178
|
+
console.error('[services] all sources exhausted — returning empty []');
|
|
179
|
+
}
|
|
180
|
+
cachedServices = result.value;
|
|
181
|
+
sendSuccess(res, result.value);
|
|
182
|
+
};
|
|
183
|
+
const handleGetService = async (serviceId, res) => {
|
|
184
|
+
if (!cachedServices) {
|
|
185
|
+
const result = await runSchedulingEffect(getScraper().getServices());
|
|
186
|
+
if (!result.ok)
|
|
187
|
+
return sendError(res, 500, result.error);
|
|
188
|
+
cachedServices = result.value;
|
|
189
|
+
}
|
|
190
|
+
const found = cachedServices.find((s) => s.id === serviceId);
|
|
191
|
+
if (!found) {
|
|
192
|
+
return sendJson(res, 404, {
|
|
193
|
+
success: false,
|
|
194
|
+
error: { tag: 'AcuityError', code: 'NOT_FOUND', message: `Service ${serviceId} not found` },
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
sendSuccess(res, found);
|
|
198
|
+
};
|
|
199
|
+
const handleAvailableDates = async (req, res) => {
|
|
200
|
+
const body = (await parseBody(req));
|
|
201
|
+
// Use service name for wizard navigation (click-based, not URL param)
|
|
202
|
+
const serviceName = body.serviceName ?? cachedServices?.find((s) => s.id === body.serviceId)?.name ?? body.serviceId;
|
|
203
|
+
console.log(`[availability/dates] serviceName="${serviceName}" from serviceId="${body.serviceId}"`);
|
|
204
|
+
const result = await runEffect(readAvailableDates({
|
|
205
|
+
serviceName,
|
|
206
|
+
targetMonth: body.startDate?.slice(0, 7),
|
|
207
|
+
monthsToScan: 2,
|
|
208
|
+
}));
|
|
209
|
+
if (!result.ok) {
|
|
210
|
+
const err = result.error;
|
|
211
|
+
console.error(`[availability/dates] error:`, err);
|
|
212
|
+
return sendJson(res, 500, {
|
|
213
|
+
success: false,
|
|
214
|
+
error: { tag: err._tag ?? 'InfrastructureError', code: 'code' in err ? err.code : 'UNKNOWN', message: 'message' in err ? err.message : 'Availability lookup failed' },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
sendSuccess(res, result.value);
|
|
218
|
+
};
|
|
219
|
+
const handleAvailableSlots = async (req, res) => {
|
|
220
|
+
const body = (await parseBody(req));
|
|
221
|
+
const serviceName = body.serviceName ?? cachedServices?.find((s) => s.id === body.serviceId)?.name ?? body.serviceId;
|
|
222
|
+
console.log(`[availability/slots] serviceName="${serviceName}" date="${body.date}"`);
|
|
223
|
+
const result = await runEffect(readTimeSlots({
|
|
224
|
+
serviceName,
|
|
225
|
+
date: body.date,
|
|
226
|
+
}));
|
|
227
|
+
if (!result.ok) {
|
|
228
|
+
const err = result.error;
|
|
229
|
+
console.error(`[availability/slots] error:`, err);
|
|
230
|
+
return sendJson(res, 500, {
|
|
231
|
+
success: false,
|
|
232
|
+
error: { tag: err._tag ?? 'InfrastructureError', code: 'code' in err ? err.code : 'UNKNOWN', message: 'message' in err ? err.message : 'Slot lookup failed' },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
sendSuccess(res, result.value);
|
|
236
|
+
};
|
|
237
|
+
const handleCheckSlot = async (req, res) => {
|
|
238
|
+
const body = (await parseBody(req));
|
|
239
|
+
const date = body.datetime.split('T')[0];
|
|
240
|
+
const serviceName = body.serviceName ?? cachedServices?.find((s) => s.id === body.serviceId)?.name ?? body.serviceId;
|
|
241
|
+
const result = await runEffect(readTimeSlots({
|
|
242
|
+
serviceName,
|
|
243
|
+
date,
|
|
244
|
+
}));
|
|
245
|
+
if (!result.ok) {
|
|
246
|
+
const err = result.error;
|
|
247
|
+
return sendJson(res, 500, {
|
|
248
|
+
success: false,
|
|
249
|
+
error: { tag: err._tag ?? 'InfrastructureError', code: 'code' in err ? err.code : 'UNKNOWN', message: 'message' in err ? err.message : 'Slot check failed' },
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const available = result.value.some((s) => s.datetime === body.datetime && s.available);
|
|
253
|
+
sendSuccess(res, available);
|
|
254
|
+
};
|
|
255
|
+
const handleCreateBooking = async (req, res) => {
|
|
256
|
+
const body = (await parseBody(req));
|
|
257
|
+
const { request } = body;
|
|
258
|
+
const serviceName = cachedServices?.find((s) => s.id === request.serviceId)?.name;
|
|
259
|
+
const result = await runEffect(Effect.gen(function* () {
|
|
260
|
+
yield* navigateToBooking({
|
|
261
|
+
serviceName: serviceName ?? request.serviceId,
|
|
262
|
+
datetime: request.datetime,
|
|
263
|
+
client: request.client,
|
|
264
|
+
appointmentTypeId: request.serviceId,
|
|
265
|
+
});
|
|
266
|
+
yield* fillFormFields({ client: request.client, customFields: request.client.customFields });
|
|
267
|
+
yield* submitBooking();
|
|
268
|
+
const confirmation = yield* extractConfirmation();
|
|
269
|
+
return toBooking(confirmation, request, '', 'acuity');
|
|
270
|
+
}));
|
|
271
|
+
if (!result.ok)
|
|
272
|
+
return sendError(res, 500, result.error);
|
|
273
|
+
sendSuccess(res, result.value);
|
|
274
|
+
};
|
|
275
|
+
const handleCreateBookingWithPayment = async (req, res) => {
|
|
276
|
+
const body = (await parseBody(req));
|
|
277
|
+
const { request, paymentRef, paymentProcessor } = body;
|
|
278
|
+
const coupon = body.couponCode ?? COUPON_CODE;
|
|
279
|
+
if (!coupon) {
|
|
280
|
+
return sendJson(res, 400, {
|
|
281
|
+
success: false,
|
|
282
|
+
error: { tag: 'ValidationError', code: 'couponCode', message: 'Coupon code is required for payment bypass' },
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
// Try to get service details for richer booking data
|
|
286
|
+
const service = cachedServices?.find((s) => s.id === request.serviceId);
|
|
287
|
+
const serviceName = service?.name ?? request.serviceId;
|
|
288
|
+
const result = await runEffect(Effect.gen(function* () {
|
|
289
|
+
yield* navigateToBooking({
|
|
290
|
+
serviceName,
|
|
291
|
+
datetime: request.datetime,
|
|
292
|
+
client: request.client,
|
|
293
|
+
appointmentTypeId: request.serviceId,
|
|
294
|
+
});
|
|
295
|
+
yield* fillFormFields({ client: request.client, customFields: request.client.customFields });
|
|
296
|
+
yield* bypassPayment(coupon);
|
|
297
|
+
yield* submitBooking();
|
|
298
|
+
const confirmation = yield* extractConfirmation();
|
|
299
|
+
return toBooking(confirmation, request, paymentRef, paymentProcessor, service ? { name: service.name, duration: service.duration, price: service.price, currency: service.currency } : undefined);
|
|
300
|
+
}));
|
|
301
|
+
if (!result.ok)
|
|
302
|
+
return sendError(res, 500, result.error);
|
|
303
|
+
sendSuccess(res, result.value);
|
|
304
|
+
};
|
|
305
|
+
// =============================================================================
|
|
306
|
+
// SERVER
|
|
307
|
+
// =============================================================================
|
|
308
|
+
const server = createServer(async (req, res) => {
|
|
309
|
+
const url = new URL(req.url ?? '/', `http://localhost:${PORT}`);
|
|
310
|
+
const path = url.pathname;
|
|
311
|
+
const method = req.method?.toUpperCase() ?? 'GET';
|
|
312
|
+
// Auth check (skip health endpoint)
|
|
313
|
+
if (AUTH_TOKEN && path !== '/health') {
|
|
314
|
+
const auth = req.headers.authorization;
|
|
315
|
+
if (auth !== `Bearer ${AUTH_TOKEN}`) {
|
|
316
|
+
return sendJson(res, 401, {
|
|
317
|
+
success: false,
|
|
318
|
+
error: { tag: 'InfrastructureError', code: 'UNAUTHORIZED', message: 'Invalid auth token' },
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
// Route matching
|
|
324
|
+
if (path === '/health' && method === 'GET') {
|
|
325
|
+
return handleHealth(req, res);
|
|
326
|
+
}
|
|
327
|
+
if (path === '/services' && method === 'GET') {
|
|
328
|
+
return await handleGetServices(req, res);
|
|
329
|
+
}
|
|
330
|
+
if (path.startsWith('/services/') && method === 'GET') {
|
|
331
|
+
const serviceId = decodeURIComponent(path.slice('/services/'.length));
|
|
332
|
+
return await handleGetService(serviceId, res);
|
|
333
|
+
}
|
|
334
|
+
if (path === '/availability/dates' && method === 'POST') {
|
|
335
|
+
return await handleAvailableDates(req, res);
|
|
336
|
+
}
|
|
337
|
+
if (path === '/availability/slots' && method === 'POST') {
|
|
338
|
+
return await handleAvailableSlots(req, res);
|
|
339
|
+
}
|
|
340
|
+
if (path === '/availability/check' && method === 'POST') {
|
|
341
|
+
return await handleCheckSlot(req, res);
|
|
342
|
+
}
|
|
343
|
+
if (path === '/booking/create' && method === 'POST') {
|
|
344
|
+
return await handleCreateBooking(req, res);
|
|
345
|
+
}
|
|
346
|
+
if (path === '/booking/create-with-payment' && method === 'POST') {
|
|
347
|
+
return await handleCreateBookingWithPayment(req, res);
|
|
348
|
+
}
|
|
349
|
+
sendJson(res, 404, {
|
|
350
|
+
success: false,
|
|
351
|
+
error: { tag: 'InfrastructureError', code: 'NOT_FOUND', message: `Unknown route: ${method} ${path}` },
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
catch (e) {
|
|
355
|
+
console.error(`[middleware-server] Unhandled error on ${method} ${path}:`, e);
|
|
356
|
+
sendJson(res, 500, {
|
|
357
|
+
success: false,
|
|
358
|
+
error: {
|
|
359
|
+
tag: 'InfrastructureError',
|
|
360
|
+
code: 'UNKNOWN',
|
|
361
|
+
message: e instanceof Error ? e.message : 'Internal server error',
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
// Only start listening when this file is executed directly (not imported)
|
|
367
|
+
if (process.argv[1]?.match(/server\.(ts|js|mjs)$/)) {
|
|
368
|
+
server.listen(PORT, '0.0.0.0', () => {
|
|
369
|
+
console.log(`[middleware-server] Listening on port ${PORT}`);
|
|
370
|
+
console.log(`[middleware-server] Acuity URL: ${ACUITY_BASE_URL}`);
|
|
371
|
+
console.log(`[middleware-server] Coupon: ${COUPON_CODE ? 'configured' : 'NOT SET'}`);
|
|
372
|
+
console.log(`[middleware-server] Auth: ${AUTH_TOKEN ? 'enabled' : 'disabled'}`);
|
|
373
|
+
console.log(`[middleware-server] Headless: ${browserConfig.headless}`);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
export { server };
|
|
377
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/middleware/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ServiceResolver — Multi-Strategy Service Name Matching
|
|
3
|
+
*
|
|
4
|
+
* Effect Context.Tag providing resilient service resolution with
|
|
5
|
+
* cascading fallback strategies and confidence scoring.
|
|
6
|
+
*
|
|
7
|
+
* Strategies (tried in order via Effect.orElse):
|
|
8
|
+
* 1. ID match (confidence 1.0) — match by Acuity numeric ID in BUSINESS object
|
|
9
|
+
* 2. Normalized (confidence 0.95) — strip punctuation, collapse whitespace, exact match
|
|
10
|
+
* 3. Token overlap (0.5-0.9) — word-level intersection scoring
|
|
11
|
+
* 4. Fuzzy/Levenshtein (0.3-0.7) — edit-distance based matching
|
|
12
|
+
*/
|
|
13
|
+
import { Context, Effect, Layer } from 'effect';
|
|
14
|
+
import type { Page, ElementHandle } from 'playwright-core';
|
|
15
|
+
import { ServiceResolverError } from './errors.js';
|
|
16
|
+
export interface ServiceResolution {
|
|
17
|
+
/** The matched DOM element (the .select-item container) */
|
|
18
|
+
readonly element: ElementHandle;
|
|
19
|
+
/** Confidence score 0-1 */
|
|
20
|
+
readonly confidence: number;
|
|
21
|
+
/** Which strategy produced the match */
|
|
22
|
+
readonly strategy: 'id-match' | 'normalized-exact' | 'token-overlap' | 'fuzzy';
|
|
23
|
+
/** The name as it appears on the page */
|
|
24
|
+
readonly matchedName: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ServiceResolverShape {
|
|
27
|
+
readonly resolve: (page: Page, serviceName: string, appointmentTypeId?: string) => Effect.Effect<ServiceResolution, ServiceResolverError>;
|
|
28
|
+
}
|
|
29
|
+
declare const ServiceResolver_base: Context.TagClass<ServiceResolver, "scheduling-kit/ServiceResolver", ServiceResolverShape>;
|
|
30
|
+
export declare class ServiceResolver extends ServiceResolver_base {
|
|
31
|
+
}
|
|
32
|
+
/** Normalize a string: lowercase, strip non-alphanumeric (keep spaces), collapse whitespace. */
|
|
33
|
+
export declare const normalize: (s: string) => string;
|
|
34
|
+
/** Token overlap score: |intersection| / max(|a|, |b|). */
|
|
35
|
+
export declare const tokenOverlap: (a: string, b: string) => number;
|
|
36
|
+
/** Levenshtein edit distance between two strings. */
|
|
37
|
+
export declare const levenshtein: (a: string, b: string) => number;
|
|
38
|
+
/** Fuzzy match confidence: 1 - (distance / maxLen). */
|
|
39
|
+
export declare const fuzzyConfidence: (a: string, b: string) => number;
|
|
40
|
+
export declare const ServiceResolverLive: Layer.Layer<ServiceResolver>;
|
|
41
|
+
/**
|
|
42
|
+
* A static ServiceResolver for tests that always returns a mock resolution.
|
|
43
|
+
*/
|
|
44
|
+
export declare const ServiceResolverTest: (mockResolution?: Partial<ServiceResolution>) => Layer.Layer<ServiceResolver>;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=service-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-resolver.d.ts","sourceRoot":"","sources":["../../src/middleware/service-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAQnD,MAAM,WAAW,iBAAiB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,2BAA2B;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wCAAwC;IACxC,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG,kBAAkB,GAAG,eAAe,GAAG,OAAO,CAAC;IAC/E,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,EAAE,CACjB,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,iBAAiB,CAAC,EAAE,MAAM,KACtB,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,CAAC;CAC5D;;AAMD,qBAAa,eAAgB,SAAQ,oBAGlC;CAAG;AAMN,gGAAgG;AAChG,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,KAAG,MAC8B,CAAC;AAMrE,2DAA2D;AAC3D,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAWnD,CAAC;AAEF,qDAAqD;AACrD,eAAO,MAAM,WAAW,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MA2BlD,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,eAAe,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAQtD,CAAC;AAmMF,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,KAAK,CAAC,eAAe,CAyE5D,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC/B,iBAAiB,OAAO,CAAC,iBAAiB,CAAC,KACzC,KAAK,CAAC,KAAK,CAAC,eAAe,CAU3B,CAAC"}
|