@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
|
@@ -1,568 +1,568 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
-
import { getAuth } from "@clerk/nextjs/server";
|
|
3
|
-
import nodemailer from "nodemailer";
|
|
4
|
-
|
|
5
|
-
interface StrapiUser {
|
|
6
|
-
id: number;
|
|
7
|
-
username: string;
|
|
8
|
-
email: string;
|
|
9
|
-
businessAdminId?: string;
|
|
10
|
-
businessTitle?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface AdRequest {
|
|
14
|
-
id: number;
|
|
15
|
-
documentId: string;
|
|
16
|
-
platform: string;
|
|
17
|
-
adType: string;
|
|
18
|
-
description: string;
|
|
19
|
-
timestamp: string;
|
|
20
|
-
userId: string;
|
|
21
|
-
credits: number;
|
|
22
|
-
lastResetDate: string;
|
|
23
|
-
nextResetDate: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const AD_REQUEST_API_URL = process.env.STRAPI_AD_REQUEST_API_URL || "";
|
|
27
|
-
const STRAPI_USER_LIST_API_URL = process.env.STRAPI_USER_LIST_API_URL || "";
|
|
28
|
-
const ADMIN_BUSINESS_ID = process.env.ADMIN_BUSINESS_ID || "";
|
|
29
|
-
const STRAPI_API_TOKEN = process.env.STRAPI_API_TOKEN || "";
|
|
30
|
-
const DEVVISTA_EMAIL = process.env.FROM_EMAIL || "";
|
|
31
|
-
const FROM_EMAIL = process.env.FROM_EMAIL || "";
|
|
32
|
-
const SMTP_HOST = process.env.SMTP_HOST || "";
|
|
33
|
-
const SMTP_PORT = process.env.SMTP_PORT || "";
|
|
34
|
-
const SMTP_USERNAME = process.env.SMTP_USERNAME || "";
|
|
35
|
-
const SMTP_PASSWORD = process.env.SMTP_PASSWORD || "";
|
|
36
|
-
|
|
37
|
-
async function verifyUser(userId: string): Promise<StrapiUser | null> {
|
|
38
|
-
try {
|
|
39
|
-
const response = await fetch(
|
|
40
|
-
`${STRAPI_USER_LIST_API_URL}?filters[authId][$eq]=${userId}`,
|
|
41
|
-
{
|
|
42
|
-
headers: {
|
|
43
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
44
|
-
"Content-Type": "application/json",
|
|
45
|
-
},
|
|
46
|
-
}
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
if (!response.ok) {
|
|
50
|
-
console.error("User verification failed:", response.status);
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const userData = await response.json();
|
|
55
|
-
const strapiUser = userData.data?.find((user: any) => user.authId === userId);
|
|
56
|
-
if (!strapiUser) return null;
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
id: strapiUser.id,
|
|
60
|
-
username: strapiUser.username,
|
|
61
|
-
email: strapiUser.email,
|
|
62
|
-
businessAdminId: strapiUser.businessAdminId,
|
|
63
|
-
businessTitle: strapiUser.businessTitle,
|
|
64
|
-
};
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error("Error verifying user:", error);
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function getUserCreditsAndRequests(userId: string, isAdmin: boolean): Promise<{ adRequests: AdRequest[]; credits: AdRequest | null }> {
|
|
72
|
-
try {
|
|
73
|
-
const response = await fetch(
|
|
74
|
-
`${AD_REQUEST_API_URL}?pagination[pageSize]=100&fields[0]=platform&fields[1]=adType&fields[2]=description&fields[3]=timestamp&fields[4]=userId&fields[5]=credits&fields[6]=lastResetDate&fields[7]=nextResetDate&fields[8]=id&fields[9]=documentId${isAdmin ? "" : `&filters[userId][$eq]=${userId}`
|
|
75
|
-
}`,
|
|
76
|
-
{
|
|
77
|
-
headers: {
|
|
78
|
-
"Content-Type": "application/json",
|
|
79
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
80
|
-
},
|
|
81
|
-
cache: "no-store",
|
|
82
|
-
}
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
if (!response.ok) {
|
|
86
|
-
const errorText = await response.text();
|
|
87
|
-
throw new Error(`Failed to fetch ad requests: ${errorText}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const result = await response.json();
|
|
91
|
-
const adRequests = (result.data || []).map((req: any) => ({
|
|
92
|
-
id: req.id || 0,
|
|
93
|
-
documentId: req.documentId || "",
|
|
94
|
-
platform: req.attributes?.platform || req.platform || "",
|
|
95
|
-
adType: req.attributes?.adType || req.adType || "",
|
|
96
|
-
description: req.attributes?.description || req.description || "",
|
|
97
|
-
timestamp: req.attributes?.timestamp || req.timestamp || new Date().toISOString(),
|
|
98
|
-
userId: req.attributes?.userId || req.userId || "",
|
|
99
|
-
credits: req.attributes?.credits ?? 3,
|
|
100
|
-
lastResetDate: req.attributes?.lastResetDate || new Date().toISOString(),
|
|
101
|
-
nextResetDate:
|
|
102
|
-
req.attributes?.nextResetDate ||
|
|
103
|
-
new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1).toISOString(),
|
|
104
|
-
}));
|
|
105
|
-
|
|
106
|
-
// Count ad requests in the current month and year
|
|
107
|
-
const now = new Date();
|
|
108
|
-
const currentMonth = now.getMonth();
|
|
109
|
-
const currentYear = now.getFullYear();
|
|
110
|
-
const monthlyRequests = adRequests.filter((req: AdRequest) => {
|
|
111
|
-
const requestDate = new Date(req.timestamp);
|
|
112
|
-
return (
|
|
113
|
-
req.userId === userId &&
|
|
114
|
-
req.platform && // Ensure it's an actual ad request
|
|
115
|
-
req.adType &&
|
|
116
|
-
requestDate.getMonth() === currentMonth &&
|
|
117
|
-
requestDate.getFullYear() === currentYear
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Calculate remaining credits as 3 minus the number of requests this month
|
|
122
|
-
const remainingCredits = 3 - monthlyRequests.length;
|
|
123
|
-
|
|
124
|
-
// Get the latest record for reset dates
|
|
125
|
-
const latestRecord = adRequests
|
|
126
|
-
.filter((req: AdRequest) => req.userId === userId)
|
|
127
|
-
.sort((a: AdRequest, b: AdRequest) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())[0];
|
|
128
|
-
|
|
129
|
-
// Create a credits object for the UI
|
|
130
|
-
const creditsData = {
|
|
131
|
-
id: latestRecord?.id || 0,
|
|
132
|
-
documentId: latestRecord?.documentId || "",
|
|
133
|
-
platform: "",
|
|
134
|
-
adType: "",
|
|
135
|
-
description: "",
|
|
136
|
-
timestamp: latestRecord?.timestamp || new Date().toISOString(),
|
|
137
|
-
userId,
|
|
138
|
-
credits: remainingCredits,
|
|
139
|
-
lastResetDate: latestRecord?.lastResetDate || new Date().toISOString(),
|
|
140
|
-
nextResetDate:
|
|
141
|
-
latestRecord?.nextResetDate ||
|
|
142
|
-
new Date(currentYear, currentMonth + 1, 1).toISOString(),
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
adRequests: adRequests.filter((req: AdRequest) => req.platform && req.adType), // Filter out credit-only records
|
|
147
|
-
credits: creditsData,
|
|
148
|
-
};
|
|
149
|
-
} catch (error) {
|
|
150
|
-
console.error("Error fetching user credits and requests:", error);
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export async function GET(request: NextRequest) {
|
|
156
|
-
try {
|
|
157
|
-
if (!AD_REQUEST_API_URL || !STRAPI_API_TOKEN) {
|
|
158
|
-
return NextResponse.json(
|
|
159
|
-
{ error: "Server configuration error: Missing required environment variables" },
|
|
160
|
-
{ status: 500 }
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const { userId } = getAuth(request);
|
|
165
|
-
if (!userId) {
|
|
166
|
-
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const user = await verifyUser(userId);
|
|
170
|
-
if (!user) {
|
|
171
|
-
return NextResponse.json({ error: "Unauthorized: User not found" }, { status: 403 });
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const isAdmin = user.businessAdminId === ADMIN_BUSINESS_ID;
|
|
175
|
-
const { adRequests, credits } = await getUserCreditsAndRequests(userId, isAdmin);
|
|
176
|
-
|
|
177
|
-
return NextResponse.json({ data: { adRequests, credits } });
|
|
178
|
-
} catch (error) {
|
|
179
|
-
console.error("GET /api/adRequest: Error:", error);
|
|
180
|
-
return NextResponse.json(
|
|
181
|
-
{ error: error instanceof Error ? error.message : "An error occurred while fetching ad requests" },
|
|
182
|
-
{ status: 500 }
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export async function POST(request: NextRequest) {
|
|
188
|
-
const { userId } = getAuth(request);
|
|
189
|
-
if (!userId) {
|
|
190
|
-
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const user = await verifyUser(userId);
|
|
194
|
-
if (!user) {
|
|
195
|
-
return NextResponse.json({ error: "Unauthorized: User not found" }, { status: 403 });
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
if (!AD_REQUEST_API_URL || !STRAPI_API_TOKEN) {
|
|
200
|
-
return NextResponse.json(
|
|
201
|
-
{ error: "Server configuration error: Missing required environment variables" },
|
|
202
|
-
{ status: 500 }
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (!FROM_EMAIL || !DEVVISTA_EMAIL || !SMTP_HOST || !SMTP_PORT || !SMTP_USERNAME || !SMTP_PASSWORD) {
|
|
207
|
-
console.error("SMTP configuration is incomplete");
|
|
208
|
-
return NextResponse.json(
|
|
209
|
-
{ error: "Server configuration error: SMTP configuration is incomplete" },
|
|
210
|
-
{ status: 500 }
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const { data } = await request.json();
|
|
215
|
-
if (!data || !data.platform || !data.adType) {
|
|
216
|
-
return NextResponse.json({ error: "Missing required fields" }, { status: 400 });
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const isAdmin = user.businessAdminId === ADMIN_BUSINESS_ID;
|
|
220
|
-
const { credits } = await getUserCreditsAndRequests(userId, isAdmin);
|
|
221
|
-
|
|
222
|
-
// Enforce 3-request limit per month
|
|
223
|
-
if (credits && credits.credits <= 0) {
|
|
224
|
-
return NextResponse.json({ error: "Monthly ad request limit reached (3 requests)" }, { status: 400 });
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const now = new Date();
|
|
228
|
-
const nextResetDate = new Date(now.getFullYear(), now.getMonth() + 1, 1).toISOString();
|
|
229
|
-
|
|
230
|
-
const response = await fetch(AD_REQUEST_API_URL, {
|
|
231
|
-
method: "POST",
|
|
232
|
-
headers: {
|
|
233
|
-
"Content-Type": "application/json",
|
|
234
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
235
|
-
},
|
|
236
|
-
body: JSON.stringify({
|
|
237
|
-
data: {
|
|
238
|
-
platform: data.platform,
|
|
239
|
-
adType: data.adType,
|
|
240
|
-
description: data.description || null,
|
|
241
|
-
timestamp: new Date().toISOString(),
|
|
242
|
-
userId,
|
|
243
|
-
credits: credits ? credits.credits - 1 : 2, // Store for UI display
|
|
244
|
-
lastResetDate: credits?.lastResetDate || new Date().toISOString(),
|
|
245
|
-
nextResetDate: credits?.nextResetDate || nextResetDate,
|
|
246
|
-
},
|
|
247
|
-
}),
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
if (!response.ok) {
|
|
251
|
-
const errorText = await response.text();
|
|
252
|
-
let errorData = {};
|
|
253
|
-
try {
|
|
254
|
-
errorData = JSON.parse(errorText);
|
|
255
|
-
} catch (parseError) {
|
|
256
|
-
console.error("Failed to parse Strapi response:", parseError);
|
|
257
|
-
}
|
|
258
|
-
return NextResponse.json(
|
|
259
|
-
{ error: `Failed to create ad request: ${response.status} - ${JSON.stringify(errorData)}` },
|
|
260
|
-
{ status: response.status }
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Send email notification
|
|
265
|
-
const transporter = nodemailer.createTransport({
|
|
266
|
-
host: SMTP_HOST,
|
|
267
|
-
port: Number(SMTP_PORT),
|
|
268
|
-
auth: {
|
|
269
|
-
user: SMTP_USERNAME,
|
|
270
|
-
pass: SMTP_PASSWORD,
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
await transporter.sendMail({
|
|
275
|
-
from: `"DevVista Kit Email Service" <${FROM_EMAIL}>`,
|
|
276
|
-
to: DEVVISTA_EMAIL,
|
|
277
|
-
subject: `New Ad Request: ${data.adType}`,
|
|
278
|
-
text: `
|
|
279
|
-
Platform: ${data.platform}
|
|
280
|
-
Ad Type: ${data.adType}
|
|
281
|
-
Description: ${data.description || "No description provided"}
|
|
282
|
-
User Email: ${user.email}
|
|
283
|
-
Business Title: ${user.businessTitle || "Not provided"}
|
|
284
|
-
Timestamp: ${new Date().toLocaleString()}
|
|
285
|
-
`,
|
|
286
|
-
html: `
|
|
287
|
-
<!DOCTYPE html>
|
|
288
|
-
<html lang="en">
|
|
289
|
-
<head>
|
|
290
|
-
<meta charset="UTF-8">
|
|
291
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
292
|
-
<title>New Ad Request</title>
|
|
293
|
-
<style>
|
|
294
|
-
* {
|
|
295
|
-
margin: 0;
|
|
296
|
-
padding: 0;
|
|
297
|
-
box-sizing: border-box;
|
|
298
|
-
}
|
|
299
|
-
body {
|
|
300
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
301
|
-
background-color: #f4f7fa;
|
|
302
|
-
color: #333333;
|
|
303
|
-
line-height: 1.6;
|
|
304
|
-
}
|
|
305
|
-
.container {
|
|
306
|
-
max-width: 600px;
|
|
307
|
-
margin: 20px auto;
|
|
308
|
-
background-color: #ffffff;
|
|
309
|
-
border-radius: 8px;
|
|
310
|
-
overflow: hidden;
|
|
311
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
312
|
-
}
|
|
313
|
-
.header {
|
|
314
|
-
background-color: #2c3e50;
|
|
315
|
-
padding: 20px;
|
|
316
|
-
text-align: center;
|
|
317
|
-
}
|
|
318
|
-
.header .business-name {
|
|
319
|
-
color: white;
|
|
320
|
-
font-size: 28px;
|
|
321
|
-
font-weight: bold;
|
|
322
|
-
margin-bottom: 10px;
|
|
323
|
-
}
|
|
324
|
-
.header h1 {
|
|
325
|
-
color: #ffffff;
|
|
326
|
-
font-size: 24px;
|
|
327
|
-
margin-top: 10px;
|
|
328
|
-
}
|
|
329
|
-
.content {
|
|
330
|
-
padding: 30px;
|
|
331
|
-
}
|
|
332
|
-
.content h2 {
|
|
333
|
-
font-size: 20px;
|
|
334
|
-
color: #2c3e50;
|
|
335
|
-
margin-bottom: 20px;
|
|
336
|
-
}
|
|
337
|
-
.content p {
|
|
338
|
-
margin-bottom: 15px;
|
|
339
|
-
font-size: 16px;
|
|
340
|
-
}
|
|
341
|
-
.content .label {
|
|
342
|
-
font-weight: 600;
|
|
343
|
-
color: #2c3e50;
|
|
344
|
-
}
|
|
345
|
-
.content .button {
|
|
346
|
-
display: inline-block;
|
|
347
|
-
padding: 12px 24px;
|
|
348
|
-
margin-top: 20px;
|
|
349
|
-
background-color: #3498db;
|
|
350
|
-
color: #ffffff;
|
|
351
|
-
text-decoration: none;
|
|
352
|
-
border-radius: 4px;
|
|
353
|
-
font-size: 16px;
|
|
354
|
-
font-weight: 500;
|
|
355
|
-
}
|
|
356
|
-
.content .button:hover {
|
|
357
|
-
background-color: #2980b9;
|
|
358
|
-
}
|
|
359
|
-
.footer {
|
|
360
|
-
background-color: #ecf0f1;
|
|
361
|
-
padding: 20px;
|
|
362
|
-
text-align: center;
|
|
363
|
-
font-size: 14px;
|
|
364
|
-
color: #7f8c8d;
|
|
365
|
-
}
|
|
366
|
-
.footer a {
|
|
367
|
-
color: #3498db;
|
|
368
|
-
text-decoration: none;
|
|
369
|
-
}
|
|
370
|
-
.footer a:hover {
|
|
371
|
-
text-decoration: underline;
|
|
372
|
-
}
|
|
373
|
-
@media only screen and (max-width: 600px) {
|
|
374
|
-
.container {
|
|
375
|
-
margin: 10px;
|
|
376
|
-
border-radius: 0;
|
|
377
|
-
}
|
|
378
|
-
.header {
|
|
379
|
-
padding: 15px;
|
|
380
|
-
}
|
|
381
|
-
.header .business-name {
|
|
382
|
-
font-size: 24px;
|
|
383
|
-
}
|
|
384
|
-
.header h1 {
|
|
385
|
-
font-size: 20px;
|
|
386
|
-
}
|
|
387
|
-
.content {
|
|
388
|
-
padding: 20px;
|
|
389
|
-
}
|
|
390
|
-
.content h2 {
|
|
391
|
-
font-size: 18px;
|
|
392
|
-
}
|
|
393
|
-
.content p {
|
|
394
|
-
font-size: 14px;
|
|
395
|
-
}
|
|
396
|
-
.content .button {
|
|
397
|
-
padding: 10px 20px;
|
|
398
|
-
font-size: 14px;
|
|
399
|
-
}
|
|
400
|
-
.footer {
|
|
401
|
-
padding: 15px;
|
|
402
|
-
font-size: 12px;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
</style>
|
|
406
|
-
</head>
|
|
407
|
-
<body>
|
|
408
|
-
<div class="container">
|
|
409
|
-
<div class="header">
|
|
410
|
-
<div class="business-name">DevVista</div>
|
|
411
|
-
<h1>New Ad Request</h1>
|
|
412
|
-
</div>
|
|
413
|
-
<div class="content">
|
|
414
|
-
<h2>Ad Request Details</h2>
|
|
415
|
-
<p><span class="label">Platform:</span> ${data.platform}</p>
|
|
416
|
-
<p><span class="label">Ad Type:</span> ${data.adType}</p>
|
|
417
|
-
<p><span class="label">Description:</span> ${data.description || "No description provided"}</p>
|
|
418
|
-
<p><span class="label">User Email:</span> <a href="mailto:${user.email}">${user.email}</a></p>
|
|
419
|
-
<p><span class="label">Business Title:</span> ${user.businessTitle || "Not provided"}</p>
|
|
420
|
-
<p><span class="label">Timestamp:</span> ${new Date().toLocaleString()}</p>
|
|
421
|
-
<a href="mailto:${user.email}" class="button">Reply to User</a>
|
|
422
|
-
</div>
|
|
423
|
-
<div class="footer">
|
|
424
|
-
<p>© ${new Date().getFullYear()} DevVista. All rights reserved.</p>
|
|
425
|
-
<p><a href="https://devvistatech.com/">Visit our website</a> | <a href="mailto:devvistainfo@gmail.com">Contact Support</a></p>
|
|
426
|
-
</div>
|
|
427
|
-
</div>
|
|
428
|
-
</body>
|
|
429
|
-
</html>
|
|
430
|
-
`,
|
|
431
|
-
replyTo: user.email,
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
const { adRequests, credits: updatedCredits } = await getUserCreditsAndRequests(userId, isAdmin);
|
|
435
|
-
|
|
436
|
-
return NextResponse.json({ data: { adRequests, credits: updatedCredits } });
|
|
437
|
-
} catch (error) {
|
|
438
|
-
console.error("POST /api/adRequest: Error:", error);
|
|
439
|
-
return NextResponse.json(
|
|
440
|
-
{ error: error instanceof Error ? error.message : "An error occurred while creating ad request" },
|
|
441
|
-
{ status: 500 }
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
export async function PUT(request: NextRequest) {
|
|
447
|
-
const { userId } = getAuth(request);
|
|
448
|
-
if (!userId) {
|
|
449
|
-
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const user = await verifyUser(userId);
|
|
453
|
-
if (!user || user.businessAdminId !== ADMIN_BUSINESS_ID) {
|
|
454
|
-
return NextResponse.json({ error: "Unauthorized: Only admin can reset credits" }, { status: 403 });
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
try {
|
|
458
|
-
if (!AD_REQUEST_API_URL || !STRAPI_API_TOKEN) {
|
|
459
|
-
return NextResponse.json(
|
|
460
|
-
{ error: "Server configuration error: Missing required environment variables" },
|
|
461
|
-
{ status: 500 }
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const { userId: targetUserId, credits, lastResetDate, nextResetDate } = await request.json();
|
|
466
|
-
if (!targetUserId) {
|
|
467
|
-
return NextResponse.json({ error: "Missing userId" }, { status: 400 });
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const { credits: currentCredits } = await getUserCreditsAndRequests(targetUserId, true);
|
|
471
|
-
const documentId = currentCredits?.documentId;
|
|
472
|
-
|
|
473
|
-
const response = await fetch(
|
|
474
|
-
documentId ? `${AD_REQUEST_API_URL}/${documentId}` : AD_REQUEST_API_URL,
|
|
475
|
-
{
|
|
476
|
-
method: documentId ? "PUT" : "POST",
|
|
477
|
-
headers: {
|
|
478
|
-
"Content-Type": "application/json",
|
|
479
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
480
|
-
},
|
|
481
|
-
body: JSON.stringify({
|
|
482
|
-
data: {
|
|
483
|
-
userId: targetUserId,
|
|
484
|
-
credits: credits || 3,
|
|
485
|
-
lastResetDate: lastResetDate || new Date().toISOString(),
|
|
486
|
-
nextResetDate:
|
|
487
|
-
nextResetDate ||
|
|
488
|
-
new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1).toISOString(),
|
|
489
|
-
platform: null,
|
|
490
|
-
adType: null,
|
|
491
|
-
description: null,
|
|
492
|
-
timestamp: new Date().toISOString(),
|
|
493
|
-
},
|
|
494
|
-
}),
|
|
495
|
-
}
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
if (!response.ok) {
|
|
499
|
-
const errorText = await response.text();
|
|
500
|
-
return NextResponse.json(
|
|
501
|
-
{ error: `Failed to reset credits: ${errorText}` },
|
|
502
|
-
{ status: response.status }
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
const { adRequests, credits: updatedCredits } = await getUserCreditsAndRequests(targetUserId, true);
|
|
507
|
-
|
|
508
|
-
return NextResponse.json({ data: { adRequests, credits: updatedCredits } });
|
|
509
|
-
} catch (error) {
|
|
510
|
-
console.error("PUT /api/adRequest: Error:", error);
|
|
511
|
-
return NextResponse.json(
|
|
512
|
-
{ error: error instanceof Error ? error.message : "An error occurred while resetting credits" },
|
|
513
|
-
{ status: 500 }
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
export async function DELETE(request: NextRequest) {
|
|
519
|
-
const { userId } = getAuth(request);
|
|
520
|
-
if (!userId) {
|
|
521
|
-
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const user = await verifyUser(userId);
|
|
525
|
-
if (!user || user.businessAdminId !== ADMIN_BUSINESS_ID) {
|
|
526
|
-
return NextResponse.json({ error: "Unauthorized: Only admin can delete ad requests" }, { status: 403 });
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
try {
|
|
530
|
-
if (!AD_REQUEST_API_URL || !STRAPI_API_TOKEN) {
|
|
531
|
-
return NextResponse.json(
|
|
532
|
-
{ error: "Server configuration error: Missing required environment variables" },
|
|
533
|
-
{ status: 500 }
|
|
534
|
-
);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const { documentId } = await request.json();
|
|
538
|
-
if (!documentId) {
|
|
539
|
-
return NextResponse.json({ error: "Missing documentId" }, { status: 400 });
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const response = await fetch(`${AD_REQUEST_API_URL}/${documentId}`, {
|
|
543
|
-
method: "DELETE",
|
|
544
|
-
headers: {
|
|
545
|
-
"Content-Type": "application/json",
|
|
546
|
-
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
547
|
-
},
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
if (!response.ok) {
|
|
551
|
-
const errorText = await response.text();
|
|
552
|
-
return NextResponse.json(
|
|
553
|
-
{ error: `Failed to delete ad request: ${errorText}` },
|
|
554
|
-
{ status: response.status }
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const { adRequests, credits } = await getUserCreditsAndRequests(userId, true);
|
|
559
|
-
|
|
560
|
-
return NextResponse.json({ data: { adRequests, credits } });
|
|
561
|
-
} catch (error) {
|
|
562
|
-
console.error("DELETE /api/adRequest: Error:", error);
|
|
563
|
-
return NextResponse.json(
|
|
564
|
-
{ error: error instanceof Error ? error.message : "An error occurred while deleting ad request" },
|
|
565
|
-
{ status: 500 }
|
|
566
|
-
);
|
|
567
|
-
}
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { getAuth } from "@clerk/nextjs/server";
|
|
3
|
+
import nodemailer from "nodemailer";
|
|
4
|
+
|
|
5
|
+
interface StrapiUser {
|
|
6
|
+
id: number;
|
|
7
|
+
username: string;
|
|
8
|
+
email: string;
|
|
9
|
+
businessAdminId?: string;
|
|
10
|
+
businessTitle?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface AdRequest {
|
|
14
|
+
id: number;
|
|
15
|
+
documentId: string;
|
|
16
|
+
platform: string;
|
|
17
|
+
adType: string;
|
|
18
|
+
description: string;
|
|
19
|
+
timestamp: string;
|
|
20
|
+
userId: string;
|
|
21
|
+
credits: number;
|
|
22
|
+
lastResetDate: string;
|
|
23
|
+
nextResetDate: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const AD_REQUEST_API_URL = process.env.STRAPI_AD_REQUEST_API_URL || "";
|
|
27
|
+
const STRAPI_USER_LIST_API_URL = process.env.STRAPI_USER_LIST_API_URL || "";
|
|
28
|
+
const ADMIN_BUSINESS_ID = process.env.ADMIN_BUSINESS_ID || "";
|
|
29
|
+
const STRAPI_API_TOKEN = process.env.STRAPI_API_TOKEN || "";
|
|
30
|
+
const DEVVISTA_EMAIL = process.env.FROM_EMAIL || "";
|
|
31
|
+
const FROM_EMAIL = process.env.FROM_EMAIL || "";
|
|
32
|
+
const SMTP_HOST = process.env.SMTP_HOST || "";
|
|
33
|
+
const SMTP_PORT = process.env.SMTP_PORT || "";
|
|
34
|
+
const SMTP_USERNAME = process.env.SMTP_USERNAME || "";
|
|
35
|
+
const SMTP_PASSWORD = process.env.SMTP_PASSWORD || "";
|
|
36
|
+
|
|
37
|
+
async function verifyUser(userId: string): Promise<StrapiUser | null> {
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(
|
|
40
|
+
`${STRAPI_USER_LIST_API_URL}?filters[authId][$eq]=${userId}`,
|
|
41
|
+
{
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
console.error("User verification failed:", response.status);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const userData = await response.json();
|
|
55
|
+
const strapiUser = userData.data?.find((user: any) => user.authId === userId);
|
|
56
|
+
if (!strapiUser) return null;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
id: strapiUser.id,
|
|
60
|
+
username: strapiUser.username,
|
|
61
|
+
email: strapiUser.email,
|
|
62
|
+
businessAdminId: strapiUser.businessAdminId,
|
|
63
|
+
businessTitle: strapiUser.businessTitle,
|
|
64
|
+
};
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error("Error verifying user:", error);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function getUserCreditsAndRequests(userId: string, isAdmin: boolean): Promise<{ adRequests: AdRequest[]; credits: AdRequest | null }> {
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(
|
|
74
|
+
`${AD_REQUEST_API_URL}?pagination[pageSize]=100&fields[0]=platform&fields[1]=adType&fields[2]=description&fields[3]=timestamp&fields[4]=userId&fields[5]=credits&fields[6]=lastResetDate&fields[7]=nextResetDate&fields[8]=id&fields[9]=documentId${isAdmin ? "" : `&filters[userId][$eq]=${userId}`
|
|
75
|
+
}`,
|
|
76
|
+
{
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
80
|
+
},
|
|
81
|
+
cache: "no-store",
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
const errorText = await response.text();
|
|
87
|
+
throw new Error(`Failed to fetch ad requests: ${errorText}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result = await response.json();
|
|
91
|
+
const adRequests = (result.data || []).map((req: any) => ({
|
|
92
|
+
id: req.id || 0,
|
|
93
|
+
documentId: req.documentId || "",
|
|
94
|
+
platform: req.attributes?.platform || req.platform || "",
|
|
95
|
+
adType: req.attributes?.adType || req.adType || "",
|
|
96
|
+
description: req.attributes?.description || req.description || "",
|
|
97
|
+
timestamp: req.attributes?.timestamp || req.timestamp || new Date().toISOString(),
|
|
98
|
+
userId: req.attributes?.userId || req.userId || "",
|
|
99
|
+
credits: req.attributes?.credits ?? 3,
|
|
100
|
+
lastResetDate: req.attributes?.lastResetDate || new Date().toISOString(),
|
|
101
|
+
nextResetDate:
|
|
102
|
+
req.attributes?.nextResetDate ||
|
|
103
|
+
new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1).toISOString(),
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
// Count ad requests in the current month and year
|
|
107
|
+
const now = new Date();
|
|
108
|
+
const currentMonth = now.getMonth();
|
|
109
|
+
const currentYear = now.getFullYear();
|
|
110
|
+
const monthlyRequests = adRequests.filter((req: AdRequest) => {
|
|
111
|
+
const requestDate = new Date(req.timestamp);
|
|
112
|
+
return (
|
|
113
|
+
req.userId === userId &&
|
|
114
|
+
req.platform && // Ensure it's an actual ad request
|
|
115
|
+
req.adType &&
|
|
116
|
+
requestDate.getMonth() === currentMonth &&
|
|
117
|
+
requestDate.getFullYear() === currentYear
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Calculate remaining credits as 3 minus the number of requests this month
|
|
122
|
+
const remainingCredits = 3 - monthlyRequests.length;
|
|
123
|
+
|
|
124
|
+
// Get the latest record for reset dates
|
|
125
|
+
const latestRecord = adRequests
|
|
126
|
+
.filter((req: AdRequest) => req.userId === userId)
|
|
127
|
+
.sort((a: AdRequest, b: AdRequest) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())[0];
|
|
128
|
+
|
|
129
|
+
// Create a credits object for the UI
|
|
130
|
+
const creditsData = {
|
|
131
|
+
id: latestRecord?.id || 0,
|
|
132
|
+
documentId: latestRecord?.documentId || "",
|
|
133
|
+
platform: "",
|
|
134
|
+
adType: "",
|
|
135
|
+
description: "",
|
|
136
|
+
timestamp: latestRecord?.timestamp || new Date().toISOString(),
|
|
137
|
+
userId,
|
|
138
|
+
credits: remainingCredits,
|
|
139
|
+
lastResetDate: latestRecord?.lastResetDate || new Date().toISOString(),
|
|
140
|
+
nextResetDate:
|
|
141
|
+
latestRecord?.nextResetDate ||
|
|
142
|
+
new Date(currentYear, currentMonth + 1, 1).toISOString(),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
adRequests: adRequests.filter((req: AdRequest) => req.platform && req.adType), // Filter out credit-only records
|
|
147
|
+
credits: creditsData,
|
|
148
|
+
};
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error("Error fetching user credits and requests:", error);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function GET(request: NextRequest) {
|
|
156
|
+
try {
|
|
157
|
+
if (!AD_REQUEST_API_URL || !STRAPI_API_TOKEN) {
|
|
158
|
+
return NextResponse.json(
|
|
159
|
+
{ error: "Server configuration error: Missing required environment variables" },
|
|
160
|
+
{ status: 500 }
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const { userId } = getAuth(request);
|
|
165
|
+
if (!userId) {
|
|
166
|
+
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const user = await verifyUser(userId);
|
|
170
|
+
if (!user) {
|
|
171
|
+
return NextResponse.json({ error: "Unauthorized: User not found" }, { status: 403 });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const isAdmin = user.businessAdminId === ADMIN_BUSINESS_ID;
|
|
175
|
+
const { adRequests, credits } = await getUserCreditsAndRequests(userId, isAdmin);
|
|
176
|
+
|
|
177
|
+
return NextResponse.json({ data: { adRequests, credits } });
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error("GET /api/adRequest: Error:", error);
|
|
180
|
+
return NextResponse.json(
|
|
181
|
+
{ error: error instanceof Error ? error.message : "An error occurred while fetching ad requests" },
|
|
182
|
+
{ status: 500 }
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function POST(request: NextRequest) {
|
|
188
|
+
const { userId } = getAuth(request);
|
|
189
|
+
if (!userId) {
|
|
190
|
+
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const user = await verifyUser(userId);
|
|
194
|
+
if (!user) {
|
|
195
|
+
return NextResponse.json({ error: "Unauthorized: User not found" }, { status: 403 });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
if (!AD_REQUEST_API_URL || !STRAPI_API_TOKEN) {
|
|
200
|
+
return NextResponse.json(
|
|
201
|
+
{ error: "Server configuration error: Missing required environment variables" },
|
|
202
|
+
{ status: 500 }
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!FROM_EMAIL || !DEVVISTA_EMAIL || !SMTP_HOST || !SMTP_PORT || !SMTP_USERNAME || !SMTP_PASSWORD) {
|
|
207
|
+
console.error("SMTP configuration is incomplete");
|
|
208
|
+
return NextResponse.json(
|
|
209
|
+
{ error: "Server configuration error: SMTP configuration is incomplete" },
|
|
210
|
+
{ status: 500 }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const { data } = await request.json();
|
|
215
|
+
if (!data || !data.platform || !data.adType) {
|
|
216
|
+
return NextResponse.json({ error: "Missing required fields" }, { status: 400 });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const isAdmin = user.businessAdminId === ADMIN_BUSINESS_ID;
|
|
220
|
+
const { credits } = await getUserCreditsAndRequests(userId, isAdmin);
|
|
221
|
+
|
|
222
|
+
// Enforce 3-request limit per month
|
|
223
|
+
if (credits && credits.credits <= 0) {
|
|
224
|
+
return NextResponse.json({ error: "Monthly ad request limit reached (3 requests)" }, { status: 400 });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const now = new Date();
|
|
228
|
+
const nextResetDate = new Date(now.getFullYear(), now.getMonth() + 1, 1).toISOString();
|
|
229
|
+
|
|
230
|
+
const response = await fetch(AD_REQUEST_API_URL, {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: {
|
|
233
|
+
"Content-Type": "application/json",
|
|
234
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
235
|
+
},
|
|
236
|
+
body: JSON.stringify({
|
|
237
|
+
data: {
|
|
238
|
+
platform: data.platform,
|
|
239
|
+
adType: data.adType,
|
|
240
|
+
description: data.description || null,
|
|
241
|
+
timestamp: new Date().toISOString(),
|
|
242
|
+
userId,
|
|
243
|
+
credits: credits ? credits.credits - 1 : 2, // Store for UI display
|
|
244
|
+
lastResetDate: credits?.lastResetDate || new Date().toISOString(),
|
|
245
|
+
nextResetDate: credits?.nextResetDate || nextResetDate,
|
|
246
|
+
},
|
|
247
|
+
}),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (!response.ok) {
|
|
251
|
+
const errorText = await response.text();
|
|
252
|
+
let errorData = {};
|
|
253
|
+
try {
|
|
254
|
+
errorData = JSON.parse(errorText);
|
|
255
|
+
} catch (parseError) {
|
|
256
|
+
console.error("Failed to parse Strapi response:", parseError);
|
|
257
|
+
}
|
|
258
|
+
return NextResponse.json(
|
|
259
|
+
{ error: `Failed to create ad request: ${response.status} - ${JSON.stringify(errorData)}` },
|
|
260
|
+
{ status: response.status }
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Send email notification
|
|
265
|
+
const transporter = nodemailer.createTransport({
|
|
266
|
+
host: SMTP_HOST,
|
|
267
|
+
port: Number(SMTP_PORT),
|
|
268
|
+
auth: {
|
|
269
|
+
user: SMTP_USERNAME,
|
|
270
|
+
pass: SMTP_PASSWORD,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
await transporter.sendMail({
|
|
275
|
+
from: `"DevVista Kit Email Service" <${FROM_EMAIL}>`,
|
|
276
|
+
to: DEVVISTA_EMAIL,
|
|
277
|
+
subject: `New Ad Request: ${data.adType}`,
|
|
278
|
+
text: `
|
|
279
|
+
Platform: ${data.platform}
|
|
280
|
+
Ad Type: ${data.adType}
|
|
281
|
+
Description: ${data.description || "No description provided"}
|
|
282
|
+
User Email: ${user.email}
|
|
283
|
+
Business Title: ${user.businessTitle || "Not provided"}
|
|
284
|
+
Timestamp: ${new Date().toLocaleString()}
|
|
285
|
+
`,
|
|
286
|
+
html: `
|
|
287
|
+
<!DOCTYPE html>
|
|
288
|
+
<html lang="en">
|
|
289
|
+
<head>
|
|
290
|
+
<meta charset="UTF-8">
|
|
291
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
292
|
+
<title>New Ad Request</title>
|
|
293
|
+
<style>
|
|
294
|
+
* {
|
|
295
|
+
margin: 0;
|
|
296
|
+
padding: 0;
|
|
297
|
+
box-sizing: border-box;
|
|
298
|
+
}
|
|
299
|
+
body {
|
|
300
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
301
|
+
background-color: #f4f7fa;
|
|
302
|
+
color: #333333;
|
|
303
|
+
line-height: 1.6;
|
|
304
|
+
}
|
|
305
|
+
.container {
|
|
306
|
+
max-width: 600px;
|
|
307
|
+
margin: 20px auto;
|
|
308
|
+
background-color: #ffffff;
|
|
309
|
+
border-radius: 8px;
|
|
310
|
+
overflow: hidden;
|
|
311
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
312
|
+
}
|
|
313
|
+
.header {
|
|
314
|
+
background-color: #2c3e50;
|
|
315
|
+
padding: 20px;
|
|
316
|
+
text-align: center;
|
|
317
|
+
}
|
|
318
|
+
.header .business-name {
|
|
319
|
+
color: white;
|
|
320
|
+
font-size: 28px;
|
|
321
|
+
font-weight: bold;
|
|
322
|
+
margin-bottom: 10px;
|
|
323
|
+
}
|
|
324
|
+
.header h1 {
|
|
325
|
+
color: #ffffff;
|
|
326
|
+
font-size: 24px;
|
|
327
|
+
margin-top: 10px;
|
|
328
|
+
}
|
|
329
|
+
.content {
|
|
330
|
+
padding: 30px;
|
|
331
|
+
}
|
|
332
|
+
.content h2 {
|
|
333
|
+
font-size: 20px;
|
|
334
|
+
color: #2c3e50;
|
|
335
|
+
margin-bottom: 20px;
|
|
336
|
+
}
|
|
337
|
+
.content p {
|
|
338
|
+
margin-bottom: 15px;
|
|
339
|
+
font-size: 16px;
|
|
340
|
+
}
|
|
341
|
+
.content .label {
|
|
342
|
+
font-weight: 600;
|
|
343
|
+
color: #2c3e50;
|
|
344
|
+
}
|
|
345
|
+
.content .button {
|
|
346
|
+
display: inline-block;
|
|
347
|
+
padding: 12px 24px;
|
|
348
|
+
margin-top: 20px;
|
|
349
|
+
background-color: #3498db;
|
|
350
|
+
color: #ffffff;
|
|
351
|
+
text-decoration: none;
|
|
352
|
+
border-radius: 4px;
|
|
353
|
+
font-size: 16px;
|
|
354
|
+
font-weight: 500;
|
|
355
|
+
}
|
|
356
|
+
.content .button:hover {
|
|
357
|
+
background-color: #2980b9;
|
|
358
|
+
}
|
|
359
|
+
.footer {
|
|
360
|
+
background-color: #ecf0f1;
|
|
361
|
+
padding: 20px;
|
|
362
|
+
text-align: center;
|
|
363
|
+
font-size: 14px;
|
|
364
|
+
color: #7f8c8d;
|
|
365
|
+
}
|
|
366
|
+
.footer a {
|
|
367
|
+
color: #3498db;
|
|
368
|
+
text-decoration: none;
|
|
369
|
+
}
|
|
370
|
+
.footer a:hover {
|
|
371
|
+
text-decoration: underline;
|
|
372
|
+
}
|
|
373
|
+
@media only screen and (max-width: 600px) {
|
|
374
|
+
.container {
|
|
375
|
+
margin: 10px;
|
|
376
|
+
border-radius: 0;
|
|
377
|
+
}
|
|
378
|
+
.header {
|
|
379
|
+
padding: 15px;
|
|
380
|
+
}
|
|
381
|
+
.header .business-name {
|
|
382
|
+
font-size: 24px;
|
|
383
|
+
}
|
|
384
|
+
.header h1 {
|
|
385
|
+
font-size: 20px;
|
|
386
|
+
}
|
|
387
|
+
.content {
|
|
388
|
+
padding: 20px;
|
|
389
|
+
}
|
|
390
|
+
.content h2 {
|
|
391
|
+
font-size: 18px;
|
|
392
|
+
}
|
|
393
|
+
.content p {
|
|
394
|
+
font-size: 14px;
|
|
395
|
+
}
|
|
396
|
+
.content .button {
|
|
397
|
+
padding: 10px 20px;
|
|
398
|
+
font-size: 14px;
|
|
399
|
+
}
|
|
400
|
+
.footer {
|
|
401
|
+
padding: 15px;
|
|
402
|
+
font-size: 12px;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
</style>
|
|
406
|
+
</head>
|
|
407
|
+
<body>
|
|
408
|
+
<div class="container">
|
|
409
|
+
<div class="header">
|
|
410
|
+
<div class="business-name">DevVista</div>
|
|
411
|
+
<h1>New Ad Request</h1>
|
|
412
|
+
</div>
|
|
413
|
+
<div class="content">
|
|
414
|
+
<h2>Ad Request Details</h2>
|
|
415
|
+
<p><span class="label">Platform:</span> ${data.platform}</p>
|
|
416
|
+
<p><span class="label">Ad Type:</span> ${data.adType}</p>
|
|
417
|
+
<p><span class="label">Description:</span> ${data.description || "No description provided"}</p>
|
|
418
|
+
<p><span class="label">User Email:</span> <a href="mailto:${user.email}">${user.email}</a></p>
|
|
419
|
+
<p><span class="label">Business Title:</span> ${user.businessTitle || "Not provided"}</p>
|
|
420
|
+
<p><span class="label">Timestamp:</span> ${new Date().toLocaleString()}</p>
|
|
421
|
+
<a href="mailto:${user.email}" class="button">Reply to User</a>
|
|
422
|
+
</div>
|
|
423
|
+
<div class="footer">
|
|
424
|
+
<p>© ${new Date().getFullYear()} DevVista. All rights reserved.</p>
|
|
425
|
+
<p><a href="https://devvistatech.com/">Visit our website</a> | <a href="mailto:devvistainfo@gmail.com">Contact Support</a></p>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
</body>
|
|
429
|
+
</html>
|
|
430
|
+
`,
|
|
431
|
+
replyTo: user.email,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const { adRequests, credits: updatedCredits } = await getUserCreditsAndRequests(userId, isAdmin);
|
|
435
|
+
|
|
436
|
+
return NextResponse.json({ data: { adRequests, credits: updatedCredits } });
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.error("POST /api/adRequest: Error:", error);
|
|
439
|
+
return NextResponse.json(
|
|
440
|
+
{ error: error instanceof Error ? error.message : "An error occurred while creating ad request" },
|
|
441
|
+
{ status: 500 }
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export async function PUT(request: NextRequest) {
|
|
447
|
+
const { userId } = getAuth(request);
|
|
448
|
+
if (!userId) {
|
|
449
|
+
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const user = await verifyUser(userId);
|
|
453
|
+
if (!user || user.businessAdminId !== ADMIN_BUSINESS_ID) {
|
|
454
|
+
return NextResponse.json({ error: "Unauthorized: Only admin can reset credits" }, { status: 403 });
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
if (!AD_REQUEST_API_URL || !STRAPI_API_TOKEN) {
|
|
459
|
+
return NextResponse.json(
|
|
460
|
+
{ error: "Server configuration error: Missing required environment variables" },
|
|
461
|
+
{ status: 500 }
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const { userId: targetUserId, credits, lastResetDate, nextResetDate } = await request.json();
|
|
466
|
+
if (!targetUserId) {
|
|
467
|
+
return NextResponse.json({ error: "Missing userId" }, { status: 400 });
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const { credits: currentCredits } = await getUserCreditsAndRequests(targetUserId, true);
|
|
471
|
+
const documentId = currentCredits?.documentId;
|
|
472
|
+
|
|
473
|
+
const response = await fetch(
|
|
474
|
+
documentId ? `${AD_REQUEST_API_URL}/${documentId}` : AD_REQUEST_API_URL,
|
|
475
|
+
{
|
|
476
|
+
method: documentId ? "PUT" : "POST",
|
|
477
|
+
headers: {
|
|
478
|
+
"Content-Type": "application/json",
|
|
479
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
480
|
+
},
|
|
481
|
+
body: JSON.stringify({
|
|
482
|
+
data: {
|
|
483
|
+
userId: targetUserId,
|
|
484
|
+
credits: credits || 3,
|
|
485
|
+
lastResetDate: lastResetDate || new Date().toISOString(),
|
|
486
|
+
nextResetDate:
|
|
487
|
+
nextResetDate ||
|
|
488
|
+
new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1).toISOString(),
|
|
489
|
+
platform: null,
|
|
490
|
+
adType: null,
|
|
491
|
+
description: null,
|
|
492
|
+
timestamp: new Date().toISOString(),
|
|
493
|
+
},
|
|
494
|
+
}),
|
|
495
|
+
}
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
if (!response.ok) {
|
|
499
|
+
const errorText = await response.text();
|
|
500
|
+
return NextResponse.json(
|
|
501
|
+
{ error: `Failed to reset credits: ${errorText}` },
|
|
502
|
+
{ status: response.status }
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const { adRequests, credits: updatedCredits } = await getUserCreditsAndRequests(targetUserId, true);
|
|
507
|
+
|
|
508
|
+
return NextResponse.json({ data: { adRequests, credits: updatedCredits } });
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.error("PUT /api/adRequest: Error:", error);
|
|
511
|
+
return NextResponse.json(
|
|
512
|
+
{ error: error instanceof Error ? error.message : "An error occurred while resetting credits" },
|
|
513
|
+
{ status: 500 }
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export async function DELETE(request: NextRequest) {
|
|
519
|
+
const { userId } = getAuth(request);
|
|
520
|
+
if (!userId) {
|
|
521
|
+
return NextResponse.json({ error: "Unauthorized: No user ID" }, { status: 401 });
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const user = await verifyUser(userId);
|
|
525
|
+
if (!user || user.businessAdminId !== ADMIN_BUSINESS_ID) {
|
|
526
|
+
return NextResponse.json({ error: "Unauthorized: Only admin can delete ad requests" }, { status: 403 });
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
if (!AD_REQUEST_API_URL || !STRAPI_API_TOKEN) {
|
|
531
|
+
return NextResponse.json(
|
|
532
|
+
{ error: "Server configuration error: Missing required environment variables" },
|
|
533
|
+
{ status: 500 }
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const { documentId } = await request.json();
|
|
538
|
+
if (!documentId) {
|
|
539
|
+
return NextResponse.json({ error: "Missing documentId" }, { status: 400 });
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const response = await fetch(`${AD_REQUEST_API_URL}/${documentId}`, {
|
|
543
|
+
method: "DELETE",
|
|
544
|
+
headers: {
|
|
545
|
+
"Content-Type": "application/json",
|
|
546
|
+
Authorization: `Bearer ${STRAPI_API_TOKEN}`,
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
if (!response.ok) {
|
|
551
|
+
const errorText = await response.text();
|
|
552
|
+
return NextResponse.json(
|
|
553
|
+
{ error: `Failed to delete ad request: ${errorText}` },
|
|
554
|
+
{ status: response.status }
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const { adRequests, credits } = await getUserCreditsAndRequests(userId, true);
|
|
559
|
+
|
|
560
|
+
return NextResponse.json({ data: { adRequests, credits } });
|
|
561
|
+
} catch (error) {
|
|
562
|
+
console.error("DELETE /api/adRequest: Error:", error);
|
|
563
|
+
return NextResponse.json(
|
|
564
|
+
{ error: error instanceof Error ? error.message : "An error occurred while deleting ad request" },
|
|
565
|
+
{ status: 500 }
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
568
|
}
|