@devdash/bofrid-api-types 0.1.5

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.
Files changed (163) hide show
  1. package/README.md +24 -0
  2. package/dist/app.d.ts +23 -0
  3. package/dist/dev.d.ts +6 -0
  4. package/dist/export-openapi.d.ts +9 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/lib/auth.d.ts +20 -0
  7. package/dist/lib/criipto-bankid.d.ts +45 -0
  8. package/dist/lib/datalake.d.ts +7 -0
  9. package/dist/lib/docs-filter.d.ts +15 -0
  10. package/dist/lib/email-action-token.d.ts +26 -0
  11. package/dist/lib/email-utm.d.ts +42 -0
  12. package/dist/lib/email.d.ts +94 -0
  13. package/dist/lib/env.d.ts +18 -0
  14. package/dist/lib/errors.d.ts +6 -0
  15. package/dist/lib/helpers.d.ts +6 -0
  16. package/dist/lib/logger.d.ts +25 -0
  17. package/dist/lib/markets.d.ts +1 -0
  18. package/dist/lib/org-number.d.ts +5 -0
  19. package/dist/lib/ownership.d.ts +47 -0
  20. package/dist/lib/pdf-watermark.d.ts +25 -0
  21. package/dist/lib/personnummer.d.ts +14 -0
  22. package/dist/lib/posthog.d.ts +51 -0
  23. package/dist/lib/premium.d.ts +13 -0
  24. package/dist/lib/profile-resolver.d.ts +14 -0
  25. package/dist/lib/redirect-state.d.ts +40 -0
  26. package/dist/lib/revalidate.d.ts +23 -0
  27. package/dist/lib/schemas.d.ts +21 -0
  28. package/dist/lib/sentry.d.ts +31 -0
  29. package/dist/lib/slug.d.ts +5 -0
  30. package/dist/lib/sms.d.ts +22 -0
  31. package/dist/lib/system-log.d.ts +31 -0
  32. package/dist/lib/webhook-events.d.ts +26 -0
  33. package/dist/lib/webhooks.d.ts +28 -0
  34. package/dist/middleware/auth-debug.d.ts +17 -0
  35. package/dist/middleware/auth.d.ts +19 -0
  36. package/dist/middleware/bibi-logger.d.ts +6 -0
  37. package/dist/middleware/cors.d.ts +1 -0
  38. package/dist/middleware/request-id.d.ts +10 -0
  39. package/dist/middleware/sentry-context.d.ts +8 -0
  40. package/dist/routes/activity-feed.d.ts +64 -0
  41. package/dist/routes/admin-bevakningar.d.ts +200 -0
  42. package/dist/routes/admin-companies.d.ts +381 -0
  43. package/dist/routes/admin-email-jobs.d.ts +257 -0
  44. package/dist/routes/admin-email-logs.d.ts +9 -0
  45. package/dist/routes/admin-fb-leads.d.ts +32 -0
  46. package/dist/routes/admin-import.d.ts +188 -0
  47. package/dist/routes/admin-login-history.d.ts +9 -0
  48. package/dist/routes/admin-marketing.d.ts +15 -0
  49. package/dist/routes/admin-metabase.d.ts +9 -0
  50. package/dist/routes/admin-notifications.d.ts +7 -0
  51. package/dist/routes/admin-paying-customers.d.ts +74 -0
  52. package/dist/routes/admin-sessions.d.ts +10 -0
  53. package/dist/routes/admin-stats.d.ts +380 -0
  54. package/dist/routes/admin-system-logs.d.ts +10 -0
  55. package/dist/routes/admin-users.d.ts +299 -0
  56. package/dist/routes/admin-webhooks.d.ts +276 -0
  57. package/dist/routes/api-keys.d.ts +123 -0
  58. package/dist/routes/applications.d.ts +385 -0
  59. package/dist/routes/auth.d.ts +15 -0
  60. package/dist/routes/billing.d.ts +369 -0
  61. package/dist/routes/bostadsmerit.d.ts +51 -0
  62. package/dist/routes/companies.d.ts +842 -0
  63. package/dist/routes/contact-reveals.d.ts +102 -0
  64. package/dist/routes/conversations/handlers/conversation.d.ts +5 -0
  65. package/dist/routes/conversations/handlers/initiate.d.ts +4 -0
  66. package/dist/routes/conversations/handlers/messages.d.ts +5 -0
  67. package/dist/routes/conversations/handlers/state.d.ts +5 -0
  68. package/dist/routes/conversations/helpers/access.d.ts +11 -0
  69. package/dist/routes/conversations/helpers/enrich-conversation.d.ts +58 -0
  70. package/dist/routes/conversations/helpers/identity.d.ts +43 -0
  71. package/dist/routes/conversations/helpers/notify-recipient.d.ts +10 -0
  72. package/dist/routes/conversations/helpers/reconcile-reveal.d.ts +10 -0
  73. package/dist/routes/conversations/helpers/scrub-contact.d.ts +1 -0
  74. package/dist/routes/conversations/index.d.ts +422 -0
  75. package/dist/routes/conversations/routes.d.ts +924 -0
  76. package/dist/routes/conversations/schemas.d.ts +216 -0
  77. package/dist/routes/cron.d.ts +27 -0
  78. package/dist/routes/documents.d.ts +493 -0
  79. package/dist/routes/email-actions.d.ts +8 -0
  80. package/dist/routes/fastighetslista.d.ts +94 -0
  81. package/dist/routes/geo.d.ts +518 -0
  82. package/dist/routes/geocoding.d.ts +192 -0
  83. package/dist/routes/health.d.ts +43 -0
  84. package/dist/routes/housing-history.d.ts +381 -0
  85. package/dist/routes/index.d.ts +15321 -0
  86. package/dist/routes/leads.d.ts +281 -0
  87. package/dist/routes/listing-helpers.d.ts +33 -0
  88. package/dist/routes/listing-publications.d.ts +636 -0
  89. package/dist/routes/listings.d.ts +1846 -0
  90. package/dist/routes/location-interests.d.ts +754 -0
  91. package/dist/routes/lookup.d.ts +109 -0
  92. package/dist/routes/mejl.d.ts +377 -0
  93. package/dist/routes/profiles.d.ts +281 -0
  94. package/dist/routes/properties.d.ts +1266 -0
  95. package/dist/routes/public-listings.d.ts +1137 -0
  96. package/dist/routes/public-profiles.d.ts +293 -0
  97. package/dist/routes/references.d.ts +695 -0
  98. package/dist/routes/search-partners.d.ts +4 -0
  99. package/dist/routes/site-config.d.ts +103 -0
  100. package/dist/routes/storage.d.ts +367 -0
  101. package/dist/routes/tenant-boost.d.ts +229 -0
  102. package/dist/routes/tenants.d.ts +336 -0
  103. package/dist/routes/track.d.ts +19 -0
  104. package/dist/routes/translate.d.ts +51 -0
  105. package/dist/routes/users.d.ts +517 -0
  106. package/dist/routes/verification.d.ts +175 -0
  107. package/dist/routes/webhooks.d.ts +9 -0
  108. package/dist/rpc.d.ts +11 -0
  109. package/dist/serve.d.ts +5 -0
  110. package/dist/services/activity-feed/activity-feed.service.d.ts +26 -0
  111. package/dist/services/applications/approval.service.d.ts +17 -0
  112. package/dist/services/auth/bankid-login.service.d.ts +40 -0
  113. package/dist/services/billing/constants.d.ts +2 -0
  114. package/dist/services/billing/contact-billing.service.d.ts +59 -0
  115. package/dist/services/billing/customer.service.d.ts +14 -0
  116. package/dist/services/billing/invoice-item.service.d.ts +49 -0
  117. package/dist/services/billing/invoice.service.d.ts +21 -0
  118. package/dist/services/billing/listing-upgrade-checkout.service.d.ts +45 -0
  119. package/dist/services/billing/purchase-receipt-email.d.ts +23 -0
  120. package/dist/services/billing/reveal-allowance.service.d.ts +33 -0
  121. package/dist/services/billing/stripe.d.ts +6 -0
  122. package/dist/services/billing/subscription.service.d.ts +21 -0
  123. package/dist/services/billing/types.d.ts +64 -0
  124. package/dist/services/billing/verify-session.service.d.ts +17 -0
  125. package/dist/services/billing/webhook.service.d.ts +8 -0
  126. package/dist/services/bostadsmerit/calculator.d.ts +51 -0
  127. package/dist/services/bostadsmerit/couple-calculator.d.ts +46 -0
  128. package/dist/services/bostadsmerit/tracker.service.d.ts +45 -0
  129. package/dist/services/chat-access/unlock-chat.service.d.ts +62 -0
  130. package/dist/services/conversations/upsert-conversation.d.ts +11 -0
  131. package/dist/services/email-jobs/email-job-sender.d.ts +7 -0
  132. package/dist/services/email-jobs/email-job.service.d.ts +67 -0
  133. package/dist/services/geo/bevakning-matching.service.d.ts +67 -0
  134. package/dist/services/geo/geo-listings.service.d.ts +38 -0
  135. package/dist/services/geo/geo.service.d.ts +233 -0
  136. package/dist/services/geo/geocode.service.d.ts +16 -0
  137. package/dist/services/geo/market-insights-by-coords.service.d.ts +67 -0
  138. package/dist/services/geo/market-insights.service.d.ts +44 -0
  139. package/dist/services/geo/market-overview.service.d.ts +42 -0
  140. package/dist/services/homii/image.d.ts +24 -0
  141. package/dist/services/homii/index.d.ts +12 -0
  142. package/dist/services/homii/location.d.ts +32 -0
  143. package/dist/services/homii/mapper.d.ts +41 -0
  144. package/dist/services/homii/types.d.ts +91 -0
  145. package/dist/services/leads/constants.d.ts +32 -0
  146. package/dist/services/leads/generate-leads.service.d.ts +38 -0
  147. package/dist/services/leads/matching.service.d.ts +55 -0
  148. package/dist/services/leads/tier.service.d.ts +6 -0
  149. package/dist/services/leads/types.d.ts +27 -0
  150. package/dist/services/listings/seo.service.d.ts +57 -0
  151. package/dist/services/listings/status.d.ts +37 -0
  152. package/dist/services/mejl/client.d.ts +38 -0
  153. package/dist/services/mrkoll/client.d.ts +95 -0
  154. package/dist/services/mrkoll/import.d.ts +38 -0
  155. package/dist/services/mrkoll/match.d.ts +35 -0
  156. package/dist/services/notifications/bibi-projects.d.ts +43 -0
  157. package/dist/services/notifications/bibi.d.ts +229 -0
  158. package/dist/services/profiles/bankid-verify.d.ts +23 -0
  159. package/dist/services/realtime.d.ts +14 -0
  160. package/dist/services/references/history-linker.d.ts +19 -0
  161. package/dist/services/tenant-boost/constants.d.ts +120 -0
  162. package/dist/services/tenant-boost/tenant-boost.service.d.ts +59 -0
  163. package/package.json +29 -0
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # @devdash/bofrid-api-types
2
+
3
+ TypeScript type definitions for the [Bofrid API](https://github.com/devdashco/bofrid-api). Consumed by [bofrid-web](https://github.com/devdashco/bofrid-web) to get full Hono RPC client typing.
4
+
5
+ Contains **only `.d.ts` files** — no runtime code. Mechanically generated from the API source by CI (`tsc --emitDeclarationOnly --noCheck` + `tsc-alias` to rewrite path aliases).
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ bun add -d @devdash/bofrid-api-types
11
+ ```
12
+
13
+ Public package — no `.npmrc`, no tokens.
14
+
15
+ ## Use
16
+
17
+ ```ts
18
+ import type { AppType } from "@devdash/bofrid-api-types";
19
+ import { hc } from "hono/client";
20
+
21
+ const client = hc<AppType>("https://vapi.bofrid.se");
22
+ ```
23
+
24
+ That's it — fully typed Hono RPC.
package/dist/app.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import "./lib/env";
2
+ import "./lib/sentry";
3
+ import "./lib/posthog";
4
+ import { OpenAPIHono } from "@hono/zod-openapi";
5
+ import { type AppType } from "./routes/index";
6
+ /**
7
+ * Context variable types available in all route handlers.
8
+ */
9
+ export type AppBindings = {
10
+ Variables: {
11
+ /** Unique ID for this request (propagated from X-Request-Id or generated) */
12
+ requestId: string;
13
+ /** Better Auth user.id (text PK from the user table) */
14
+ authUserId: string;
15
+ /** Bofrid profile UUID (PK from bofrid_profiles) */
16
+ profileId: string;
17
+ /** True when authenticated via BOFRID_API_KEY (master service key) */
18
+ isMasterKey?: boolean;
19
+ };
20
+ };
21
+ declare const app: OpenAPIHono<AppBindings, {}, "/">;
22
+ export default app;
23
+ export type { AppType };
package/dist/dev.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ declare const _default: {
2
+ port: number;
3
+ idleTimeout: number;
4
+ fetch: (request: Request, Env?: unknown, executionCtx?: import("hono").ExecutionContext) => Response | Promise<Response>;
5
+ };
6
+ export default _default;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Export the OpenAPI spec to a static JSON file.
3
+ *
4
+ * Usage: bun apps/api/src/export-openapi.ts
5
+ *
6
+ * Writes openapi.json to the repo root so AI agents and build tools
7
+ * can read the spec without the server running (Zero-Drift step 2).
8
+ */
9
+ export {};
@@ -0,0 +1,3 @@
1
+ declare const _default: (incoming: import("http").IncomingMessage | import("http2").Http2ServerRequest, outgoing: import("http").ServerResponse | import("http2").Http2ServerResponse) => Promise<void>;
2
+ export default _default;
3
+ export type { AppType } from "./routes/index";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Better Auth instance for the Hono API server.
3
+ *
4
+ * Uses the shared config from @bofrid/auth/config. No platform-specific
5
+ * plugins needed — the API server only reads sessions, never sets cookies
6
+ * via Next.js helpers.
7
+ */
8
+ export declare const auth: import("better-auth").Auth<import("better-auth").BetterAuthOptions>;
9
+ export type Auth = typeof auth;
10
+ /**
11
+ * Generate a magic link verification URL that logs the user in when clicked.
12
+ * Creates a verification token in the DB and returns the full URL pointing
13
+ * to the web app's Better Auth verify endpoint.
14
+ *
15
+ * @param email — The user's email address
16
+ * @param callbackURL — Where to redirect after successful verification (e.g. /dashboard/...)
17
+ * @param expirySeconds — Custom expiry (default: 15 min). Use longer values for digest emails.
18
+ * @returns The full magic link URL
19
+ */
20
+ export declare function generateMagicLinkUrl(email: string, callbackURL: string, expirySeconds?: number): Promise<string>;
@@ -0,0 +1,45 @@
1
+ export interface CriiptoBankIDUser {
2
+ personalNumber: string;
3
+ name: string;
4
+ givenName: string;
5
+ surname: string;
6
+ ipAddress?: string;
7
+ }
8
+ export declare class CriiptoBankIDError extends Error {
9
+ errorCode?: string | undefined;
10
+ details?: string | undefined;
11
+ constructor(message: string, errorCode?: string | undefined, details?: string | undefined);
12
+ }
13
+ export interface CriiptoTokenResponse {
14
+ id_token: string;
15
+ access_token?: string;
16
+ token_type?: string;
17
+ expires_in?: number;
18
+ }
19
+ export declare class CriiptoBankIDService {
20
+ private domain;
21
+ private clientId;
22
+ private clientSecret;
23
+ constructor();
24
+ /**
25
+ * Extract user information from Criipto ID token (JWT decode, no verification).
26
+ */
27
+ extractUserFromIdToken(idToken: string): CriiptoBankIDUser;
28
+ /**
29
+ * Build the OIDC authorize URL for the redirect flow.
30
+ * The user's browser is redirected here; Criipto handles the full BankID UX.
31
+ *
32
+ * On mobile, pass `platform` ("ios" | "android") to add `login_hint=appswitch:<platform>`
33
+ * so Criipto launches the BankID app directly instead of showing a QR code.
34
+ */
35
+ buildAuthorizeUrl(redirectUri: string, state: string, platform?: string): string;
36
+ /**
37
+ * Exchange an authorization code for an id_token (server-to-server).
38
+ */
39
+ exchangeCodeForToken(code: string, redirectUri: string): Promise<CriiptoTokenResponse>;
40
+ /**
41
+ * Get client IP from request headers.
42
+ */
43
+ static getClientIP(headers: Headers): string;
44
+ }
45
+ export declare function getCriiptoBankIDService(): CriiptoBankIDService;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Datalake client — queries self-hosted Supabase (supabase.bofrid.dev) via REST API.
3
+ *
4
+ * Used by lookup routes and folkbokföring import.
5
+ */
6
+ export declare function isDatalakeConfigured(): boolean;
7
+ export declare function datalakeQuery<T>(table: string, params: Record<string, string>): Promise<T[]>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Spec filtering for the public docs surface (docs.bofrid.com).
3
+ *
4
+ * The API exposes one OpenAPI document, but external callers should only see
5
+ * the read-only public endpoints. An `?key=BOFRID_API_KEY` query unlocks the
6
+ * full spec. Paths under `/dev/`, `/cron/`, `/internal/` are never exposed —
7
+ * they aren't meaningful outside the deployment.
8
+ */
9
+ type Spec = Record<string, unknown> & {
10
+ info?: Record<string, unknown>;
11
+ paths?: Record<string, unknown>;
12
+ };
13
+ export declare function stripExcludedPaths(spec: Spec): Spec;
14
+ export declare function filterPublicSpec(spec: Spec): Spec;
15
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Signed, stateless tokens for one-click email actions (no login required).
3
+ *
4
+ * Format: `base64url(json_payload).base64url(hmac-sha256)`
5
+ * Payloads (discriminated by `action`):
6
+ * - `{ action: "submit", listingId, exp }` — landlord one-click publish
7
+ * - `{ action: "cancel_bevakning", bevakningId, userId, exp }` — tenant unsubscribe
8
+ */
9
+ export interface SubmitListingTokenPayload {
10
+ action: "submit";
11
+ listingId: string;
12
+ exp: number;
13
+ }
14
+ export interface CancelBevakningTokenPayload {
15
+ action: "cancel_bevakning";
16
+ bevakningId: string;
17
+ userId: string;
18
+ exp: number;
19
+ }
20
+ export type EmailActionPayload = SubmitListingTokenPayload | CancelBevakningTokenPayload;
21
+ /** Create a signed token for a one-click "submit listing" email action. */
22
+ export declare function createEmailActionToken(listingId: string, action: "submit"): string;
23
+ /** Create a signed token for a one-click "cancel bevakning" email action. */
24
+ export declare function createCancelBevakningToken(bevakningId: string, userId: string): string;
25
+ /** Verify and decode a signed email action token. Returns null if invalid/expired/tampered. */
26
+ export declare function verifyEmailActionToken(token: string): EmailActionPayload | null;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Email UTM rewriter
3
+ *
4
+ * Centralized URL instrumentation for outgoing transactional emails. Every
5
+ * email link to a Bofrid destination gets `utm_source=email&utm_medium=transactional&utm_campaign=<template>`
6
+ * appended automatically so PostHog autocapture / pageview events can attribute
7
+ * sessions back to a specific email template.
8
+ *
9
+ * Two URL shapes need to be handled:
10
+ *
11
+ * 1. Direct destination URLs — `https://bofrid.se/dashboard/foo` — UTM is
12
+ * appended to the URL itself.
13
+ *
14
+ * 2. Magic-link verify URLs — `https://bofrid.se/api/auth/magic-link/verify?token=…&callbackURL=/dashboard/foo`
15
+ * The user hits the verify endpoint, Better Auth strips the token and
16
+ * redirects to `callbackURL`. UTM appended to the verify URL itself would
17
+ * be lost on redirect, so we drill into the `callbackURL` query param and
18
+ * append UTM there — the user lands on the destination with UTM intact.
19
+ *
20
+ * URLs that don't render a trackable page (`/email-actions/*` one-click action
21
+ * endpoints, other `/api/*` paths) and non-Bofrid hosts (mailto:, tel:, support
22
+ * links, etc.) are left untouched. URLs that already carry `utm_source` are
23
+ * also untouched so per-call manual UTM stays authoritative.
24
+ */
25
+ /**
26
+ * Append UTM to a fully-qualified URL string, handling magic-link verify URLs
27
+ * by drilling into their `callbackURL`/`errorCallbackURL` params.
28
+ *
29
+ * Returns the original string unchanged if the URL can't be parsed, points at
30
+ * a non-Bofrid host, or already carries UTM.
31
+ */
32
+ export declare function addEmailUtmToUrl(urlStr: string, template: string): string;
33
+ export declare function rewriteEmailHtmlLinks(html: string, template: string): string;
34
+ export declare function rewriteEmailTextLinks(text: string, template: string): string;
35
+ export declare function rewriteEmailLinks(opts: {
36
+ html: string;
37
+ text: string;
38
+ template: string;
39
+ }): {
40
+ html: string;
41
+ text: string;
42
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Email Service — mejl.to (production) with Resend fallback, or Mailpit (staging/CI)
3
+ *
4
+ * Centralized email sending for the Hono API.
5
+ *
6
+ * Transport priority:
7
+ * 1. Mailpit HTTP API — set MAILPIT_URL (e.g. https://mailpit.bofrid.dev)
8
+ * 2. SMTP — set SMTP_HOST + SMTP_PORT (local dev with Mailpit on localhost)
9
+ * 3. mejl.to (Amazon SES via mail.bofrid.se) — production primary
10
+ * 4. Resend — production fallback when mejl.to fails
11
+ * 5. Gmail SMTP relay — last-resort fallback
12
+ *
13
+ * Bulk emails (bevakningar) use Google Workspace SMTP relay via service account
14
+ * OAuth2. Set GOOGLE_SERVICE_ACCOUNT_EMAIL + GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY.
15
+ */
16
+ declare const EMAIL_FROM: string;
17
+ declare const EMAIL_REPLY_TO = "hej@bofrid.se";
18
+ declare const SUPPORT_EMAIL = "support@bofrid.se";
19
+ declare const ADMIN_EMAILS: string[];
20
+ export { EMAIL_FROM, EMAIL_REPLY_TO, SUPPORT_EMAIL, ADMIN_EMAILS };
21
+ import type { EmailLocale } from "@bofrid/email";
22
+ export declare function translatePropertyType(type: string | null | undefined, locale?: EmailLocale): string;
23
+ type NotificationCategory = "listings" | "applications" | "leads" | "documents" | "messages";
24
+ /**
25
+ * Resolve a user's preferred email locale from their profile preferences.
26
+ * Falls back to "sv" if not set or profile not found.
27
+ */
28
+ export declare function getUserLocale(profileId: string): Promise<EmailLocale>;
29
+ /** Which transport delivered the email. */
30
+ export type EmailTransport = "mejl" | "resend" | "gmail-smtp" | "gmail-api" | "smtp" | "mailpit-http";
31
+ interface SendEmailOptions {
32
+ to: string;
33
+ subject: string;
34
+ html: string;
35
+ text: string;
36
+ replyTo?: string;
37
+ /** Optional — when provided, the send is logged to bofrid_email_logs automatically. */
38
+ log?: {
39
+ recipientId?: string | null;
40
+ templateName: string;
41
+ metadata?: Record<string, unknown>;
42
+ };
43
+ }
44
+ /**
45
+ * Send an email using SMTP/Mailpit (if SMTP_HOST is set) or mejl.to (production)
46
+ * with Resend as a secondary fallback. SMTP is preferred when available — useful
47
+ * for staging/CI email capture.
48
+ *
49
+ * When `opts.log` is provided, the send result (sent/failed) is persisted
50
+ * to `bofrid_email_logs` for audit and future automation (e.g. reminder dedup).
51
+ */
52
+ export declare function sendEmail(opts: SendEmailOptions): Promise<void>;
53
+ /**
54
+ * Get company notification preferences. All categories default to `true`.
55
+ */
56
+ export declare function getCompanyNotificationPrefs(companyId: string): Promise<Record<NotificationCategory, boolean>>;
57
+ /**
58
+ * Get company preferred locale, falling back to "sv".
59
+ */
60
+ export declare function getCompanyLocale(companyId: string): Promise<EmailLocale>;
61
+ interface SendBulkEmailOptions {
62
+ to: string;
63
+ subject: string;
64
+ html: string;
65
+ text: string;
66
+ replyTo?: string;
67
+ /** Optional — when provided, the send is logged to bofrid_email_logs automatically. */
68
+ log?: {
69
+ recipientId?: string | null;
70
+ templateName: string;
71
+ metadata?: Record<string, unknown>;
72
+ };
73
+ }
74
+ /**
75
+ * Send a single email via Gmail API (for bulk campaigns).
76
+ * Uses SMTP/Mailpit when SMTP_HOST is set (staging/CI),
77
+ * Gmail API when Google service account is configured,
78
+ * or falls back to sendEmail() (Resend).
79
+ */
80
+ export declare function sendBulkEmail(opts: SendBulkEmailOptions): Promise<void>;
81
+ /**
82
+ * Send emails in throttled batches via Gmail API.
83
+ * Reuses a single access token for the entire batch run.
84
+ *
85
+ * @param emails - Array of email options to send
86
+ * @param batchSize - Emails per batch (default 10)
87
+ * @param delayMs - Delay between batches in ms (default 2000)
88
+ * @returns { sent, failed, errors }
89
+ */
90
+ export declare function sendBulkEmailBatch(emails: SendBulkEmailOptions[], batchSize?: number, delayMs?: number): Promise<{
91
+ sent: number;
92
+ failed: number;
93
+ errors: string[];
94
+ }>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Startup environment variable validation for apps/api.
3
+ *
4
+ * Imported as a side-effect in app.ts before any routes load.
5
+ * Fails fast with a clear, grouped error instead of cryptic crashes
6
+ * deep in auth/db/storage modules.
7
+ *
8
+ * Auth-related vars (DATABASE_URL, BETTER_AUTH_SECRET, MEJL_API_KEY)
9
+ * are also validated by @bofrid/auth/config.ts, but surfacing them here
10
+ * gives a single, readable error on startup.
11
+ */
12
+ export declare const REQUIRED_CORE: readonly [readonly ["DATABASE_URL", "Postgres connection string (Supabase)"], readonly ["BETTER_AUTH_URL", "Better Auth server URL (e.g. https://api.bofrid.se)"], readonly ["BETTER_AUTH_SECRET", "Signing secret for Better Auth sessions"], readonly ["APP_URL", "Frontend URL for emails/revalidation (e.g. https://bofrid.se)"]];
13
+ export declare const REQUIRED_AUTH: readonly [];
14
+ export declare const REQUIRED_STORAGE: readonly [readonly ["R2_ENDPOINT", "Cloudflare R2 S3 endpoint URL"], readonly ["R2_ACCESS_KEY_ID", "Cloudflare R2 access key"], readonly ["R2_SECRET_ACCESS_KEY", "Cloudflare R2 secret key"], readonly ["R2_BUCKET_NAME", "R2 bucket name (e.g. bofrid-media)"], readonly ["R2_PUBLIC_URL", "R2 public CDN URL (e.g. https://bofrid.media)"]];
15
+ export declare const REQUIRED_GEOCODING: readonly [readonly ["GOOGLE_PLACES_API_KEY", "Google Places API key for address geocoding"]];
16
+ export declare const REQUIRED_BANKID: readonly [readonly ["CRIIPTO_DOMAIN", "Criipto tenant domain (e.g. your-tenant.criipto.id)"], readonly ["CRIIPTO_CLIENT_ID", "Criipto OAuth client ID"], readonly ["CRIIPTO_CLIENT_SECRET", "Criipto OAuth client secret"]];
17
+ export declare const REQUIRED_SERVICES: readonly [readonly ["BOFRID_MASTER_KEY", "AES-256 encryption key for document/PII encryption (openssl rand -hex 32)"], readonly ["BOFRID_API_KEY", "Master API key for headless/service auth (X-API-Key header)"], readonly ["STRIPE_SECRET_KEY", "Stripe secret key for billing features"], readonly ["STRIPE_WEBHOOK_SECRET", "Stripe webhook signing secret"], readonly ["GEMINI_API_KEY", "Google Gemini API key for AI translation"], readonly ["NUSVAR_API_KEY", "Nusvar API key for folkbokföring lookups"]];
18
+ export declare const ALL_REQUIRED_VARS: readonly [readonly ["DATABASE_URL", "Postgres connection string (Supabase)"], readonly ["BETTER_AUTH_URL", "Better Auth server URL (e.g. https://api.bofrid.se)"], readonly ["BETTER_AUTH_SECRET", "Signing secret for Better Auth sessions"], readonly ["APP_URL", "Frontend URL for emails/revalidation (e.g. https://bofrid.se)"], readonly ["CRIIPTO_DOMAIN", "Criipto tenant domain (e.g. your-tenant.criipto.id)"], readonly ["CRIIPTO_CLIENT_ID", "Criipto OAuth client ID"], readonly ["CRIIPTO_CLIENT_SECRET", "Criipto OAuth client secret"], readonly ["R2_ENDPOINT", "Cloudflare R2 S3 endpoint URL"], readonly ["R2_ACCESS_KEY_ID", "Cloudflare R2 access key"], readonly ["R2_SECRET_ACCESS_KEY", "Cloudflare R2 secret key"], readonly ["R2_BUCKET_NAME", "R2 bucket name (e.g. bofrid-media)"], readonly ["R2_PUBLIC_URL", "R2 public CDN URL (e.g. https://bofrid.media)"], readonly ["GOOGLE_PLACES_API_KEY", "Google Places API key for address geocoding"], readonly ["BOFRID_MASTER_KEY", "AES-256 encryption key for document/PII encryption (openssl rand -hex 32)"], readonly ["BOFRID_API_KEY", "Master API key for headless/service auth (X-API-Key header)"], readonly ["STRIPE_SECRET_KEY", "Stripe secret key for billing features"], readonly ["STRIPE_WEBHOOK_SECRET", "Stripe webhook signing secret"], readonly ["GEMINI_API_KEY", "Google Gemini API key for AI translation"], readonly ["NUSVAR_API_KEY", "Nusvar API key for folkbokföring lookups"]];
@@ -0,0 +1,6 @@
1
+ import { HTTPException } from "hono/http-exception";
2
+ export declare function unauthorized(message?: string): HTTPException;
3
+ export declare function forbidden(message?: string): HTTPException;
4
+ export declare function notFound(message?: string): HTTPException;
5
+ export declare function badRequest(message?: string): HTTPException;
6
+ export declare function serverError(message?: string): HTTPException;
@@ -0,0 +1,6 @@
1
+ export { formatDisplayName, formatFullName } from "@bofrid/db/name-format";
2
+ /**
3
+ * Filters out undefined values from an object, returning only the keys
4
+ * the client explicitly sent. Used for partial (PATCH) updates.
5
+ */
6
+ export declare function definedFields(body: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Structured JSON logger for production (Railway/Docker).
3
+ *
4
+ * In production, outputs JSON lines so Railway's log viewer can filter/search.
5
+ * In development, outputs human-readable prefixed strings.
6
+ *
7
+ * Usage:
8
+ * import { log } from "../lib/logger";
9
+ * log.error("source", "Something broke", { userId, path });
10
+ * log.warn("source", "Degraded state", { detail });
11
+ * log.info("source", "Startup complete", { port });
12
+ */
13
+ type LogData = Record<string, unknown>;
14
+ export declare const log: {
15
+ info: (source: string, message: string, data?: LogData) => void;
16
+ warn: (source: string, message: string, data?: LogData) => void;
17
+ error: (source: string, message: string, data?: LogData) => void;
18
+ fatal: (source: string, message: string, data?: LogData) => void;
19
+ /** Extract safe error info for logging (message + stack). */
20
+ errData(err: unknown): {
21
+ error: string;
22
+ stack?: string;
23
+ };
24
+ };
25
+ export {};
@@ -0,0 +1 @@
1
+ export declare function getCountryByMarket(market: string | null | undefined): string | undefined;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Swedish Organization Number (Organisationsnummer) validation.
3
+ * Format: XXXXXX-XXXX (10 digits, Luhn checksum).
4
+ */
5
+ export declare function validateSwedishOrgNumber(orgNumber: string): boolean;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Check if a profile is an active member (any role) of a company.
3
+ */
4
+ export declare function isCompanyMember(companyId: string, profileId: string): Promise<boolean>;
5
+ /**
6
+ * Check if a profile is an active admin of a company.
7
+ */
8
+ export declare function isCompanyAdmin(companyId: string, profileId: string): Promise<boolean>;
9
+ /**
10
+ * Resolve the company a user is currently "acting as".
11
+ * Returns the companyId if the user's businessType is 'company', they have a
12
+ * linked companyId, and they are an active member. Returns null otherwise.
13
+ */
14
+ export declare function resolveActingCompany(profileId: string): Promise<string | null>;
15
+ /**
16
+ * Check if a profile has platform-admin privileges.
17
+ * Source of truth: Better Auth's user.role column.
18
+ */
19
+ export declare function checkIsAdmin(profileId: string): Promise<boolean>;
20
+ /**
21
+ * Check ownership of a resource that has landlordId and optional companyId.
22
+ * Returns true if the profile is the direct landlord, an active company member,
23
+ * or a platform admin (when `allowAdmin` is true).
24
+ *
25
+ * When `respectMode` is true, the check enforces context separation:
26
+ * - Private users can only access resources where companyId IS NULL
27
+ * - Company users can access resources owned by their acting company, OR
28
+ * their own private resources (companyId IS NULL + landlordId matches).
29
+ * The carve-out exists so users who later joined a company through
30
+ * onboarding don't lose access to their pre-company properties.
31
+ * This prevents a private account from seeing arbitrary company resources
32
+ * (and vice versa) while keeping users' own resources visible.
33
+ */
34
+ export declare function isResourceOwner(resource: {
35
+ landlordId: string | null;
36
+ companyId: string | null;
37
+ }, profileId: string, opts?: {
38
+ allowAdmin?: boolean;
39
+ respectMode?: boolean;
40
+ }): Promise<boolean>;
41
+ /**
42
+ * Resolve a landlordId for lead creation.
43
+ * Returns the direct landlordId if set, otherwise falls back to the
44
+ * company's first active admin (ordered by most recently updated).
45
+ * Returns null if neither exists.
46
+ */
47
+ export declare function resolveLeadLandlordId(landlordId: string | null, companyId: string | null): Promise<string | null>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * PDF watermark utility for landlord document views.
3
+ *
4
+ * Adds a diagonal, semi-transparent watermark with:
5
+ * - Landlord name / company
6
+ * - Timestamp (ISO)
7
+ * - Short trace ID (first 8 chars of a hash)
8
+ *
9
+ * This makes it traceable if a landlord leaks a tenant's document.
10
+ * Only applied to PDF files; images/other formats are returned as-is.
11
+ */
12
+ export interface WatermarkInfo {
13
+ /** Landlord profile ID */
14
+ profileId: string;
15
+ /** Landlord display name (or company name) */
16
+ displayName: string;
17
+ /** Document ID being viewed */
18
+ documentId: string;
19
+ }
20
+ /**
21
+ * Apply a traceable watermark to a PDF buffer.
22
+ * Returns the watermarked PDF as a Buffer.
23
+ * If anything fails, returns the original buffer unchanged — never breaks the download.
24
+ */
25
+ export declare function watermarkPdf(pdfBytes: Buffer, info: WatermarkInfo): Promise<Buffer>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Personnummer utilities
3
+ *
4
+ * Shared helpers for Swedish personal identity number (personnummer) validation,
5
+ * normalization, and HMAC hashing for identity lookup.
6
+ */
7
+ export declare function isValidPersonnummer(pnr: string): boolean;
8
+ export declare function normalizePersonnummer(input: string): string;
9
+ export declare function calculateAgeFromPersonnummer(pnr: string): number | null;
10
+ export declare function generateIdentityHash(personnummer: string): string;
11
+ /**
12
+ * Mask an email for display: "philip@bofrid.se" → "ph***@bofrid.se"
13
+ */
14
+ export declare function maskEmail(email: string): string;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * PostHog server-side client for the Hono API.
3
+ *
4
+ * Use for business events that can't fire from the browser — Stripe webhooks,
5
+ * cron outcomes, reveals, Boost purchases, email deliveries, etc.
6
+ *
7
+ * Errors should still go to Sentry. PostHog here is for product/business events.
8
+ *
9
+ * Env vars (all optional — PostHog is silently disabled when KEY is missing):
10
+ * POSTHOG_API_KEY — Project API key (phc_*)
11
+ * POSTHOG_HOST — Defaults to https://eu.i.posthog.com
12
+ */
13
+ declare const IS_ENABLED: boolean;
14
+ type CaptureProps = {
15
+ distinctId: string;
16
+ event: string;
17
+ properties?: Record<string, unknown>;
18
+ groups?: Record<string, string>;
19
+ };
20
+ /**
21
+ * Capture a product event and flush immediately.
22
+ * Must await in serverless — the process exits before the default 10s flush interval fires.
23
+ */
24
+ export declare function capture({ distinctId, event, properties, groups }: CaptureProps): Promise<void>;
25
+ /**
26
+ * Capture an exception. Sentry is the primary error destination — this is
27
+ * only for cases where you also want the exception in PostHog alongside
28
+ * user/session context. Prefer `captureException` from `@/lib/sentry`.
29
+ */
30
+ export declare function captureServerException(err: unknown, opts: {
31
+ distinctId: string;
32
+ properties?: Record<string, unknown>;
33
+ }): void;
34
+ /**
35
+ * Flush pending events (call before process.exit / at end of serverless
36
+ * function invocations to avoid dropped events on Vercel).
37
+ */
38
+ export declare function flush(): Promise<void>;
39
+ /**
40
+ * Identify a user. Call on login/signup to associate distinct ID with user properties.
41
+ * No-op when PostHog is disabled.
42
+ */
43
+ export declare function identify({ distinctId, properties, }: {
44
+ distinctId: string;
45
+ properties?: Record<string, unknown>;
46
+ }): void;
47
+ /**
48
+ * Graceful shutdown — flush + close the underlying HTTP pool.
49
+ */
50
+ export declare function shutdown(): Promise<void>;
51
+ export { IS_ENABLED as isPostHogEnabled };
@@ -0,0 +1,13 @@
1
+ export declare const PREMIUM_BOOST_DAYS = 30;
2
+ /** SQL fragment: true when a listing's premium boost is still within the 30-day window. */
3
+ export declare const premiumBoostActiveSql: import("drizzle-orm").SQL<unknown>;
4
+ /** JS predicate mirroring `premiumBoostActiveSql` for in-memory checks. */
5
+ export declare function isPremiumBoostActive(upgradedAt: Date | string | null | undefined): boolean;
6
+ /**
7
+ * Whether the authenticated user has ≥1 listing currently inside the
8
+ * premium-visibility window — either on a property they own directly
9
+ * (private landlord) or on a property of a company they belong to.
10
+ *
11
+ * Used to gate premium-only landlord surfaces (e.g. Hyresmarknad).
12
+ */
13
+ export declare function hasActivePremiumListing(authUserId: string): Promise<boolean>;
@@ -0,0 +1,14 @@
1
+ import { profiles } from "@bofrid/db";
2
+ export type ProfileRow = typeof profiles.$inferSelect;
3
+ /**
4
+ * Resolve Better Auth user.id → Bofrid profile row.
5
+ *
6
+ * Looks up by authUserId first, then falls back to email match for migrated
7
+ * Firebase profiles. Does NOT auto-create — profile creation is handled
8
+ * exclusively by the Better Auth databaseHooks.user.create.after hook.
9
+ */
10
+ export declare function resolveProfile(authUserId: string): Promise<ProfileRow | null>;
11
+ /**
12
+ * Resolve or throw — for routes that require a profile to exist.
13
+ */
14
+ export declare function requireProfile(authUserId: string): Promise<ProfileRow>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Stateless signed redirect state for BankID OIDC redirect flow.
3
+ *
4
+ * Uses HMAC-SHA256 signed tokens — survives Vercel Serverless (no memory/DB).
5
+ */
6
+ /** State stored during the Criipto OIDC redirect flow. */
7
+ export interface RedirectState {
8
+ authUserId: string;
9
+ profileId: string;
10
+ /** The web app URL to redirect back to after verification. */
11
+ returnUrl: string;
12
+ timestamp: number;
13
+ }
14
+ /**
15
+ * Encode redirect state into a signed, URL-safe token.
16
+ * Format: `base64url(json_payload).base64url(hmac)`
17
+ */
18
+ export declare function encodeRedirectState(data: Omit<RedirectState, "timestamp">): string;
19
+ /**
20
+ * Decode and verify a signed redirect state token.
21
+ * Returns the state data or `null` if invalid/expired/tampered.
22
+ */
23
+ /** State stored during the BankID login redirect flow — no user identity yet. */
24
+ export interface LoginRedirectState {
25
+ flow: 'login';
26
+ returnUrl: string;
27
+ timestamp: number;
28
+ }
29
+ /**
30
+ * Encode login redirect state into a signed, URL-safe token.
31
+ */
32
+ export declare function encodeLoginRedirectState(data: {
33
+ returnUrl: string;
34
+ }): string;
35
+ /**
36
+ * Decode and verify a signed login redirect state token.
37
+ * Returns the state data or `null` if invalid/expired/tampered.
38
+ */
39
+ export declare function decodeLoginRedirectState(token: string): LoginRedirectState | null;
40
+ export declare function decodeRedirectState(token: string): RedirectState | null;
@@ -0,0 +1,23 @@
1
+ export declare function revalidateNextPaths(paths: string[]): void;
2
+ interface ListingForRevalidation {
3
+ id: string;
4
+ publicUrl?: string | null;
5
+ publicUrlEn?: string | null;
6
+ }
7
+ /**
8
+ * Revalidate every cached page that shows a single listing, plus the landlord's
9
+ * dashboard. Call after any mutation that changes user-visible listing data.
10
+ *
11
+ * URLs covered:
12
+ * - Legacy `/bostad/{id}` + `/en/listing/{id}` (308-redirect or fallback render)
13
+ * - SEO `publicUrl` + `publicUrlEn` (e.g. /hyra-lagenhet/.../{slug})
14
+ * - `/dashboard/properties` (landlord's listing list)
15
+ *
16
+ * Optionally deletes the R2-cached OG image so the next social/AI scrape
17
+ * regenerates it. Pass `invalidateOgImage: true` when something visible on
18
+ * the OG card changes — gallery photos, price, address, room count.
19
+ */
20
+ export declare function revalidateListingPaths(listing: ListingForRevalidation, options?: {
21
+ invalidateOgImage?: boolean;
22
+ }): void;
23
+ export {};