@mars-stack/core 0.4.0
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/README.md +32 -0
- package/cursor/manifest.json +304 -0
- package/cursor/rules/mars-composition-patterns.mdc +186 -0
- package/cursor/rules/mars-data-access.mdc +26 -0
- package/cursor/rules/mars-project-structure.mdc +34 -0
- package/cursor/rules/mars-security.mdc +25 -0
- package/cursor/rules/mars-testing.mdc +24 -0
- package/cursor/rules/mars-ui-conventions.mdc +29 -0
- package/cursor/skills/mars-add-api-route/SKILL.md +120 -0
- package/cursor/skills/mars-add-audit-log/SKILL.md +373 -0
- package/cursor/skills/mars-add-blog/SKILL.md +447 -0
- package/cursor/skills/mars-add-command-palette/SKILL.md +438 -0
- package/cursor/skills/mars-add-component/SKILL.md +158 -0
- package/cursor/skills/mars-add-crud-routes/SKILL.md +221 -0
- package/cursor/skills/mars-add-e2e-test/SKILL.md +227 -0
- package/cursor/skills/mars-add-error-boundary/SKILL.md +472 -0
- package/cursor/skills/mars-add-feature/SKILL.md +174 -0
- package/cursor/skills/mars-add-middleware/SKILL.md +135 -0
- package/cursor/skills/mars-add-page/SKILL.md +153 -0
- package/cursor/skills/mars-add-prisma-model/SKILL.md +148 -0
- package/cursor/skills/mars-add-protected-resource/SKILL.md +192 -0
- package/cursor/skills/mars-add-role/SKILL.md +156 -0
- package/cursor/skills/mars-add-server-action/SKILL.md +167 -0
- package/cursor/skills/mars-add-webhook/SKILL.md +192 -0
- package/cursor/skills/mars-build-complete-feature/SKILL.md +228 -0
- package/cursor/skills/mars-build-dashboard/SKILL.md +211 -0
- package/cursor/skills/mars-build-data-table/SKILL.md +284 -0
- package/cursor/skills/mars-build-form/SKILL.md +229 -0
- package/cursor/skills/mars-build-landing-page/SKILL.md +248 -0
- package/cursor/skills/mars-capture-conversation-context/SKILL.md +119 -0
- package/cursor/skills/mars-configure-ai/SKILL.md +617 -0
- package/cursor/skills/mars-configure-analytics/SKILL.md +413 -0
- package/cursor/skills/mars-configure-dark-mode/SKILL.md +309 -0
- package/cursor/skills/mars-configure-email/SKILL.md +170 -0
- package/cursor/skills/mars-configure-email-verification/SKILL.md +333 -0
- package/cursor/skills/mars-configure-feature-flags/SKILL.md +361 -0
- package/cursor/skills/mars-configure-i18n/SKILL.md +518 -0
- package/cursor/skills/mars-configure-jobs/SKILL.md +500 -0
- package/cursor/skills/mars-configure-magic-links/SKILL.md +385 -0
- package/cursor/skills/mars-configure-multi-tenancy/SKILL.md +611 -0
- package/cursor/skills/mars-configure-notifications/SKILL.md +569 -0
- package/cursor/skills/mars-configure-oauth/SKILL.md +217 -0
- package/cursor/skills/mars-configure-onboarding/SKILL.md +483 -0
- package/cursor/skills/mars-configure-payments/SKILL.md +243 -0
- package/cursor/skills/mars-configure-realtime/SKILL.md +733 -0
- package/cursor/skills/mars-configure-search/SKILL.md +581 -0
- package/cursor/skills/mars-configure-storage/SKILL.md +273 -0
- package/cursor/skills/mars-configure-two-factor/SKILL.md +518 -0
- package/cursor/skills/mars-create-execution-plan/SKILL.md +204 -0
- package/cursor/skills/mars-create-seed/SKILL.md +191 -0
- package/cursor/skills/mars-deploy-to-vercel/SKILL.md +300 -0
- package/cursor/skills/mars-design-tokens/SKILL.md +138 -0
- package/cursor/skills/mars-setup-billing/SKILL.md +322 -0
- package/cursor/skills/mars-setup-project/SKILL.md +104 -0
- package/cursor/skills/mars-setup-teams/SKILL.md +688 -0
- package/cursor/skills/mars-test-api-route/SKILL.md +219 -0
- package/cursor/skills/mars-update-architecture-docs/SKILL.md +189 -0
- package/dist/api-error/index.d.ts +27 -0
- package/dist/api-error/index.d.ts.map +1 -0
- package/dist/api-error/index.js +2 -0
- package/dist/auth/credential-tag.d.ts +5 -0
- package/dist/auth/credential-tag.d.ts.map +1 -0
- package/dist/auth/credential-tag.js +2 -0
- package/dist/auth/crypto-utils.d.ts +43 -0
- package/dist/auth/crypto-utils.d.ts.map +1 -0
- package/dist/auth/crypto-utils.js +1 -0
- package/dist/auth/csrf.d.ts +32 -0
- package/dist/auth/csrf.d.ts.map +1 -0
- package/dist/auth/csrf.js +2 -0
- package/dist/auth/hooks/index.d.ts +4 -0
- package/dist/auth/hooks/index.d.ts.map +1 -0
- package/dist/auth/hooks/index.js +68 -0
- package/dist/auth/hooks/useCSRF.d.ts +7 -0
- package/dist/auth/hooks/useCSRF.d.ts.map +1 -0
- package/dist/auth/hooks/usePasswordStrength.d.ts +17 -0
- package/dist/auth/hooks/usePasswordStrength.d.ts.map +1 -0
- package/dist/auth/internal-api-key.d.ts +5 -0
- package/dist/auth/internal-api-key.d.ts.map +1 -0
- package/dist/auth/internal-api-key.js +30 -0
- package/dist/auth/link-utils.d.ts +13 -0
- package/dist/auth/link-utils.d.ts.map +1 -0
- package/dist/auth/link-utils.js +1 -0
- package/dist/auth/middleware.d.ts +56 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +3 -0
- package/dist/auth/password.d.ts +28 -0
- package/dist/auth/password.d.ts.map +1 -0
- package/dist/auth/password.js +1 -0
- package/dist/auth/reset-token.d.ts +3 -0
- package/dist/auth/reset-token.d.ts.map +1 -0
- package/dist/auth/reset-token.js +9 -0
- package/dist/auth/responses.d.ts +15 -0
- package/dist/auth/responses.d.ts.map +1 -0
- package/dist/auth/responses.js +2 -0
- package/dist/auth/session.d.ts +79 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/session.js +1 -0
- package/dist/auth/types.d.ts +18 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +10 -0
- package/dist/auth/validation.d.ts +146 -0
- package/dist/auth/validation.d.ts.map +1 -0
- package/dist/auth/validation.js +116 -0
- package/dist/auth/validators.d.ts +4 -0
- package/dist/auth/validators.d.ts.map +1 -0
- package/dist/auth/validators.js +27 -0
- package/dist/auth/verification.d.ts +54 -0
- package/dist/auth/verification.d.ts.map +1 -0
- package/dist/auth/verification.js +39 -0
- package/dist/chunk-4LS3QDD5.js +162 -0
- package/dist/chunk-ABBUHT5Z.js +110 -0
- package/dist/chunk-CTYAVMOF.js +15 -0
- package/dist/chunk-GVLH2GQP.js +14 -0
- package/dist/chunk-HOSMMQMA.js +109 -0
- package/dist/chunk-MXQ66RUN.js +28 -0
- package/dist/chunk-PZE3JGXO.js +149 -0
- package/dist/chunk-QAH2Y5WK.js +93 -0
- package/dist/chunk-QWMN5UJC.js +76 -0
- package/dist/chunk-ROQV54MU.js +117 -0
- package/dist/chunk-U4NZQ366.js +46 -0
- package/dist/chunk-WBJOIENS.js +22 -0
- package/dist/chunk-WO6FHJHG.js +29 -0
- package/dist/chunk-Z5BEKPJI.js +96 -0
- package/dist/chunk-ZA46T6GX.js +24 -0
- package/dist/configure-mars.d.ts +104 -0
- package/dist/configure-mars.d.ts.map +1 -0
- package/dist/database/index.d.ts +8 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +1 -0
- package/dist/email/index.d.ts +25 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +2 -0
- package/dist/email/types.d.ts +18 -0
- package/dist/email/types.d.ts.map +1 -0
- package/dist/env/index.d.ts +36 -0
- package/dist/env/index.d.ts.map +1 -0
- package/dist/env/index.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +163 -0
- package/dist/logger/index.d.ts +80 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +1 -0
- package/dist/payments/index.d.ts +53 -0
- package/dist/payments/index.d.ts.map +1 -0
- package/dist/payments/index.js +72 -0
- package/dist/plugin/builtin/email-plugins.d.ts +10 -0
- package/dist/plugin/builtin/email-plugins.d.ts.map +1 -0
- package/dist/plugin/builtin/index.d.ts +4 -0
- package/dist/plugin/builtin/index.d.ts.map +1 -0
- package/dist/plugin/builtin/index.js +324 -0
- package/dist/plugin/builtin/payment-plugins.d.ts +4 -0
- package/dist/plugin/builtin/payment-plugins.d.ts.map +1 -0
- package/dist/plugin/builtin/storage-plugins.d.ts +5 -0
- package/dist/plugin/builtin/storage-plugins.d.ts.map +1 -0
- package/dist/plugin/index.d.ts +21 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +30 -0
- package/dist/rate-limit/index.d.ts +89 -0
- package/dist/rate-limit/index.d.ts.map +1 -0
- package/dist/rate-limit/index.js +166 -0
- package/dist/seo/faq.d.ts +37 -0
- package/dist/seo/faq.d.ts.map +1 -0
- package/dist/seo/index.d.ts +75 -0
- package/dist/seo/index.d.ts.map +1 -0
- package/dist/seo/index.js +1 -0
- package/dist/storage/index.d.ts +50 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +211 -0
- package/dist/test-utils/factories.d.ts +38 -0
- package/dist/test-utils/factories.d.ts.map +1 -0
- package/dist/test-utils/index.d.ts +6 -0
- package/dist/test-utils/index.d.ts.map +1 -0
- package/dist/test-utils/index.js +117 -0
- package/dist/test-utils/mock-auth.d.ts +25 -0
- package/dist/test-utils/mock-auth.d.ts.map +1 -0
- package/dist/test-utils/mock-prisma.d.ts +55 -0
- package/dist/test-utils/mock-prisma.d.ts.map +1 -0
- package/dist/test-utils/render.d.ts +4 -0
- package/dist/test-utils/render.d.ts.map +1 -0
- package/dist/test-utils/request-helpers.d.ts +6 -0
- package/dist/test-utils/request-helpers.d.ts.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/math.d.ts +2 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/math.js +7 -0
- package/dist/utils/optional-import.d.ts +14 -0
- package/dist/utils/optional-import.d.ts.map +1 -0
- package/package.json +205 -0
- package/scripts/generate-skill-adapters.ts +146 -0
- package/scripts/postinstall.mjs +146 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type FaqParagraphPart = {
|
|
2
|
+
type: 'text';
|
|
3
|
+
value: string;
|
|
4
|
+
} | {
|
|
5
|
+
type: 'link';
|
|
6
|
+
href: string;
|
|
7
|
+
text: string;
|
|
8
|
+
};
|
|
9
|
+
export type FaqAnswerBlock = {
|
|
10
|
+
type: 'paragraph';
|
|
11
|
+
parts: ReadonlyArray<FaqParagraphPart>;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'unordered_list';
|
|
14
|
+
items: ReadonlyArray<string>;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'ordered_list';
|
|
17
|
+
items: ReadonlyArray<string>;
|
|
18
|
+
};
|
|
19
|
+
export interface FaqItem {
|
|
20
|
+
question: string;
|
|
21
|
+
answer: ReadonlyArray<FaqAnswerBlock>;
|
|
22
|
+
}
|
|
23
|
+
export interface FaqSection {
|
|
24
|
+
heading: string;
|
|
25
|
+
items: ReadonlyArray<FaqItem>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Converts structured FAQ answer blocks to a plain-text string.
|
|
29
|
+
* Paragraphs are collapsed to single lines; list items are joined with semicolons.
|
|
30
|
+
*
|
|
31
|
+
* @param blocks - Array of paragraph or list blocks from an FAQ answer
|
|
32
|
+
* @returns A newline-separated plain-text representation
|
|
33
|
+
* @example
|
|
34
|
+
* const text = toPlainTextFromFaqBlocks(faqItem.answer);
|
|
35
|
+
*/
|
|
36
|
+
export declare function toPlainTextFromFaqBlocks(blocks: ReadonlyArray<FaqAnswerBlock>): string;
|
|
37
|
+
//# sourceMappingURL=faq.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faq.d.ts","sourceRoot":"","sources":["../../src/seo/faq.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC;AAE3D,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;CAC/B;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,GAAG,MAAM,CAsBtF"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export { toPlainTextFromFaqBlocks } from './faq';
|
|
2
|
+
export type { FaqParagraphPart, FaqAnswerBlock, FaqItem, FaqSection } from './faq';
|
|
3
|
+
export interface BreadcrumbItem {
|
|
4
|
+
name: string;
|
|
5
|
+
url: string;
|
|
6
|
+
}
|
|
7
|
+
export interface JsonLdFaqItem {
|
|
8
|
+
question: string;
|
|
9
|
+
answer: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ArticleJsonLdInput {
|
|
12
|
+
title: string;
|
|
13
|
+
description: string;
|
|
14
|
+
slug: string;
|
|
15
|
+
date: string;
|
|
16
|
+
author: string;
|
|
17
|
+
tags: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface SiteNavItem {
|
|
20
|
+
name: string;
|
|
21
|
+
url: string;
|
|
22
|
+
}
|
|
23
|
+
interface SeoConfig {
|
|
24
|
+
name: string;
|
|
25
|
+
url: string;
|
|
26
|
+
description: string;
|
|
27
|
+
supportEmail?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Builds a standalone FAQ page JSON-LD structured data object.
|
|
31
|
+
*
|
|
32
|
+
* @param items - Array of question/answer pairs
|
|
33
|
+
* @returns A Schema.org FAQPage JSON-LD object
|
|
34
|
+
* @example
|
|
35
|
+
* const jsonLd = buildFaqPageJsonLd([{ question: 'What is Mars?', answer: 'A SaaS generator.' }]);
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Builds a Schema.org FAQPage JSON-LD object from question/answer pairs.
|
|
39
|
+
*
|
|
40
|
+
* @param items - Array of question/answer objects
|
|
41
|
+
* @returns A JSON-LD object suitable for embedding in a `<script type="application/ld+json">` tag
|
|
42
|
+
* @example
|
|
43
|
+
* const jsonLd = buildFaqPageJsonLd([
|
|
44
|
+
* { question: 'What is Mars?', answer: 'A SaaS scaffold tool.' },
|
|
45
|
+
* ]);
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildFaqPageJsonLd(items: JsonLdFaqItem[]): Record<string, unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* Creates a set of JSON-LD structured data builders scoped to a site's identity.
|
|
50
|
+
* Supports Organization, WebSite, SiteNavigation, BreadcrumbList, and Article schemas.
|
|
51
|
+
*
|
|
52
|
+
* @param config - Site identity with name, URL, description, and optional support email
|
|
53
|
+
* @returns Builder functions for each supported Schema.org type
|
|
54
|
+
* @example
|
|
55
|
+
* const seo = createSeoBuilders({ name: 'Acme', url: 'https://acme.com', description: 'SaaS' });
|
|
56
|
+
* const orgLd = seo.buildOrganizationJsonLd();
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* Creates a set of Schema.org JSON-LD builders pre-configured with site metadata.
|
|
60
|
+
* Returns builders for Organization, WebSite, SiteNavigation, BreadcrumbList, and Article.
|
|
61
|
+
*
|
|
62
|
+
* @param config - Site metadata including name, URL, description, and optional support email
|
|
63
|
+
* @returns An object with JSON-LD builder functions
|
|
64
|
+
* @example
|
|
65
|
+
* const seo = createSeoBuilders({ name: 'Acme', url: 'https://acme.com', description: 'SaaS app' });
|
|
66
|
+
* const orgLd = seo.buildOrganizationJsonLd();
|
|
67
|
+
*/
|
|
68
|
+
export declare function createSeoBuilders(config: SeoConfig): {
|
|
69
|
+
buildOrganizationJsonLd: () => Record<string, unknown>;
|
|
70
|
+
buildWebSiteJsonLd: () => Record<string, unknown>;
|
|
71
|
+
buildSiteNavigationJsonLd: (items: SiteNavItem[]) => Record<string, unknown>;
|
|
72
|
+
buildBreadcrumbListJsonLd: (items: BreadcrumbItem[]) => Record<string, unknown>;
|
|
73
|
+
buildArticleJsonLd: (article: ArticleJsonLdInput) => Record<string, unknown>;
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/seo/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,OAAO,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEnF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAalF;AAED;;;;;;;;;GASG;AACH;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS;mCAIb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;8BAU5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;uCASZ,WAAW,EAAE,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;uCAavC,cAAc,EAAE,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;kCAa/C,kBAAkB,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;EAkClF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { buildFaqPageJsonLd, createSeoBuilders, toPlainTextFromFaqBlocks } from '../chunk-ABBUHT5Z.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
export interface UploadParams {
|
|
3
|
+
filename: string;
|
|
4
|
+
contentType: string;
|
|
5
|
+
data: Buffer | ReadableStream | Blob;
|
|
6
|
+
access?: 'public' | 'private';
|
|
7
|
+
maxSizeBytes?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface UploadResult {
|
|
10
|
+
url: string;
|
|
11
|
+
pathname: string;
|
|
12
|
+
contentType: string;
|
|
13
|
+
size: number;
|
|
14
|
+
}
|
|
15
|
+
export interface StorageProvider {
|
|
16
|
+
upload(params: UploadParams): Promise<UploadResult>;
|
|
17
|
+
delete(url: string): Promise<void>;
|
|
18
|
+
getSignedUrl?(url: string, expiresIn?: number): Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
export interface StorageServiceConfig {
|
|
21
|
+
provider: string;
|
|
22
|
+
maxFileSizeBytes?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a file storage service backed by the configured provider
|
|
26
|
+
* (Vercel Blob, S3, or local filesystem). Enforces file size limits and
|
|
27
|
+
* sanitizes filenames across all providers.
|
|
28
|
+
*
|
|
29
|
+
* @param config - Configuration with provider name and optional global max file size
|
|
30
|
+
* @returns An object with upload, delete, and getSignedUrl methods
|
|
31
|
+
* @example
|
|
32
|
+
* const storage = createStorageService({ provider: 'vercel', maxFileSizeBytes: 10_000_000 });
|
|
33
|
+
* const result = await storage.upload({ filename: 'photo.jpg', contentType: 'image/jpeg', data: buffer });
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Creates a storage service backed by the configured provider (Vercel Blob, S3, or local filesystem).
|
|
37
|
+
* Enforces an optional global max file size from the config.
|
|
38
|
+
*
|
|
39
|
+
* @param config - Configuration with provider name and optional max file size in bytes
|
|
40
|
+
* @returns An object with upload, delete, and getSignedUrl methods
|
|
41
|
+
* @example
|
|
42
|
+
* const storage = createStorageService({ provider: 'vercel', maxFileSizeBytes: 10_000_000 });
|
|
43
|
+
* const result = await storage.upload({ filename: 'avatar.png', contentType: 'image/png', data: buffer });
|
|
44
|
+
*/
|
|
45
|
+
export declare function createStorageService(config: StorageServiceConfig): {
|
|
46
|
+
upload: (params: UploadParams) => Promise<UploadResult>;
|
|
47
|
+
delete: (url: string) => Promise<void>;
|
|
48
|
+
getSignedUrl: (url: string, expiresIn?: number) => Promise<string>;
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAGrB,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,YAAY,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAwOD;;;;;;;;;;GAUG;AACH;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB;qBAIjC,YAAY,KAAG,OAAO,CAAC,YAAY,CAAC;kBAQnC,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;wBAIpB,MAAM,cAAc,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;EAQ9E"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { importOptional } from '../chunk-CTYAVMOF.js';
|
|
2
|
+
import 'server-only';
|
|
3
|
+
|
|
4
|
+
function sanitizeFilename(filename) {
|
|
5
|
+
return filename.replace(/[^\w.\-]/g, "_").replace(/\.{2,}/g, ".").slice(0, 255);
|
|
6
|
+
}
|
|
7
|
+
function getDataSize(data) {
|
|
8
|
+
if (Buffer.isBuffer(data)) return data.byteLength;
|
|
9
|
+
if (data instanceof Blob) return data.size;
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function validateFileSize(data, maxSizeBytes) {
|
|
13
|
+
if (!maxSizeBytes) return;
|
|
14
|
+
const size = getDataSize(data);
|
|
15
|
+
if (size !== null && size > maxSizeBytes) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`File size ${size} bytes exceeds maximum allowed size of ${maxSizeBytes} bytes`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function createVercelBlobProvider() {
|
|
22
|
+
return {
|
|
23
|
+
async upload(params) {
|
|
24
|
+
const { put } = await importOptional("@vercel/blob");
|
|
25
|
+
validateFileSize(params.data, params.maxSizeBytes);
|
|
26
|
+
const safe = sanitizeFilename(params.filename);
|
|
27
|
+
const blob = await put(safe, params.data, {
|
|
28
|
+
access: "public",
|
|
29
|
+
contentType: params.contentType,
|
|
30
|
+
addRandomSuffix: true
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
url: blob.url,
|
|
34
|
+
pathname: blob.pathname,
|
|
35
|
+
contentType: params.contentType,
|
|
36
|
+
size: getDataSize(params.data) ?? 0
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
async delete(url) {
|
|
40
|
+
const { del } = await importOptional("@vercel/blob");
|
|
41
|
+
await del(url);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function createS3Provider() {
|
|
46
|
+
function getS3Config() {
|
|
47
|
+
const region = process.env.AWS_REGION;
|
|
48
|
+
const bucket = process.env.S3_BUCKET_NAME;
|
|
49
|
+
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
|
|
50
|
+
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
51
|
+
if (!region) throw new Error("AWS_REGION is not set");
|
|
52
|
+
if (!bucket) throw new Error("S3_BUCKET_NAME is not set");
|
|
53
|
+
if (!accessKeyId) throw new Error("AWS_ACCESS_KEY_ID is not set");
|
|
54
|
+
if (!secretAccessKey) throw new Error("AWS_SECRET_ACCESS_KEY is not set");
|
|
55
|
+
return { region, bucket, accessKeyId, secretAccessKey };
|
|
56
|
+
}
|
|
57
|
+
async function getClient() {
|
|
58
|
+
const { S3Client } = await importOptional("@aws-sdk/client-s3");
|
|
59
|
+
const config = getS3Config();
|
|
60
|
+
return {
|
|
61
|
+
client: new S3Client({
|
|
62
|
+
region: config.region,
|
|
63
|
+
credentials: {
|
|
64
|
+
accessKeyId: config.accessKeyId,
|
|
65
|
+
secretAccessKey: config.secretAccessKey
|
|
66
|
+
}
|
|
67
|
+
}),
|
|
68
|
+
bucket: config.bucket
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
async upload(params) {
|
|
73
|
+
const { PutObjectCommand } = await importOptional("@aws-sdk/client-s3");
|
|
74
|
+
validateFileSize(params.data, params.maxSizeBytes);
|
|
75
|
+
const safe = sanitizeFilename(params.filename);
|
|
76
|
+
const key = `uploads/${Date.now()}-${safe}`;
|
|
77
|
+
const { client, bucket } = await getClient();
|
|
78
|
+
let body;
|
|
79
|
+
if (Buffer.isBuffer(params.data)) {
|
|
80
|
+
body = params.data;
|
|
81
|
+
} else if (params.data instanceof Blob) {
|
|
82
|
+
body = params.data;
|
|
83
|
+
} else {
|
|
84
|
+
const chunks = [];
|
|
85
|
+
const reader = params.data.getReader();
|
|
86
|
+
let done = false;
|
|
87
|
+
while (!done) {
|
|
88
|
+
const result = await reader.read();
|
|
89
|
+
done = result.done;
|
|
90
|
+
if (result.value) chunks.push(result.value);
|
|
91
|
+
}
|
|
92
|
+
body = Buffer.concat(chunks);
|
|
93
|
+
}
|
|
94
|
+
const command = new PutObjectCommand({
|
|
95
|
+
Bucket: bucket,
|
|
96
|
+
Key: key,
|
|
97
|
+
Body: body,
|
|
98
|
+
ContentType: params.contentType,
|
|
99
|
+
ACL: params.access === "public" ? "public-read" : "private"
|
|
100
|
+
});
|
|
101
|
+
await client.send(command);
|
|
102
|
+
const config = getS3Config();
|
|
103
|
+
const url = `https://${bucket}.s3.${config.region}.amazonaws.com/${key}`;
|
|
104
|
+
return {
|
|
105
|
+
url,
|
|
106
|
+
pathname: key,
|
|
107
|
+
contentType: params.contentType,
|
|
108
|
+
size: Buffer.isBuffer(body) ? body.byteLength : body.size
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
async delete(url) {
|
|
112
|
+
const { DeleteObjectCommand } = await importOptional("@aws-sdk/client-s3");
|
|
113
|
+
const { client, bucket } = await getClient();
|
|
114
|
+
const urlObj = new URL(url);
|
|
115
|
+
const key = urlObj.pathname.slice(1);
|
|
116
|
+
const command = new DeleteObjectCommand({
|
|
117
|
+
Bucket: bucket,
|
|
118
|
+
Key: key
|
|
119
|
+
});
|
|
120
|
+
await client.send(command);
|
|
121
|
+
},
|
|
122
|
+
async getSignedUrl(url, expiresIn = 3600) {
|
|
123
|
+
const { GetObjectCommand } = await importOptional("@aws-sdk/client-s3");
|
|
124
|
+
const { getSignedUrl: s3GetSignedUrl } = await importOptional("@aws-sdk/s3-request-presigner");
|
|
125
|
+
const { client, bucket } = await getClient();
|
|
126
|
+
const urlObj = new URL(url);
|
|
127
|
+
const key = urlObj.pathname.slice(1);
|
|
128
|
+
const command = new GetObjectCommand({
|
|
129
|
+
Bucket: bucket,
|
|
130
|
+
Key: key
|
|
131
|
+
});
|
|
132
|
+
return s3GetSignedUrl(client, command, { expiresIn });
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function createLocalProvider() {
|
|
137
|
+
return {
|
|
138
|
+
async upload(params) {
|
|
139
|
+
const { writeFile, mkdir } = await import('fs/promises');
|
|
140
|
+
const { join } = await import('path');
|
|
141
|
+
validateFileSize(params.data, params.maxSizeBytes);
|
|
142
|
+
const safe = sanitizeFilename(params.filename);
|
|
143
|
+
const timestamped = `${Date.now()}-${safe}`;
|
|
144
|
+
const uploadsDir = join(process.cwd(), "public", "uploads");
|
|
145
|
+
await mkdir(uploadsDir, { recursive: true });
|
|
146
|
+
const filePath = join(uploadsDir, timestamped);
|
|
147
|
+
let buffer;
|
|
148
|
+
if (Buffer.isBuffer(params.data)) {
|
|
149
|
+
buffer = params.data;
|
|
150
|
+
} else if (params.data instanceof Blob) {
|
|
151
|
+
buffer = Buffer.from(await params.data.arrayBuffer());
|
|
152
|
+
} else {
|
|
153
|
+
const chunks = [];
|
|
154
|
+
const reader = params.data.getReader();
|
|
155
|
+
let done = false;
|
|
156
|
+
while (!done) {
|
|
157
|
+
const result = await reader.read();
|
|
158
|
+
done = result.done;
|
|
159
|
+
if (result.value) chunks.push(result.value);
|
|
160
|
+
}
|
|
161
|
+
buffer = Buffer.concat(chunks);
|
|
162
|
+
}
|
|
163
|
+
await writeFile(filePath, buffer);
|
|
164
|
+
return {
|
|
165
|
+
url: `/uploads/${timestamped}`,
|
|
166
|
+
pathname: `uploads/${timestamped}`,
|
|
167
|
+
contentType: params.contentType,
|
|
168
|
+
size: buffer.byteLength
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
async delete(url) {
|
|
172
|
+
const { unlink } = await import('fs/promises');
|
|
173
|
+
const { join } = await import('path');
|
|
174
|
+
const relativePath = url.startsWith("/") ? url.slice(1) : url;
|
|
175
|
+
const filePath = join(process.cwd(), "public", relativePath);
|
|
176
|
+
try {
|
|
177
|
+
await unlink(filePath);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error.code !== "ENOENT") throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
var providerFactories = {
|
|
185
|
+
vercel: createVercelBlobProvider,
|
|
186
|
+
s3: createS3Provider,
|
|
187
|
+
local: createLocalProvider
|
|
188
|
+
};
|
|
189
|
+
function createStorageService(config) {
|
|
190
|
+
const factory = providerFactories[config.provider] ?? providerFactories.local;
|
|
191
|
+
const provider = factory();
|
|
192
|
+
async function upload(params) {
|
|
193
|
+
const effectiveParams = {
|
|
194
|
+
...params,
|
|
195
|
+
maxSizeBytes: params.maxSizeBytes ?? config.maxFileSizeBytes
|
|
196
|
+
};
|
|
197
|
+
return provider.upload(effectiveParams);
|
|
198
|
+
}
|
|
199
|
+
async function deleteFile(url) {
|
|
200
|
+
return provider.delete(url);
|
|
201
|
+
}
|
|
202
|
+
async function getSignedUrl(url, expiresIn) {
|
|
203
|
+
if (!provider.getSignedUrl) {
|
|
204
|
+
return url;
|
|
205
|
+
}
|
|
206
|
+
return provider.getSignedUrl(url, expiresIn);
|
|
207
|
+
}
|
|
208
|
+
return { upload, delete: deleteFile, getSignedUrl };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export { createStorageService };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
interface MockUserOverrides {
|
|
2
|
+
id?: string;
|
|
3
|
+
email?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
role?: string;
|
|
6
|
+
emailVerified?: boolean;
|
|
7
|
+
image?: string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function createMockUser(overrides?: MockUserOverrides): {
|
|
10
|
+
id: string;
|
|
11
|
+
email: string;
|
|
12
|
+
name: string;
|
|
13
|
+
password: string;
|
|
14
|
+
role: string;
|
|
15
|
+
emailVerified: Date | null;
|
|
16
|
+
image: string | null;
|
|
17
|
+
failedLoginAttempts: number;
|
|
18
|
+
lastFailedLogin: null;
|
|
19
|
+
lockedUntil: null;
|
|
20
|
+
termsAcceptedAt: Date;
|
|
21
|
+
privacyAcceptedAt: Date;
|
|
22
|
+
marketingOptIn: boolean;
|
|
23
|
+
marketingOptInAt: null;
|
|
24
|
+
createdAt: Date;
|
|
25
|
+
updatedAt: Date;
|
|
26
|
+
};
|
|
27
|
+
interface MockSessionOverrides {
|
|
28
|
+
userId?: string;
|
|
29
|
+
role?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function createMockSession(overrides?: MockSessionOverrides): {
|
|
32
|
+
userId: string;
|
|
33
|
+
role: string;
|
|
34
|
+
email: string;
|
|
35
|
+
name: string;
|
|
36
|
+
};
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=factories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factories.d.ts","sourceRoot":"","sources":["../../src/test-utils/factories.ts"],"names":[],"mappings":"AAAA,UAAU,iBAAiB;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAgB,cAAc,CAAC,SAAS,GAAE,iBAAsB;;;;;;;;;;;;;;;;;EAmB/D;AAED,UAAU,oBAAoB;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,oBAAyB;;;;;EAOrE"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { renderWithProviders } from './render';
|
|
2
|
+
export { createMockUser, createMockSession } from './factories';
|
|
3
|
+
export { mockPrisma, resetMockPrisma } from './mock-prisma';
|
|
4
|
+
export { mockAuth, mockAuthAsAdmin, mockAuthAsUser } from './mock-auth';
|
|
5
|
+
export { createTestRequest, createTestRequestWithBody } from './request-helpers';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test-utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { vi } from 'vitest';
|
|
4
|
+
import { NextRequest } from 'next/server';
|
|
5
|
+
|
|
6
|
+
// src/test-utils/render.tsx
|
|
7
|
+
function AllProviders({ children }) {
|
|
8
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
9
|
+
}
|
|
10
|
+
function renderWithProviders(ui, options) {
|
|
11
|
+
return render(ui, { wrapper: AllProviders, ...options });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/test-utils/factories.ts
|
|
15
|
+
function createMockUser(overrides = {}) {
|
|
16
|
+
return {
|
|
17
|
+
id: overrides.id ?? "user-test-id",
|
|
18
|
+
email: overrides.email ?? "test@example.com",
|
|
19
|
+
name: overrides.name ?? "Test User",
|
|
20
|
+
password: "$2a$10$mockhashedpassword",
|
|
21
|
+
role: overrides.role ?? "user",
|
|
22
|
+
emailVerified: overrides.emailVerified ? /* @__PURE__ */ new Date() : null,
|
|
23
|
+
image: overrides.image ?? null,
|
|
24
|
+
failedLoginAttempts: 0,
|
|
25
|
+
lastFailedLogin: null,
|
|
26
|
+
lockedUntil: null,
|
|
27
|
+
termsAcceptedAt: /* @__PURE__ */ new Date("2025-01-01"),
|
|
28
|
+
privacyAcceptedAt: /* @__PURE__ */ new Date("2025-01-01"),
|
|
29
|
+
marketingOptIn: false,
|
|
30
|
+
marketingOptInAt: null,
|
|
31
|
+
createdAt: /* @__PURE__ */ new Date("2025-01-01T00:00:00.000Z"),
|
|
32
|
+
updatedAt: /* @__PURE__ */ new Date("2025-01-01T00:00:00.000Z")
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function createMockSession(overrides = {}) {
|
|
36
|
+
return {
|
|
37
|
+
userId: overrides.userId ?? "user-test-id",
|
|
38
|
+
role: overrides.role ?? "user",
|
|
39
|
+
email: "test@example.com",
|
|
40
|
+
name: "Test User"
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function createMockModel() {
|
|
44
|
+
return {
|
|
45
|
+
findUnique: vi.fn(),
|
|
46
|
+
findFirst: vi.fn(),
|
|
47
|
+
findMany: vi.fn(),
|
|
48
|
+
create: vi.fn(),
|
|
49
|
+
update: vi.fn(),
|
|
50
|
+
delete: vi.fn(),
|
|
51
|
+
deleteMany: vi.fn(),
|
|
52
|
+
count: vi.fn(),
|
|
53
|
+
aggregate: vi.fn(),
|
|
54
|
+
upsert: vi.fn()
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
var mockPrisma = {
|
|
58
|
+
user: createMockModel(),
|
|
59
|
+
account: createMockModel(),
|
|
60
|
+
session: createMockModel(),
|
|
61
|
+
verificationToken: createMockModel(),
|
|
62
|
+
$transaction: vi.fn((fn) => fn(mockPrisma)),
|
|
63
|
+
$connect: vi.fn(),
|
|
64
|
+
$disconnect: vi.fn()
|
|
65
|
+
};
|
|
66
|
+
function resetMockPrisma() {
|
|
67
|
+
Object.values(mockPrisma).forEach((model) => {
|
|
68
|
+
if (typeof model === "object" && model !== null) {
|
|
69
|
+
Object.values(model).forEach((method) => {
|
|
70
|
+
if (typeof method === "function" && "mockReset" in method) {
|
|
71
|
+
method.mockReset();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
vi.mock("@mars-stack/core/database", () => ({
|
|
78
|
+
prisma: mockPrisma
|
|
79
|
+
}));
|
|
80
|
+
function mockAuth(options = {}) {
|
|
81
|
+
const session = createMockSession({
|
|
82
|
+
userId: options.userId,
|
|
83
|
+
role: options.role
|
|
84
|
+
});
|
|
85
|
+
vi.doMock("@mars-stack/core/auth/session", () => ({
|
|
86
|
+
getSession: vi.fn().mockResolvedValue(session),
|
|
87
|
+
verifySession: vi.fn().mockResolvedValue(session)
|
|
88
|
+
}));
|
|
89
|
+
return session;
|
|
90
|
+
}
|
|
91
|
+
function mockAuthAsUser(userId = "user-test-id") {
|
|
92
|
+
return mockAuth({ userId, role: "user" });
|
|
93
|
+
}
|
|
94
|
+
function mockAuthAsAdmin(userId = "admin-test-id") {
|
|
95
|
+
return mockAuth({ userId, role: "admin" });
|
|
96
|
+
}
|
|
97
|
+
function createTestRequest(method, path) {
|
|
98
|
+
return new NextRequest(new URL(path, "http://localhost:3000"), {
|
|
99
|
+
method,
|
|
100
|
+
headers: {
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
"x-csrf-token": "test-csrf-token"
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function createTestRequestWithBody(method, path, body) {
|
|
107
|
+
return new NextRequest(new URL(path, "http://localhost:3000"), {
|
|
108
|
+
method,
|
|
109
|
+
headers: {
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
"x-csrf-token": "test-csrf-token"
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(body)
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export { createMockSession, createMockUser, createTestRequest, createTestRequestWithBody, mockAuth, mockAuthAsAdmin, mockAuthAsUser, mockPrisma, renderWithProviders, resetMockPrisma };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface MockAuthOptions {
|
|
2
|
+
userId?: string;
|
|
3
|
+
role?: string;
|
|
4
|
+
email?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function mockAuth(options?: MockAuthOptions): {
|
|
7
|
+
userId: string;
|
|
8
|
+
role: string;
|
|
9
|
+
email: string;
|
|
10
|
+
name: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function mockAuthAsUser(userId?: string): {
|
|
13
|
+
userId: string;
|
|
14
|
+
role: string;
|
|
15
|
+
email: string;
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function mockAuthAsAdmin(userId?: string): {
|
|
19
|
+
userId: string;
|
|
20
|
+
role: string;
|
|
21
|
+
email: string;
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=mock-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-auth.d.ts","sourceRoot":"","sources":["../../src/test-utils/mock-auth.ts"],"names":[],"mappings":"AAGA,UAAU,eAAe;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB;;;;;EAYrD;AAED,wBAAgB,cAAc,CAAC,MAAM,SAAiB;;;;;EAErD;AAED,wBAAgB,eAAe,CAAC,MAAM,SAAkB;;;;;EAEvD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export declare const mockPrisma: {
|
|
2
|
+
user: {
|
|
3
|
+
findUnique: import("vitest").Mock<(...args: any[]) => any>;
|
|
4
|
+
findFirst: import("vitest").Mock<(...args: any[]) => any>;
|
|
5
|
+
findMany: import("vitest").Mock<(...args: any[]) => any>;
|
|
6
|
+
create: import("vitest").Mock<(...args: any[]) => any>;
|
|
7
|
+
update: import("vitest").Mock<(...args: any[]) => any>;
|
|
8
|
+
delete: import("vitest").Mock<(...args: any[]) => any>;
|
|
9
|
+
deleteMany: import("vitest").Mock<(...args: any[]) => any>;
|
|
10
|
+
count: import("vitest").Mock<(...args: any[]) => any>;
|
|
11
|
+
aggregate: import("vitest").Mock<(...args: any[]) => any>;
|
|
12
|
+
upsert: import("vitest").Mock<(...args: any[]) => any>;
|
|
13
|
+
};
|
|
14
|
+
account: {
|
|
15
|
+
findUnique: import("vitest").Mock<(...args: any[]) => any>;
|
|
16
|
+
findFirst: import("vitest").Mock<(...args: any[]) => any>;
|
|
17
|
+
findMany: import("vitest").Mock<(...args: any[]) => any>;
|
|
18
|
+
create: import("vitest").Mock<(...args: any[]) => any>;
|
|
19
|
+
update: import("vitest").Mock<(...args: any[]) => any>;
|
|
20
|
+
delete: import("vitest").Mock<(...args: any[]) => any>;
|
|
21
|
+
deleteMany: import("vitest").Mock<(...args: any[]) => any>;
|
|
22
|
+
count: import("vitest").Mock<(...args: any[]) => any>;
|
|
23
|
+
aggregate: import("vitest").Mock<(...args: any[]) => any>;
|
|
24
|
+
upsert: import("vitest").Mock<(...args: any[]) => any>;
|
|
25
|
+
};
|
|
26
|
+
session: {
|
|
27
|
+
findUnique: import("vitest").Mock<(...args: any[]) => any>;
|
|
28
|
+
findFirst: import("vitest").Mock<(...args: any[]) => any>;
|
|
29
|
+
findMany: import("vitest").Mock<(...args: any[]) => any>;
|
|
30
|
+
create: import("vitest").Mock<(...args: any[]) => any>;
|
|
31
|
+
update: import("vitest").Mock<(...args: any[]) => any>;
|
|
32
|
+
delete: import("vitest").Mock<(...args: any[]) => any>;
|
|
33
|
+
deleteMany: import("vitest").Mock<(...args: any[]) => any>;
|
|
34
|
+
count: import("vitest").Mock<(...args: any[]) => any>;
|
|
35
|
+
aggregate: import("vitest").Mock<(...args: any[]) => any>;
|
|
36
|
+
upsert: import("vitest").Mock<(...args: any[]) => any>;
|
|
37
|
+
};
|
|
38
|
+
verificationToken: {
|
|
39
|
+
findUnique: import("vitest").Mock<(...args: any[]) => any>;
|
|
40
|
+
findFirst: import("vitest").Mock<(...args: any[]) => any>;
|
|
41
|
+
findMany: import("vitest").Mock<(...args: any[]) => any>;
|
|
42
|
+
create: import("vitest").Mock<(...args: any[]) => any>;
|
|
43
|
+
update: import("vitest").Mock<(...args: any[]) => any>;
|
|
44
|
+
delete: import("vitest").Mock<(...args: any[]) => any>;
|
|
45
|
+
deleteMany: import("vitest").Mock<(...args: any[]) => any>;
|
|
46
|
+
count: import("vitest").Mock<(...args: any[]) => any>;
|
|
47
|
+
aggregate: import("vitest").Mock<(...args: any[]) => any>;
|
|
48
|
+
upsert: import("vitest").Mock<(...args: any[]) => any>;
|
|
49
|
+
};
|
|
50
|
+
$transaction: import("vitest").Mock<(fn: (tx: unknown) => unknown) => unknown>;
|
|
51
|
+
$connect: import("vitest").Mock<(...args: any[]) => any>;
|
|
52
|
+
$disconnect: import("vitest").Mock<(...args: any[]) => any>;
|
|
53
|
+
};
|
|
54
|
+
export declare function resetMockPrisma(): void;
|
|
55
|
+
//# sourceMappingURL=mock-prisma.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-prisma.d.ts","sourceRoot":"","sources":["../../src/test-utils/mock-prisma.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6CAKI,CAAC,EAAE,EAAE,OAAO,KAAK,OAAO;;;CAGlD,CAAC;AAEF,wBAAgB,eAAe,SAU9B"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type RenderOptions } from '@testing-library/react';
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
export declare function renderWithProviders(ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>): import("@testing-library/react").RenderResult<typeof import("@testing-library/dom/types/queries"), HTMLElement, HTMLElement>;
|
|
4
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/test-utils/render.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,OAAO,CAAC;AAUrD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,gIAE7F"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
3
|
+
export declare function createTestRequest(method: HttpMethod, path: string): NextRequest;
|
|
4
|
+
export declare function createTestRequestWithBody(method: HttpMethod, path: string, body: Record<string, unknown>): NextRequest;
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=request-helpers.d.ts.map
|