@devvistatech/devvista-kit 0.0.10 → 0.0.12
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/CHANGELOG.md +12 -12
- package/LICENSE +6 -6
- package/README.md +15 -15
- package/app/about/page.tsx +298 -298
- package/app/adRequest/page.tsx +549 -549
- package/app/analytics/page.tsx +346 -346
- package/app/api/about/route.ts +306 -306
- package/app/api/adRequest/route.ts +567 -567
- package/app/api/analytics/[reportType]/route.ts +337 -337
- package/app/api/bio/route.ts +313 -313
- package/app/api/blog/route.ts +306 -306
- package/app/api/chat/route.ts +14 -14
- package/app/api/contact/route.ts +409 -409
- package/app/api/contacts/route.ts +224 -224
- package/app/api/files/route.ts +429 -429
- package/app/api/gallery-data/route.ts +735 -735
- package/app/api/schedule/route.ts +455 -455
- package/app/api/sync-user/route.ts +131 -131
- package/app/api/trial-request/route.ts +297 -297
- package/app/blog/[id]/page.tsx +288 -288
- package/app/blog/page.tsx +216 -216
- package/app/contact/page.tsx +284 -284
- package/app/faq/page.tsx +191 -191
- package/app/gallery/page.tsx +315 -315
- package/app/globals.css +58 -58
- package/app/layout.tsx +110 -110
- package/app/not-found.tsx +20 -20
- package/app/page.tsx +338 -338
- package/app/schedule/page.tsx +660 -660
- package/bin/init.js +219 -219
- package/components/addOns/functional/BioEditor.tsx +446 -446
- package/components/addOns/functional/CalendlyWidget.tsx +107 -107
- package/components/addOns/functional/ClassList.tsx +145 -145
- package/components/addOns/functional/ClassPopup.tsx +398 -398
- package/components/addOns/functional/ContactForm.tsx +284 -284
- package/components/addOns/functional/FileUploader.tsx +294 -294
- package/components/addOns/functional/ImageDescCarousel.tsx +730 -730
- package/components/addOns/functional/NewUserAnalytics.tsx +100 -100
- package/components/addOns/functional/ScheduleCarousel.tsx +171 -171
- package/components/addOns/functional/aboutSections/AboutSection.tsx +544 -544
- package/components/addOns/functional/aboutSections/constants/aboutSection.ts +65 -65
- package/components/addOns/functional/blogSections/BlogDashboard.tsx +184 -184
- package/components/addOns/functional/blogSections/BlogFormPopUp.tsx +554 -554
- package/components/addOns/functional/blogSections/BlogList.tsx +148 -148
- package/components/addOns/functional/blogSections/BlogSidebar.tsx +58 -58
- package/components/addOns/functional/blogSections/constants/blogDashboard.ts +28 -28
- package/components/addOns/functional/blogSections/constants/blogFormPopUp.ts +97 -97
- package/components/addOns/functional/blogSections/constants/blogList.ts +22 -22
- package/components/addOns/functional/blogSections/constants/blogSidebar.ts +15 -15
- package/components/addOns/functional/contactsDashboard/ContactsDashboard.tsx +366 -366
- package/components/addOns/functional/contactsDashboard/constants/contactsDashboard.ts +70 -70
- package/components/addOns/functional/galleries/GalleryComplex.tsx +836 -836
- package/components/addOns/functional/galleries/GallerySimple.tsx +509 -509
- package/components/addOns/functional/galleries/constants/galleryComplex.ts +106 -106
- package/components/addOns/functional/galleries/constants/gallerySimple.ts +76 -76
- package/components/addOns/functional/schedules/ScheduleGridOne.tsx +262 -262
- package/components/addOns/functional/schedules/ScheduleGridTwo.tsx +294 -294
- package/components/addOns/functional/schedules/ScheduleGridTwoBasic.tsx +288 -288
- package/components/addOns/functional/schedules/SchedulerForm.tsx +428 -428
- package/components/addOns/functional/schedules/constants/ScheduleGridTwo.ts +40 -40
- package/components/addOns/functional/schedules/constants/ScheduleGridTwoBasic.ts +40 -40
- package/components/addOns/functional/schedules/constants/SchedulerForm.ts +65 -65
- package/components/addOns/functional/schedules/constants/scheduleGridOne.ts +54 -54
- package/components/addOns/non-functional/AnnouncementBanner.tsx +46 -46
- package/components/addOns/non-functional/FeaturesSection.tsx +62 -62
- package/components/addOns/non-functional/Heros/HeroSection.tsx +142 -142
- package/components/addOns/non-functional/IconBubble.tsx +49 -49
- package/components/addOns/non-functional/SampleCarousel.tsx +204 -204
- package/components/addOns/non-functional/Testimonials.tsx +334 -334
- package/components/addOns/non-functional/ThreeSetGallery.tsx +63 -63
- package/components/addOns/non-functional/aboutSections/AboutSection.tsx +62 -62
- package/components/addOns/non-functional/aboutSections/constants/aboutSection.ts +24 -24
- package/components/addOns/non-functional/imageCarousels/ProductSlider.tsx +117 -117
- package/components/addOns/non-functional/imageCarousels/ProgramCarousel.tsx +232 -232
- package/components/addOns/non-functional/imageCarousels/constants/programCarousel.ts +39 -39
- package/components/addOns/non-functional/imageCarousels/constants/programSlider.ts +36 -36
- package/components/addOns/non-functional/spinner.tsx +21 -21
- package/components/footers/footer.tsx +453 -453
- package/components/navBars/navbar.tsx +310 -310
- package/components/other/accordion.tsx +58 -58
- package/components/other/admin-menu.tsx +68 -68
- package/components/other/alert-dialog.tsx +141 -141
- package/components/other/alert.tsx +59 -59
- package/components/other/aspect-ratio.tsx +7 -7
- package/components/other/avatar.tsx +50 -50
- package/components/other/badge.tsx +36 -36
- package/components/other/breadcrumb.tsx +115 -115
- package/components/other/button.tsx +738 -738
- package/components/other/calendar.tsx +66 -66
- package/components/other/card.tsx +86 -86
- package/components/other/carousel.tsx +274 -274
- package/components/other/chart.tsx +363 -363
- package/components/other/checkbox.tsx +30 -30
- package/components/other/collapsible.tsx +11 -11
- package/components/other/command.tsx +155 -155
- package/components/other/context-menu.tsx +200 -200
- package/components/other/dialog.tsx +122 -122
- package/components/other/drawer.tsx +118 -118
- package/components/other/dropdown-menu.tsx +200 -200
- package/components/other/form.tsx +179 -179
- package/components/other/hover-card.tsx +29 -29
- package/components/other/input-otp.tsx +71 -71
- package/components/other/input.tsx +25 -25
- package/components/other/label.tsx +26 -26
- package/components/other/menubar.tsx +236 -236
- package/components/other/mobile-icon.tsx +21 -21
- package/components/other/navigation-menu.tsx +128 -128
- package/components/other/pagination.tsx +117 -117
- package/components/other/popover.tsx +31 -31
- package/components/other/progress.tsx +28 -28
- package/components/other/radio-group.tsx +44 -44
- package/components/other/resizable.tsx +45 -45
- package/components/other/scroll-area.tsx +48 -48
- package/components/other/select.tsx +160 -160
- package/components/other/separator.tsx +31 -31
- package/components/other/sheet.tsx +140 -140
- package/components/other/skeleton.tsx +15 -15
- package/components/other/slider.tsx +28 -28
- package/components/other/social-icons.tsx +39 -39
- package/components/other/sonner.tsx +31 -31
- package/components/other/switch.tsx +29 -29
- package/components/other/table.tsx +117 -117
- package/components/other/tabs.tsx +55 -55
- package/components/other/textarea.tsx +24 -24
- package/components/other/toast.tsx +122 -122
- package/components/other/toaster.tsx +35 -35
- package/components/other/toggle-group.tsx +61 -61
- package/components/other/toggle.tsx +45 -45
- package/components/other/tooltip.tsx +30 -30
- package/components/theme-provider.tsx +8 -8
- package/components/types.ts +49 -49
- package/hooks/use-toast.ts +188 -188
- package/lib/auth-context.tsx +130 -130
- package/lib/constants/about.ts +34 -34
- package/lib/constants/adRequest.ts +113 -113
- package/lib/constants/contact.ts +40 -40
- package/lib/constants/faq.ts +34 -34
- package/lib/constants/gallery.ts +42 -42
- package/lib/constants/page.ts +69 -69
- package/lib/constants/schedule.ts +71 -71
- package/lib/google-analytics.tsx +97 -97
- package/lib/verify-user.ts +117 -117
- package/middleware.ts +42 -42
- package/netlify.toml +5 -5
- package/next.config.js +10 -10
- package/package.json +115 -115
- package/tailwind.config.ts +89 -89
- package/tsconfig.json +23 -23
- package/dist/.next/types/app/api/about/route.js +0 -52
- package/dist/.next/types/app/api/blog/route.js +0 -52
- package/dist/.next/types/app/api/files/route.js +0 -52
- package/dist/.next/types/app/api/schedule/route.js +0 -52
- package/dist/.next/types/app/api/sync-user/route.js +0 -52
- package/dist/.next/types/app/layout.js +0 -22
- package/dist/.next/types/app/page.js +0 -22
- package/dist/app/about/page.jsx +0 -258
- package/dist/app/adRequest/page.jsx +0 -531
- package/dist/app/analytics/page.jsx +0 -298
- package/dist/app/api/about/route.js +0 -285
- package/dist/app/api/adRequest/route.js +0 -440
- package/dist/app/api/analytics/[reportType]/route.js +0 -357
- package/dist/app/api/bio/route.js +0 -293
- package/dist/app/api/blog/route.js +0 -366
- package/dist/app/api/chat/route.js +0 -58
- package/dist/app/api/contact/route.js +0 -163
- package/dist/app/api/contacts/route.js +0 -234
- package/dist/app/api/files/route.js +0 -444
- package/dist/app/api/gallery-data/route.js +0 -719
- package/dist/app/api/schedule/route.js +0 -461
- package/dist/app/api/sync-user/route.js +0 -186
- package/dist/app/api/trial-request/route.js +0 -165
- package/dist/app/blog/[id]/page.jsx +0 -312
- package/dist/app/blog/page.jsx +0 -210
- package/dist/app/constants/about.js +0 -32
- package/dist/app/constants/adRequest.js +0 -113
- package/dist/app/constants/contact.js +0 -40
- package/dist/app/constants/faq.js +0 -36
- package/dist/app/constants/gallery.js +0 -42
- package/dist/app/constants/page.js +0 -69
- package/dist/app/constants/schedule.js +0 -71
- package/dist/app/contact/page.jsx +0 -119
- package/dist/app/faq/page.jsx +0 -97
- package/dist/app/gallery/page.jsx +0 -281
- package/dist/app/layout.jsx +0 -45
- package/dist/app/not-found.jsx +0 -14
- package/dist/app/page.jsx +0 -324
- package/dist/app/schedule/page.jsx +0 -500
- package/dist/components/addOns/functional/BioEditor.jsx +0 -187
- package/dist/components/addOns/functional/CalendlyWidget.jsx +0 -61
- package/dist/components/addOns/functional/ClassList.jsx +0 -158
- package/dist/components/addOns/functional/ClassPopup.jsx +0 -300
- package/dist/components/addOns/functional/ContactForm.jsx +0 -219
- package/dist/components/addOns/functional/FileUploader.jsx +0 -222
- package/dist/components/addOns/functional/ImageDescCarousel.jsx +0 -491
- package/dist/components/addOns/functional/NewUserAnalytics.jsx +0 -71
- package/dist/components/addOns/functional/ScheduleCarousel.jsx +0 -68
- package/dist/components/addOns/functional/aboutSections/AboutSection.jsx +0 -372
- package/dist/components/addOns/functional/aboutSections/constants/aboutSection.js +0 -65
- package/dist/components/addOns/functional/blogSections/BlogDashboard.jsx +0 -111
- package/dist/components/addOns/functional/blogSections/BlogFormPopUp.jsx +0 -465
- package/dist/components/addOns/functional/blogSections/BlogList.jsx +0 -170
- package/dist/components/addOns/functional/blogSections/BlogSidebar.jsx +0 -35
- package/dist/components/addOns/functional/blogSections/constants/blogDashboard.js +0 -28
- package/dist/components/addOns/functional/blogSections/constants/blogFormPopUp.js +0 -97
- package/dist/components/addOns/functional/blogSections/constants/blogList.js +0 -22
- package/dist/components/addOns/functional/blogSections/constants/blogSidebar.js +0 -15
- package/dist/components/addOns/functional/contactsDashboard/ContactsDashboard.jsx +0 -355
- package/dist/components/addOns/functional/contactsDashboard/constants/contactsDashboard.js +0 -70
- package/dist/components/addOns/functional/galleries/GalleryComplex.jsx +0 -605
- package/dist/components/addOns/functional/galleries/GallerySimple.jsx +0 -363
- package/dist/components/addOns/functional/galleries/constants/galleryComplex.js +0 -106
- package/dist/components/addOns/functional/galleries/constants/gallerySimple.js +0 -76
- package/dist/components/addOns/functional/schedules/ScheduleGridOne.jsx +0 -167
- package/dist/components/addOns/functional/schedules/ScheduleGridTwo.jsx +0 -100
- package/dist/components/addOns/functional/schedules/ScheduleGridTwoBasic.jsx +0 -97
- package/dist/components/addOns/functional/schedules/SchedulerForm.jsx +0 -188
- package/dist/components/addOns/functional/schedules/constants/ScheduleGridTwo.js +0 -40
- package/dist/components/addOns/functional/schedules/constants/ScheduleGridTwoBasic.js +0 -40
- package/dist/components/addOns/functional/schedules/constants/SchedulerForm.js +0 -65
- package/dist/components/addOns/functional/schedules/constants/scheduleGridOne.js +0 -54
- package/dist/components/addOns/non-functional/AnnouncementBanner.jsx +0 -24
- package/dist/components/addOns/non-functional/FeaturesSection.jsx +0 -38
- package/dist/components/addOns/non-functional/HeroSection.jsx +0 -71
- package/dist/components/addOns/non-functional/Heros/HeroSection.jsx +0 -71
- package/dist/components/addOns/non-functional/IconBubble.jsx +0 -36
- package/dist/components/addOns/non-functional/SampleCarousel.jsx +0 -114
- package/dist/components/addOns/non-functional/Testimonials.jsx +0 -177
- package/dist/components/addOns/non-functional/ThreeSetGallery.jsx +0 -40
- package/dist/components/addOns/non-functional/aboutSections/AboutSection.jsx +0 -35
- package/dist/components/addOns/non-functional/aboutSections/constants/aboutSection.js +0 -24
- package/dist/components/addOns/non-functional/imageCarousels/ProductSlider.jsx +0 -80
- package/dist/components/addOns/non-functional/imageCarousels/ProgramCarousel.jsx +0 -155
- package/dist/components/addOns/non-functional/imageCarousels/constants/programCarousel.js +0 -39
- package/dist/components/addOns/non-functional/imageCarousels/constants/programSlider.js +0 -36
- package/dist/components/addOns/non-functional/spinner.jsx +0 -13
- package/dist/components/footers/footer.jsx +0 -217
- package/dist/components/navBars/navbar.jsx +0 -159
- package/dist/components/other/accordion.jsx +0 -40
- package/dist/components/other/admin-menu.jsx +0 -34
- package/dist/components/other/alert-dialog.jsx +0 -64
- package/dist/components/other/alert.jsx +0 -41
- package/dist/components/other/aspect-ratio.jsx +0 -4
- package/dist/components/other/avatar.jsx +0 -31
- package/dist/components/other/badge.jsx +0 -32
- package/dist/components/other/breadcrumb.jsx +0 -57
- package/dist/components/other/button.jsx +0 -322
- package/dist/components/other/calendar.jsx +0 -43
- package/dist/components/other/card.jsx +0 -44
- package/dist/components/other/carousel.jsx +0 -140
- package/dist/components/other/chart.jsx +0 -182
- package/dist/components/other/checkbox.jsx +0 -26
- package/dist/components/other/collapsible.jsx +0 -6
- package/dist/components/other/command.jsx +0 -68
- package/dist/components/other/context-menu.jsx +0 -88
- package/dist/components/other/dialog.jsx +0 -60
- package/dist/components/other/drawer.jsx +0 -60
- package/dist/components/other/dropdown-menu.jsx +0 -90
- package/dist/components/other/form.jsx +0 -89
- package/dist/components/other/hover-card.jsx +0 -23
- package/dist/components/other/input-otp.jsx +0 -46
- package/dist/components/other/input.jsx +0 -19
- package/dist/components/other/label.jsx +0 -23
- package/dist/components/other/login-popup.jsx +0 -1
- package/dist/components/other/menubar.jsx +0 -96
- package/dist/components/other/mobile-icon.jsx +0 -11
- package/dist/components/other/navigation-menu.jsx +0 -62
- package/dist/components/other/pagination.jsx +0 -63
- package/dist/components/other/popover.jsx +0 -25
- package/dist/components/other/progress.jsx +0 -23
- package/dist/components/other/radio-group.jsx +0 -31
- package/dist/components/other/resizable.jsx +0 -29
- package/dist/components/other/scroll-area.jsx +0 -36
- package/dist/components/other/select.jsx +0 -83
- package/dist/components/other/separator.jsx +0 -21
- package/dist/components/other/sheet.jsx +0 -74
- package/dist/components/other/signup-popup.jsx +0 -1
- package/dist/components/other/skeleton.jsx +0 -17
- package/dist/components/other/slider.jsx +0 -26
- package/dist/components/other/social-icons.jsx +0 -15
- package/dist/components/other/sonner.jsx +0 -27
- package/dist/components/other/switch.jsx +0 -23
- package/dist/components/other/table.jsx +0 -56
- package/dist/components/other/tabs.jsx +0 -32
- package/dist/components/other/textarea.jsx +0 -19
- package/dist/components/other/toast.jsx +0 -58
- package/dist/components/other/toaster.jsx +0 -31
- package/dist/components/other/toggle-group.jsx +0 -41
- package/dist/components/other/toggle.jsx +0 -39
- package/dist/components/other/tooltip.jsx +0 -24
- package/dist/components/theme-provider.jsx +0 -18
- package/dist/components/types.js +0 -1
- package/dist/hooks/use-toast.js +0 -135
- package/dist/lib/auth-context.jsx +0 -144
- package/dist/lib/constants/about.js +0 -32
- package/dist/lib/constants/adRequest.js +0 -113
- package/dist/lib/constants/contact.js +0 -40
- package/dist/lib/constants/faq.js +0 -36
- package/dist/lib/constants/gallery.js +0 -42
- package/dist/lib/constants/page.js +0 -69
- package/dist/lib/constants/schedule.js +0 -71
- package/dist/lib/google-analytics.jsx +0 -148
- package/dist/lib/utils.js +0 -9
- package/dist/lib/verify-user.js +0 -142
- package/dist/middleware.js +0 -37
- package/dist/tailwind.config.js +0 -86
- package/dist/tsconfig.tsbuildinfo +0 -1
package/app/api/files/route.ts
CHANGED
|
@@ -1,430 +1,430 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
-
import { getAuth } from "@clerk/nextjs/server";
|
|
3
|
-
|
|
4
|
-
export const revalidate = 0; // Disable Next.js ISR
|
|
5
|
-
|
|
6
|
-
interface StrapiUser {
|
|
7
|
-
id: number;
|
|
8
|
-
username: string;
|
|
9
|
-
email: string;
|
|
10
|
-
businessAdminId?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface UploadedFile {
|
|
14
|
-
id: number;
|
|
15
|
-
documentId: string;
|
|
16
|
-
name: string;
|
|
17
|
-
url: string;
|
|
18
|
-
createdAt: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const DOC_API_URL = process.env.STRAPI_DOC_API_URL || "";
|
|
22
|
-
const BASE_URL = process.env.STRAPI_API_URL || "";
|
|
23
|
-
const UPLOAD_API_URL = `${BASE_URL}/api/upload`;
|
|
24
|
-
const STRAPI_API_TOKEN = process.env.STRAPI_API_TOKEN || "";
|
|
25
|
-
const STRAPI_USER_LIST_API_URL = process.env.STRAPI_USER_LIST_API_URL || "";
|
|
26
|
-
|
|
27
|
-
async function verifyUser(userId: string): Promise<StrapiUser | null> {
|
|
28
|
-
try {
|
|
29
|
-
const response = await fetch(
|
|
30
|
-
`${STRAPI_USER_LIST_API_URL}?filters[authId][$eq]=${userId}`,
|
|
31
|
-
{
|
|
32
|
-
headers: {
|
|
33
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
34
|
-
"Content-Type": "application/json",
|
|
35
|
-
},
|
|
36
|
-
cache: "no-store",
|
|
37
|
-
}
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
if (!response.ok) {
|
|
41
|
-
const errorText = await response.text();
|
|
42
|
-
console.error("verifyUser failed:", errorText);
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const userData = await response.json();
|
|
47
|
-
const strapiUser = userData.data?.find((user: any) => user.authId === userId);
|
|
48
|
-
if (!strapiUser) {
|
|
49
|
-
console.error("verifyUser: No user found for authId", { userId });
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
id: strapiUser.id,
|
|
55
|
-
username: strapiUser.username,
|
|
56
|
-
email: strapiUser.email,
|
|
57
|
-
businessAdminId: strapiUser.businessAdminId,
|
|
58
|
-
};
|
|
59
|
-
} catch (error) {
|
|
60
|
-
console.error("verifyUser error:", error);
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function GET() {
|
|
66
|
-
try {
|
|
67
|
-
if (!DOC_API_URL || !STRAPI_API_TOKEN) {
|
|
68
|
-
console.error(
|
|
69
|
-
"Missing environment variables:",
|
|
70
|
-
!DOC_API_URL && "STRAPI_DOC_API_URL",
|
|
71
|
-
!STRAPI_API_TOKEN && "STRAPI_API_TOKEN"
|
|
72
|
-
);
|
|
73
|
-
return NextResponse.json(
|
|
74
|
-
{ error: "Server configuration error: Missing required environment variables" },
|
|
75
|
-
{ status: 500 }
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const response = await fetch(
|
|
80
|
-
`${DOC_API_URL}?publicationState=live&populate=uploadedDoc&fields[0]=createdAt&pagination[pageSize]=100&t=${Date.now()}`,
|
|
81
|
-
{
|
|
82
|
-
headers: {
|
|
83
|
-
"Content-Type": "application/json",
|
|
84
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
85
|
-
},
|
|
86
|
-
cache: "no-store",
|
|
87
|
-
}
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
if (!response.ok) {
|
|
91
|
-
const errorText = await response.text();
|
|
92
|
-
console.error("GET /api/files: Fetch files failed:", errorText);
|
|
93
|
-
return NextResponse.json(
|
|
94
|
-
{ error: `Failed to fetch files: ${errorText}` },
|
|
95
|
-
{ status: response.status }
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const result = await response.json();
|
|
100
|
-
|
|
101
|
-
const files: UploadedFile[] = (result.data || [])
|
|
102
|
-
.map((item: any) => {
|
|
103
|
-
const uploadedDoc = item.uploadedDoc;
|
|
104
|
-
if (!uploadedDoc || !uploadedDoc.url) {
|
|
105
|
-
console.warn(`Fitness plan missing for item ${item.id}`);
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
const rawUrl = uploadedDoc.url || "";
|
|
109
|
-
const fileUrl = rawUrl.startsWith("http") ? rawUrl : `${BASE_URL}${rawUrl}`;
|
|
110
|
-
return {
|
|
111
|
-
id: item.id,
|
|
112
|
-
documentId: item.documentId || `doc-${item.id}`,
|
|
113
|
-
name: uploadedDoc.name || "Unnamed File",
|
|
114
|
-
url: fileUrl,
|
|
115
|
-
createdAt: item.createdAt,
|
|
116
|
-
};
|
|
117
|
-
})
|
|
118
|
-
.filter((file: UploadedFile | null): file is UploadedFile => file !== null)
|
|
119
|
-
.sort((a: UploadedFile, b: UploadedFile) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
120
|
-
|
|
121
|
-
return NextResponse.json({
|
|
122
|
-
data: files,
|
|
123
|
-
meta: result.meta,
|
|
124
|
-
});
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.error("GET /api/files: Error:", error);
|
|
127
|
-
return NextResponse.json(
|
|
128
|
-
{ error: error instanceof Error ? error.message : "An error occurred while fetching files" },
|
|
129
|
-
{ status: 500 }
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export async function POST(request: NextRequest) {
|
|
135
|
-
const { userId } = getAuth(request);
|
|
136
|
-
if (!userId) {
|
|
137
|
-
console.error("POST /api/files: No userId, returning 401");
|
|
138
|
-
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const user = await verifyUser(userId);
|
|
142
|
-
|
|
143
|
-
if (!user || !user.businessAdminId) {
|
|
144
|
-
console.error("POST /api/files: Unauthorized", { user });
|
|
145
|
-
return NextResponse.json({ error: "Unauthorized: Admin access required" }, { status: 403 });
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
if (!DOC_API_URL || !STRAPI_API_TOKEN) {
|
|
150
|
-
console.error(
|
|
151
|
-
"Missing environment variables:",
|
|
152
|
-
!DOC_API_URL && "STRAPI_DOC_API_URL",
|
|
153
|
-
!STRAPI_API_TOKEN && "STRAPI_API_TOKEN"
|
|
154
|
-
);
|
|
155
|
-
return NextResponse.json(
|
|
156
|
-
{ error: "Server configuration error: Missing required environment variables" },
|
|
157
|
-
{ status: 500 }
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const formData = await request.formData();
|
|
162
|
-
const file = formData.get("files") as File | null;
|
|
163
|
-
|
|
164
|
-
if (!file) {
|
|
165
|
-
console.error("POST /api/files: No file provided");
|
|
166
|
-
return NextResponse.json({ error: "No file provided" }, { status: 400 });
|
|
167
|
-
}
|
|
168
|
-
if (file.type !== "application/pdf") {
|
|
169
|
-
console.error("POST /api/files: Invalid file type", { fileType: file.type });
|
|
170
|
-
return NextResponse.json({ error: "Only PDF files are allowed" }, { status: 400 });
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const uploadFormData = new FormData();
|
|
174
|
-
uploadFormData.append("files", file);
|
|
175
|
-
|
|
176
|
-
const uploadResponse = await fetch(UPLOAD_API_URL, {
|
|
177
|
-
method: "POST",
|
|
178
|
-
headers: {
|
|
179
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
180
|
-
},
|
|
181
|
-
body: uploadFormData,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
if (!uploadResponse.ok) {
|
|
185
|
-
const errorText = await uploadResponse.text();
|
|
186
|
-
console.error("POST /api/files: Upload failed:", errorText);
|
|
187
|
-
return NextResponse.json(
|
|
188
|
-
{ error: `Failed to upload file: ${errorText}` },
|
|
189
|
-
{ status: uploadResponse.status }
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const uploadResult = await uploadResponse.json();
|
|
194
|
-
const uploadedFileId = uploadResult[0]?.id;
|
|
195
|
-
const uploadedFileUrl = uploadResult[0]?.url;
|
|
196
|
-
const uploadedFileName = uploadResult[0]?.name || file.name;
|
|
197
|
-
|
|
198
|
-
if (!uploadedFileId || !uploadedFileUrl) {
|
|
199
|
-
console.error("POST /api/files: Invalid upload response", { uploadResult });
|
|
200
|
-
return NextResponse.json({ error: "Invalid upload response" }, { status: 400 });
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const payload = {
|
|
204
|
-
data: {
|
|
205
|
-
uploadedDoc: uploadedFileId,
|
|
206
|
-
publishedAt: new Date().toISOString(),
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const createResponse = await fetch(DOC_API_URL, {
|
|
211
|
-
method: "POST",
|
|
212
|
-
headers: {
|
|
213
|
-
"Content-Type": "application/json",
|
|
214
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
215
|
-
},
|
|
216
|
-
body: JSON.stringify(payload),
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
if (!createResponse.ok) {
|
|
220
|
-
const errorData = await createResponse.json();
|
|
221
|
-
let errorMessage = `Failed to create document entry: ${createResponse.status} - ${errorData.error?.message || "Unknown error"}`;
|
|
222
|
-
if (errorData.error?.details) {
|
|
223
|
-
errorMessage += `\nDetails: ${JSON.stringify(errorData.error.details, null, 2)}`;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const fallbackPayload = {
|
|
227
|
-
data: {
|
|
228
|
-
uploadedDoc: { id: uploadedFileId },
|
|
229
|
-
publishedAt: new Date().toISOString(),
|
|
230
|
-
},
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
const fallbackResponse = await fetch(DOC_API_URL, {
|
|
234
|
-
method: "POST",
|
|
235
|
-
headers: {
|
|
236
|
-
"Content-Type": "application/json",
|
|
237
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
238
|
-
},
|
|
239
|
-
body: JSON.stringify(fallbackPayload),
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
if (!fallbackResponse.ok) {
|
|
243
|
-
const fallbackErrorData = await fallbackResponse.json();
|
|
244
|
-
errorMessage += `\nFailed to create document entry (fallback): ${fallbackResponse.status} - ${fallbackErrorData.error?.message || "Unknown error"}`;
|
|
245
|
-
if (fallbackErrorData.error?.details) {
|
|
246
|
-
errorMessage += `\nDetails: ${JSON.stringify(fallbackErrorData.error.details, null, 2)}`;
|
|
247
|
-
}
|
|
248
|
-
console.error("POST /api/files: Create document failed", { errorMessage });
|
|
249
|
-
return NextResponse.json({ error: errorMessage }, { status: 500 });
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const filesResponse = await fetch(
|
|
254
|
-
`${DOC_API_URL}?publicationState=live&populate=uploadedDoc&fields[0]=createdAt&pagination[pageSize]=100&t=${Date.now()}`,
|
|
255
|
-
{
|
|
256
|
-
headers: {
|
|
257
|
-
"Content-Type": "application/json",
|
|
258
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
259
|
-
},
|
|
260
|
-
cache: "no-store",
|
|
261
|
-
}
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
if (!filesResponse.ok) {
|
|
265
|
-
const errorText = await filesResponse.text();
|
|
266
|
-
console.error("POST /api/files: Refetch files failed:", errorText);
|
|
267
|
-
return NextResponse.json(
|
|
268
|
-
{ error: `Failed to fetch files: ${errorText}` },
|
|
269
|
-
{ status: filesResponse.status }
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const filesResult = await filesResponse.json();
|
|
274
|
-
const files: UploadedFile[] = (filesResult.data || [])
|
|
275
|
-
.map((item: any) => {
|
|
276
|
-
const uploadedDoc = item.uploadedDoc;
|
|
277
|
-
if (!uploadedDoc || !uploadedDoc.url) {
|
|
278
|
-
console.warn(`Fitness plan missing for item ${item.id}`);
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
const rawUrl = uploadedDoc.url || "";
|
|
282
|
-
const fileUrl = rawUrl.startsWith("http") ? rawUrl : `${BASE_URL}${rawUrl}`;
|
|
283
|
-
return {
|
|
284
|
-
id: item.id,
|
|
285
|
-
documentId: item.documentId || `doc-${item.id}`,
|
|
286
|
-
name: uploadedDoc.name || "Unnamed File",
|
|
287
|
-
url: fileUrl,
|
|
288
|
-
createdAt: item.createdAt,
|
|
289
|
-
};
|
|
290
|
-
})
|
|
291
|
-
.filter((file: UploadedFile | null): file is UploadedFile => file !== null)
|
|
292
|
-
.sort((a: UploadedFile, b: UploadedFile) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
293
|
-
|
|
294
|
-
return NextResponse.json({
|
|
295
|
-
data: files,
|
|
296
|
-
meta: filesResult.meta,
|
|
297
|
-
newFileUrl: uploadedFileUrl,
|
|
298
|
-
});
|
|
299
|
-
} catch (error) {
|
|
300
|
-
console.error("POST /api/files: Error:", error);
|
|
301
|
-
return NextResponse.json(
|
|
302
|
-
{ error: error instanceof Error ? error.message : "An error occurred during file upload" },
|
|
303
|
-
{ status: 500 }
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export async function DELETE(request: NextRequest) {
|
|
309
|
-
const { userId } = getAuth(request);
|
|
310
|
-
if (!userId) {
|
|
311
|
-
console.error("DELETE /api/files: No userId, returning 401");
|
|
312
|
-
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const user = await verifyUser(userId);
|
|
316
|
-
|
|
317
|
-
if (!user || !user.businessAdminId) {
|
|
318
|
-
console.error("DELETE /api/files: Unauthorized", { user });
|
|
319
|
-
return NextResponse.json({ error: "Unauthorized: Admin access required" }, { status: 403 });
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
try {
|
|
323
|
-
const { documentId } = await request.json();
|
|
324
|
-
if (!documentId) {
|
|
325
|
-
console.error("DELETE /api/files: Document ID is required");
|
|
326
|
-
return NextResponse.json({ error: "Document ID is required" }, { status: 400 });
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const docResponse = await fetch(`${DOC_API_URL}/${documentId}?populate=uploadedDoc`, {
|
|
330
|
-
headers: {
|
|
331
|
-
"Content-Type": "application/json",
|
|
332
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
333
|
-
},
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
if (!docResponse.ok) {
|
|
337
|
-
const errorText = await docResponse.text();
|
|
338
|
-
console.error("DELETE /api/files: Fetch document failed:", errorText);
|
|
339
|
-
return NextResponse.json(
|
|
340
|
-
{ error: `Failed to fetch document: ${errorText}` },
|
|
341
|
-
{ status: docResponse.status }
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const docData = await docResponse.json();
|
|
346
|
-
const uploadFileId = docData.data?.uploadedDoc?.id;
|
|
347
|
-
|
|
348
|
-
const deleteDocResponse = await fetch(`${DOC_API_URL}/${documentId}`, {
|
|
349
|
-
method: "DELETE",
|
|
350
|
-
headers: {
|
|
351
|
-
"Content-Type": "application/json",
|
|
352
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
353
|
-
},
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
if (!deleteDocResponse.ok) {
|
|
357
|
-
const errorText = await deleteDocResponse.text();
|
|
358
|
-
console.error("DELETE /api/files: Failed to delete document:", errorText);
|
|
359
|
-
return NextResponse.json(
|
|
360
|
-
{ error: `Failed to delete document: ${errorText}` },
|
|
361
|
-
{ status: deleteDocResponse.status }
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (uploadFileId) {
|
|
366
|
-
const deleteFileResponse = await fetch(`${BASE_URL}/api/upload/files/${uploadFileId}`, {
|
|
367
|
-
method: "DELETE",
|
|
368
|
-
headers: {
|
|
369
|
-
"Content-Type": "application/json",
|
|
370
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
371
|
-
},
|
|
372
|
-
});
|
|
373
|
-
if (!deleteFileResponse.ok) {
|
|
374
|
-
console.warn(`DELETE /api/files: Failed to delete associated file: ${deleteFileResponse.status}`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const filesResponse = await fetch(
|
|
379
|
-
`${DOC_API_URL}?publicationState=live&populate=uploadedDoc&fields[0]=createdAt&pagination[pageSize]=100&t=${Date.now()}`,
|
|
380
|
-
{
|
|
381
|
-
headers: {
|
|
382
|
-
"Content-Type": "application/json",
|
|
383
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
384
|
-
},
|
|
385
|
-
cache: "no-store",
|
|
386
|
-
}
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
if (!filesResponse.ok) {
|
|
390
|
-
const errorText = await filesResponse.text();
|
|
391
|
-
console.error("DELETE /api/files: Refetch files failed:", errorText);
|
|
392
|
-
return NextResponse.json(
|
|
393
|
-
{ error: `Failed to fetch files: ${errorText}` },
|
|
394
|
-
{ status: filesResponse.status }
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const filesResult = await filesResponse.json();
|
|
399
|
-
const files: UploadedFile[] = (filesResult.data || [])
|
|
400
|
-
.map((item: any) => {
|
|
401
|
-
const uploadedDoc = item.uploadedDoc;
|
|
402
|
-
if (!uploadedDoc || !uploadedDoc.url) {
|
|
403
|
-
console.warn(`Fitness plan missing for item ${item.id}`);
|
|
404
|
-
return null;
|
|
405
|
-
}
|
|
406
|
-
const rawUrl = uploadedDoc.url || "";
|
|
407
|
-
const fileUrl = rawUrl.startsWith("http") ? rawUrl : `${BASE_URL}${rawUrl}`;
|
|
408
|
-
return {
|
|
409
|
-
id: item.id,
|
|
410
|
-
documentId: item.documentId || `doc-${item.id}`,
|
|
411
|
-
name: uploadedDoc.name || "Unnamed File",
|
|
412
|
-
url: fileUrl,
|
|
413
|
-
createdAt: item.createdAt,
|
|
414
|
-
};
|
|
415
|
-
})
|
|
416
|
-
.filter((file: UploadedFile | null): file is UploadedFile => file !== null)
|
|
417
|
-
.sort((a: UploadedFile, b: UploadedFile) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
418
|
-
|
|
419
|
-
return NextResponse.json({
|
|
420
|
-
data: files,
|
|
421
|
-
meta: filesResult.meta,
|
|
422
|
-
});
|
|
423
|
-
} catch (error) {
|
|
424
|
-
console.error("DELETE /api/files: Error:", error);
|
|
425
|
-
return NextResponse.json(
|
|
426
|
-
{ error: error instanceof Error ? error.message : "An error occurred while deleting file" },
|
|
427
|
-
{ status: 500 }
|
|
428
|
-
);
|
|
429
|
-
}
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getAuth } from "@clerk/nextjs/server";
|
|
3
|
+
|
|
4
|
+
export const revalidate = 0; // Disable Next.js ISR
|
|
5
|
+
|
|
6
|
+
interface StrapiUser {
|
|
7
|
+
id: number;
|
|
8
|
+
username: string;
|
|
9
|
+
email: string;
|
|
10
|
+
businessAdminId?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface UploadedFile {
|
|
14
|
+
id: number;
|
|
15
|
+
documentId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
url: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DOC_API_URL = process.env.STRAPI_DOC_API_URL || "";
|
|
22
|
+
const BASE_URL = process.env.STRAPI_API_URL || "";
|
|
23
|
+
const UPLOAD_API_URL = `${BASE_URL}/api/upload`;
|
|
24
|
+
const STRAPI_API_TOKEN = process.env.STRAPI_API_TOKEN || "";
|
|
25
|
+
const STRAPI_USER_LIST_API_URL = process.env.STRAPI_USER_LIST_API_URL || "";
|
|
26
|
+
|
|
27
|
+
async function verifyUser(userId: string): Promise<StrapiUser | null> {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(
|
|
30
|
+
`${STRAPI_USER_LIST_API_URL}?filters[authId][$eq]=${userId}`,
|
|
31
|
+
{
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
},
|
|
36
|
+
cache: "no-store",
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
const errorText = await response.text();
|
|
42
|
+
console.error("verifyUser failed:", errorText);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const userData = await response.json();
|
|
47
|
+
const strapiUser = userData.data?.find((user: any) => user.authId === userId);
|
|
48
|
+
if (!strapiUser) {
|
|
49
|
+
console.error("verifyUser: No user found for authId", { userId });
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
id: strapiUser.id,
|
|
55
|
+
username: strapiUser.username,
|
|
56
|
+
email: strapiUser.email,
|
|
57
|
+
businessAdminId: strapiUser.businessAdminId,
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error("verifyUser error:", error);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function GET() {
|
|
66
|
+
try {
|
|
67
|
+
if (!DOC_API_URL || !STRAPI_API_TOKEN) {
|
|
68
|
+
console.error(
|
|
69
|
+
"Missing environment variables:",
|
|
70
|
+
!DOC_API_URL && "STRAPI_DOC_API_URL",
|
|
71
|
+
!STRAPI_API_TOKEN && "STRAPI_API_TOKEN"
|
|
72
|
+
);
|
|
73
|
+
return NextResponse.json(
|
|
74
|
+
{ error: "Server configuration error: Missing required environment variables" },
|
|
75
|
+
{ status: 500 }
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const response = await fetch(
|
|
80
|
+
`${DOC_API_URL}?publicationState=live&populate=uploadedDoc&fields[0]=createdAt&pagination[pageSize]=100&t=${Date.now()}`,
|
|
81
|
+
{
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
85
|
+
},
|
|
86
|
+
cache: "no-store",
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
const errorText = await response.text();
|
|
92
|
+
console.error("GET /api/files: Fetch files failed:", errorText);
|
|
93
|
+
return NextResponse.json(
|
|
94
|
+
{ error: `Failed to fetch files: ${errorText}` },
|
|
95
|
+
{ status: response.status }
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const result = await response.json();
|
|
100
|
+
|
|
101
|
+
const files: UploadedFile[] = (result.data || [])
|
|
102
|
+
.map((item: any) => {
|
|
103
|
+
const uploadedDoc = item.uploadedDoc;
|
|
104
|
+
if (!uploadedDoc || !uploadedDoc.url) {
|
|
105
|
+
console.warn(`Fitness plan missing for item ${item.id}`);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const rawUrl = uploadedDoc.url || "";
|
|
109
|
+
const fileUrl = rawUrl.startsWith("http") ? rawUrl : `${BASE_URL}${rawUrl}`;
|
|
110
|
+
return {
|
|
111
|
+
id: item.id,
|
|
112
|
+
documentId: item.documentId || `doc-${item.id}`,
|
|
113
|
+
name: uploadedDoc.name || "Unnamed File",
|
|
114
|
+
url: fileUrl,
|
|
115
|
+
createdAt: item.createdAt,
|
|
116
|
+
};
|
|
117
|
+
})
|
|
118
|
+
.filter((file: UploadedFile | null): file is UploadedFile => file !== null)
|
|
119
|
+
.sort((a: UploadedFile, b: UploadedFile) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
120
|
+
|
|
121
|
+
return NextResponse.json({
|
|
122
|
+
data: files,
|
|
123
|
+
meta: result.meta,
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error("GET /api/files: Error:", error);
|
|
127
|
+
return NextResponse.json(
|
|
128
|
+
{ error: error instanceof Error ? error.message : "An error occurred while fetching files" },
|
|
129
|
+
{ status: 500 }
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function POST(request: NextRequest) {
|
|
135
|
+
const { userId } = getAuth(request);
|
|
136
|
+
if (!userId) {
|
|
137
|
+
console.error("POST /api/files: No userId, returning 401");
|
|
138
|
+
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const user = await verifyUser(userId);
|
|
142
|
+
|
|
143
|
+
if (!user || !user.businessAdminId) {
|
|
144
|
+
console.error("POST /api/files: Unauthorized", { user });
|
|
145
|
+
return NextResponse.json({ error: "Unauthorized: Admin access required" }, { status: 403 });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
if (!DOC_API_URL || !STRAPI_API_TOKEN) {
|
|
150
|
+
console.error(
|
|
151
|
+
"Missing environment variables:",
|
|
152
|
+
!DOC_API_URL && "STRAPI_DOC_API_URL",
|
|
153
|
+
!STRAPI_API_TOKEN && "STRAPI_API_TOKEN"
|
|
154
|
+
);
|
|
155
|
+
return NextResponse.json(
|
|
156
|
+
{ error: "Server configuration error: Missing required environment variables" },
|
|
157
|
+
{ status: 500 }
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const formData = await request.formData();
|
|
162
|
+
const file = formData.get("files") as File | null;
|
|
163
|
+
|
|
164
|
+
if (!file) {
|
|
165
|
+
console.error("POST /api/files: No file provided");
|
|
166
|
+
return NextResponse.json({ error: "No file provided" }, { status: 400 });
|
|
167
|
+
}
|
|
168
|
+
if (file.type !== "application/pdf") {
|
|
169
|
+
console.error("POST /api/files: Invalid file type", { fileType: file.type });
|
|
170
|
+
return NextResponse.json({ error: "Only PDF files are allowed" }, { status: 400 });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const uploadFormData = new FormData();
|
|
174
|
+
uploadFormData.append("files", file);
|
|
175
|
+
|
|
176
|
+
const uploadResponse = await fetch(UPLOAD_API_URL, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
headers: {
|
|
179
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
180
|
+
},
|
|
181
|
+
body: uploadFormData,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (!uploadResponse.ok) {
|
|
185
|
+
const errorText = await uploadResponse.text();
|
|
186
|
+
console.error("POST /api/files: Upload failed:", errorText);
|
|
187
|
+
return NextResponse.json(
|
|
188
|
+
{ error: `Failed to upload file: ${errorText}` },
|
|
189
|
+
{ status: uploadResponse.status }
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const uploadResult = await uploadResponse.json();
|
|
194
|
+
const uploadedFileId = uploadResult[0]?.id;
|
|
195
|
+
const uploadedFileUrl = uploadResult[0]?.url;
|
|
196
|
+
const uploadedFileName = uploadResult[0]?.name || file.name;
|
|
197
|
+
|
|
198
|
+
if (!uploadedFileId || !uploadedFileUrl) {
|
|
199
|
+
console.error("POST /api/files: Invalid upload response", { uploadResult });
|
|
200
|
+
return NextResponse.json({ error: "Invalid upload response" }, { status: 400 });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const payload = {
|
|
204
|
+
data: {
|
|
205
|
+
uploadedDoc: uploadedFileId,
|
|
206
|
+
publishedAt: new Date().toISOString(),
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const createResponse = await fetch(DOC_API_URL, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: {
|
|
213
|
+
"Content-Type": "application/json",
|
|
214
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
215
|
+
},
|
|
216
|
+
body: JSON.stringify(payload),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (!createResponse.ok) {
|
|
220
|
+
const errorData = await createResponse.json();
|
|
221
|
+
let errorMessage = `Failed to create document entry: ${createResponse.status} - ${errorData.error?.message || "Unknown error"}`;
|
|
222
|
+
if (errorData.error?.details) {
|
|
223
|
+
errorMessage += `\nDetails: ${JSON.stringify(errorData.error.details, null, 2)}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const fallbackPayload = {
|
|
227
|
+
data: {
|
|
228
|
+
uploadedDoc: { id: uploadedFileId },
|
|
229
|
+
publishedAt: new Date().toISOString(),
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const fallbackResponse = await fetch(DOC_API_URL, {
|
|
234
|
+
method: "POST",
|
|
235
|
+
headers: {
|
|
236
|
+
"Content-Type": "application/json",
|
|
237
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
238
|
+
},
|
|
239
|
+
body: JSON.stringify(fallbackPayload),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (!fallbackResponse.ok) {
|
|
243
|
+
const fallbackErrorData = await fallbackResponse.json();
|
|
244
|
+
errorMessage += `\nFailed to create document entry (fallback): ${fallbackResponse.status} - ${fallbackErrorData.error?.message || "Unknown error"}`;
|
|
245
|
+
if (fallbackErrorData.error?.details) {
|
|
246
|
+
errorMessage += `\nDetails: ${JSON.stringify(fallbackErrorData.error.details, null, 2)}`;
|
|
247
|
+
}
|
|
248
|
+
console.error("POST /api/files: Create document failed", { errorMessage });
|
|
249
|
+
return NextResponse.json({ error: errorMessage }, { status: 500 });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const filesResponse = await fetch(
|
|
254
|
+
`${DOC_API_URL}?publicationState=live&populate=uploadedDoc&fields[0]=createdAt&pagination[pageSize]=100&t=${Date.now()}`,
|
|
255
|
+
{
|
|
256
|
+
headers: {
|
|
257
|
+
"Content-Type": "application/json",
|
|
258
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
259
|
+
},
|
|
260
|
+
cache: "no-store",
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (!filesResponse.ok) {
|
|
265
|
+
const errorText = await filesResponse.text();
|
|
266
|
+
console.error("POST /api/files: Refetch files failed:", errorText);
|
|
267
|
+
return NextResponse.json(
|
|
268
|
+
{ error: `Failed to fetch files: ${errorText}` },
|
|
269
|
+
{ status: filesResponse.status }
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const filesResult = await filesResponse.json();
|
|
274
|
+
const files: UploadedFile[] = (filesResult.data || [])
|
|
275
|
+
.map((item: any) => {
|
|
276
|
+
const uploadedDoc = item.uploadedDoc;
|
|
277
|
+
if (!uploadedDoc || !uploadedDoc.url) {
|
|
278
|
+
console.warn(`Fitness plan missing for item ${item.id}`);
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const rawUrl = uploadedDoc.url || "";
|
|
282
|
+
const fileUrl = rawUrl.startsWith("http") ? rawUrl : `${BASE_URL}${rawUrl}`;
|
|
283
|
+
return {
|
|
284
|
+
id: item.id,
|
|
285
|
+
documentId: item.documentId || `doc-${item.id}`,
|
|
286
|
+
name: uploadedDoc.name || "Unnamed File",
|
|
287
|
+
url: fileUrl,
|
|
288
|
+
createdAt: item.createdAt,
|
|
289
|
+
};
|
|
290
|
+
})
|
|
291
|
+
.filter((file: UploadedFile | null): file is UploadedFile => file !== null)
|
|
292
|
+
.sort((a: UploadedFile, b: UploadedFile) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
293
|
+
|
|
294
|
+
return NextResponse.json({
|
|
295
|
+
data: files,
|
|
296
|
+
meta: filesResult.meta,
|
|
297
|
+
newFileUrl: uploadedFileUrl,
|
|
298
|
+
});
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.error("POST /api/files: Error:", error);
|
|
301
|
+
return NextResponse.json(
|
|
302
|
+
{ error: error instanceof Error ? error.message : "An error occurred during file upload" },
|
|
303
|
+
{ status: 500 }
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export async function DELETE(request: NextRequest) {
|
|
309
|
+
const { userId } = getAuth(request);
|
|
310
|
+
if (!userId) {
|
|
311
|
+
console.error("DELETE /api/files: No userId, returning 401");
|
|
312
|
+
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const user = await verifyUser(userId);
|
|
316
|
+
|
|
317
|
+
if (!user || !user.businessAdminId) {
|
|
318
|
+
console.error("DELETE /api/files: Unauthorized", { user });
|
|
319
|
+
return NextResponse.json({ error: "Unauthorized: Admin access required" }, { status: 403 });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const { documentId } = await request.json();
|
|
324
|
+
if (!documentId) {
|
|
325
|
+
console.error("DELETE /api/files: Document ID is required");
|
|
326
|
+
return NextResponse.json({ error: "Document ID is required" }, { status: 400 });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const docResponse = await fetch(`${DOC_API_URL}/${documentId}?populate=uploadedDoc`, {
|
|
330
|
+
headers: {
|
|
331
|
+
"Content-Type": "application/json",
|
|
332
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
if (!docResponse.ok) {
|
|
337
|
+
const errorText = await docResponse.text();
|
|
338
|
+
console.error("DELETE /api/files: Fetch document failed:", errorText);
|
|
339
|
+
return NextResponse.json(
|
|
340
|
+
{ error: `Failed to fetch document: ${errorText}` },
|
|
341
|
+
{ status: docResponse.status }
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const docData = await docResponse.json();
|
|
346
|
+
const uploadFileId = docData.data?.uploadedDoc?.id;
|
|
347
|
+
|
|
348
|
+
const deleteDocResponse = await fetch(`${DOC_API_URL}/${documentId}`, {
|
|
349
|
+
method: "DELETE",
|
|
350
|
+
headers: {
|
|
351
|
+
"Content-Type": "application/json",
|
|
352
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (!deleteDocResponse.ok) {
|
|
357
|
+
const errorText = await deleteDocResponse.text();
|
|
358
|
+
console.error("DELETE /api/files: Failed to delete document:", errorText);
|
|
359
|
+
return NextResponse.json(
|
|
360
|
+
{ error: `Failed to delete document: ${errorText}` },
|
|
361
|
+
{ status: deleteDocResponse.status }
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (uploadFileId) {
|
|
366
|
+
const deleteFileResponse = await fetch(`${BASE_URL}/api/upload/files/${uploadFileId}`, {
|
|
367
|
+
method: "DELETE",
|
|
368
|
+
headers: {
|
|
369
|
+
"Content-Type": "application/json",
|
|
370
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
if (!deleteFileResponse.ok) {
|
|
374
|
+
console.warn(`DELETE /api/files: Failed to delete associated file: ${deleteFileResponse.status}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const filesResponse = await fetch(
|
|
379
|
+
`${DOC_API_URL}?publicationState=live&populate=uploadedDoc&fields[0]=createdAt&pagination[pageSize]=100&t=${Date.now()}`,
|
|
380
|
+
{
|
|
381
|
+
headers: {
|
|
382
|
+
"Content-Type": "application/json",
|
|
383
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
384
|
+
},
|
|
385
|
+
cache: "no-store",
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
if (!filesResponse.ok) {
|
|
390
|
+
const errorText = await filesResponse.text();
|
|
391
|
+
console.error("DELETE /api/files: Refetch files failed:", errorText);
|
|
392
|
+
return NextResponse.json(
|
|
393
|
+
{ error: `Failed to fetch files: ${errorText}` },
|
|
394
|
+
{ status: filesResponse.status }
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const filesResult = await filesResponse.json();
|
|
399
|
+
const files: UploadedFile[] = (filesResult.data || [])
|
|
400
|
+
.map((item: any) => {
|
|
401
|
+
const uploadedDoc = item.uploadedDoc;
|
|
402
|
+
if (!uploadedDoc || !uploadedDoc.url) {
|
|
403
|
+
console.warn(`Fitness plan missing for item ${item.id}`);
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
const rawUrl = uploadedDoc.url || "";
|
|
407
|
+
const fileUrl = rawUrl.startsWith("http") ? rawUrl : `${BASE_URL}${rawUrl}`;
|
|
408
|
+
return {
|
|
409
|
+
id: item.id,
|
|
410
|
+
documentId: item.documentId || `doc-${item.id}`,
|
|
411
|
+
name: uploadedDoc.name || "Unnamed File",
|
|
412
|
+
url: fileUrl,
|
|
413
|
+
createdAt: item.createdAt,
|
|
414
|
+
};
|
|
415
|
+
})
|
|
416
|
+
.filter((file: UploadedFile | null): file is UploadedFile => file !== null)
|
|
417
|
+
.sort((a: UploadedFile, b: UploadedFile) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
418
|
+
|
|
419
|
+
return NextResponse.json({
|
|
420
|
+
data: files,
|
|
421
|
+
meta: filesResult.meta,
|
|
422
|
+
});
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.error("DELETE /api/files: Error:", error);
|
|
425
|
+
return NextResponse.json(
|
|
426
|
+
{ error: error instanceof Error ? error.message : "An error occurred while deleting file" },
|
|
427
|
+
{ status: 500 }
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
430
|
}
|