@bash-app/bash-common 30.51.0 → 30.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/dist/definitions.d.ts +668 -0
  2. package/dist/definitions.d.ts.map +1 -0
  3. package/dist/definitions.js +467 -0
  4. package/dist/definitions.js.map +1 -0
  5. package/dist/extendedSchemas.d.ts +2206 -0
  6. package/dist/extendedSchemas.d.ts.map +1 -0
  7. package/dist/extendedSchemas.js +429 -0
  8. package/dist/extendedSchemas.js.map +1 -0
  9. package/dist/index.d.ts +39 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +40 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/utils/addressUtils.d.ts +14 -0
  14. package/dist/utils/addressUtils.d.ts.map +1 -0
  15. package/dist/utils/addressUtils.js +177 -0
  16. package/dist/utils/addressUtils.js.map +1 -0
  17. package/dist/utils/apiUtils.d.ts +14 -0
  18. package/dist/utils/apiUtils.d.ts.map +1 -0
  19. package/dist/utils/apiUtils.js +61 -0
  20. package/dist/utils/apiUtils.js.map +1 -0
  21. package/dist/utils/arrayUtils.d.ts +2 -0
  22. package/dist/utils/arrayUtils.d.ts.map +1 -0
  23. package/dist/utils/arrayUtils.js +5 -0
  24. package/dist/utils/arrayUtils.js.map +1 -0
  25. package/dist/utils/awsS3Utils.d.ts +10 -0
  26. package/dist/utils/awsS3Utils.d.ts.map +1 -0
  27. package/dist/utils/awsS3Utils.js +89 -0
  28. package/dist/utils/awsS3Utils.js.map +1 -0
  29. package/dist/utils/badgeUtils.d.ts +33 -0
  30. package/dist/utils/badgeUtils.d.ts.map +1 -0
  31. package/dist/utils/badgeUtils.js +57 -0
  32. package/dist/utils/badgeUtils.js.map +1 -0
  33. package/dist/utils/blog/blogDbUtils.d.ts +48 -0
  34. package/dist/utils/blog/blogDbUtils.d.ts.map +1 -0
  35. package/dist/utils/blog/blogDbUtils.js +218 -0
  36. package/dist/utils/blog/blogDbUtils.js.map +1 -0
  37. package/dist/utils/blogUtils.d.ts +19 -0
  38. package/dist/utils/blogUtils.d.ts.map +1 -0
  39. package/dist/utils/blogUtils.js +118 -0
  40. package/dist/utils/blogUtils.js.map +1 -0
  41. package/dist/utils/dateTimeUtils.d.ts +38 -0
  42. package/dist/utils/dateTimeUtils.d.ts.map +1 -0
  43. package/dist/utils/dateTimeUtils.js +207 -0
  44. package/dist/utils/dateTimeUtils.js.map +1 -0
  45. package/dist/utils/entityUtils.d.ts +5 -0
  46. package/dist/utils/entityUtils.d.ts.map +1 -0
  47. package/dist/utils/entityUtils.js +5 -0
  48. package/dist/utils/entityUtils.js.map +1 -0
  49. package/dist/utils/generalDateTimeUtils.d.ts +15 -0
  50. package/dist/utils/generalDateTimeUtils.d.ts.map +1 -0
  51. package/dist/utils/generalDateTimeUtils.js +38 -0
  52. package/dist/utils/generalDateTimeUtils.js.map +1 -0
  53. package/dist/utils/luxonUtils.d.ts +95 -0
  54. package/dist/utils/luxonUtils.d.ts.map +1 -0
  55. package/dist/utils/luxonUtils.js +623 -0
  56. package/dist/utils/luxonUtils.js.map +1 -0
  57. package/dist/utils/mathUtils.d.ts +2 -0
  58. package/dist/utils/mathUtils.d.ts.map +1 -0
  59. package/dist/utils/mathUtils.js +4 -0
  60. package/dist/utils/mathUtils.js.map +1 -0
  61. package/dist/utils/objUtils.d.ts +3 -0
  62. package/dist/utils/objUtils.d.ts.map +1 -0
  63. package/dist/utils/objUtils.js +29 -0
  64. package/dist/utils/objUtils.js.map +1 -0
  65. package/dist/utils/paymentUtils.d.ts +18 -0
  66. package/dist/utils/paymentUtils.d.ts.map +1 -0
  67. package/dist/utils/paymentUtils.js +50 -0
  68. package/dist/utils/paymentUtils.js.map +1 -0
  69. package/dist/utils/promoCodesUtils.d.ts +6 -0
  70. package/dist/utils/promoCodesUtils.d.ts.map +1 -0
  71. package/dist/utils/promoCodesUtils.js +23 -0
  72. package/dist/utils/promoCodesUtils.js.map +1 -0
  73. package/dist/utils/qrCodeUtils.d.ts +4 -0
  74. package/dist/utils/qrCodeUtils.d.ts.map +1 -0
  75. package/dist/utils/qrCodeUtils.js +22 -0
  76. package/dist/utils/qrCodeUtils.js.map +1 -0
  77. package/dist/utils/recurrenceUtils.d.ts +8 -0
  78. package/dist/utils/recurrenceUtils.d.ts.map +1 -0
  79. package/dist/utils/recurrenceUtils.js +153 -0
  80. package/dist/utils/recurrenceUtils.js.map +1 -0
  81. package/dist/utils/reviewUtils.d.ts +29 -0
  82. package/dist/utils/reviewUtils.d.ts.map +1 -0
  83. package/dist/utils/reviewUtils.js +65 -0
  84. package/dist/utils/reviewUtils.js.map +1 -0
  85. package/dist/utils/service/apiServiceBookingApiUtils.d.ts +21 -0
  86. package/dist/utils/service/apiServiceBookingApiUtils.d.ts.map +1 -0
  87. package/dist/utils/service/apiServiceBookingApiUtils.js +131 -0
  88. package/dist/utils/service/apiServiceBookingApiUtils.js.map +1 -0
  89. package/dist/utils/service/attendeeOptionUtils.d.ts +11 -0
  90. package/dist/utils/service/attendeeOptionUtils.d.ts.map +1 -0
  91. package/dist/utils/service/attendeeOptionUtils.js +9 -0
  92. package/dist/utils/service/attendeeOptionUtils.js.map +1 -0
  93. package/dist/utils/service/frontendServiceBookingUtils.d.ts +53 -0
  94. package/dist/utils/service/frontendServiceBookingUtils.d.ts.map +1 -0
  95. package/dist/utils/service/frontendServiceBookingUtils.js +134 -0
  96. package/dist/utils/service/frontendServiceBookingUtils.js.map +1 -0
  97. package/dist/utils/service/regexUtils.d.ts +6 -0
  98. package/dist/utils/service/regexUtils.d.ts.map +1 -0
  99. package/dist/utils/service/regexUtils.js +16 -0
  100. package/dist/utils/service/regexUtils.js.map +1 -0
  101. package/dist/utils/service/serviceBookingStatusUtils.d.ts +21 -0
  102. package/dist/utils/service/serviceBookingStatusUtils.d.ts.map +1 -0
  103. package/dist/utils/service/serviceBookingStatusUtils.js +130 -0
  104. package/dist/utils/service/serviceBookingStatusUtils.js.map +1 -0
  105. package/dist/utils/service/serviceBookingTypes.d.ts +19 -0
  106. package/dist/utils/service/serviceBookingTypes.d.ts.map +1 -0
  107. package/dist/utils/service/serviceBookingTypes.js +2 -0
  108. package/dist/utils/service/serviceBookingTypes.js.map +1 -0
  109. package/dist/utils/service/serviceDBUtils.d.ts +5 -0
  110. package/dist/utils/service/serviceDBUtils.d.ts.map +1 -0
  111. package/dist/utils/service/serviceDBUtils.js +48 -0
  112. package/dist/utils/service/serviceDBUtils.js.map +1 -0
  113. package/dist/utils/service/serviceRateDBUtils.d.ts +1 -0
  114. package/dist/utils/service/serviceRateDBUtils.d.ts.map +1 -0
  115. package/dist/utils/service/serviceRateDBUtils.js +159 -0
  116. package/dist/utils/service/serviceRateDBUtils.js.map +1 -0
  117. package/dist/utils/service/serviceRateTypes.d.ts +9 -0
  118. package/dist/utils/service/serviceRateTypes.d.ts.map +1 -0
  119. package/dist/utils/service/serviceRateTypes.js +2 -0
  120. package/dist/utils/service/serviceRateTypes.js.map +1 -0
  121. package/dist/utils/service/serviceRateUtils.d.ts +24 -0
  122. package/dist/utils/service/serviceRateUtils.d.ts.map +1 -0
  123. package/dist/utils/service/serviceRateUtils.js +183 -0
  124. package/dist/utils/service/serviceRateUtils.js.map +1 -0
  125. package/dist/utils/service/serviceUtils.d.ts +83 -0
  126. package/dist/utils/service/serviceUtils.d.ts.map +1 -0
  127. package/dist/utils/service/serviceUtils.js +311 -0
  128. package/dist/utils/service/serviceUtils.js.map +1 -0
  129. package/dist/utils/service/venueUtils.d.ts +13 -0
  130. package/dist/utils/service/venueUtils.d.ts.map +1 -0
  131. package/dist/utils/service/venueUtils.js +18 -0
  132. package/dist/utils/service/venueUtils.js.map +1 -0
  133. package/dist/utils/slugUtils.d.ts +9 -0
  134. package/dist/utils/slugUtils.d.ts.map +1 -0
  135. package/dist/utils/slugUtils.js +47 -0
  136. package/dist/utils/slugUtils.js.map +1 -0
  137. package/dist/utils/sortUtils.d.ts +7 -0
  138. package/dist/utils/sortUtils.d.ts.map +1 -0
  139. package/dist/utils/sortUtils.js +26 -0
  140. package/dist/utils/sortUtils.js.map +1 -0
  141. package/dist/utils/stringUtils.d.ts +2 -0
  142. package/dist/utils/stringUtils.d.ts.map +1 -0
  143. package/dist/utils/stringUtils.js +6 -0
  144. package/dist/utils/stringUtils.js.map +1 -0
  145. package/dist/utils/stripeAccountUtils.d.ts +3 -0
  146. package/dist/utils/stripeAccountUtils.d.ts.map +1 -0
  147. package/dist/utils/stripeAccountUtils.js +10 -0
  148. package/dist/utils/stripeAccountUtils.js.map +1 -0
  149. package/dist/utils/ticketListUtils.d.ts +12 -0
  150. package/dist/utils/ticketListUtils.d.ts.map +1 -0
  151. package/dist/utils/ticketListUtils.js +78 -0
  152. package/dist/utils/ticketListUtils.js.map +1 -0
  153. package/dist/utils/typeUtils.d.ts +11 -0
  154. package/dist/utils/typeUtils.d.ts.map +1 -0
  155. package/dist/utils/typeUtils.js +8 -0
  156. package/dist/utils/typeUtils.js.map +1 -0
  157. package/dist/utils/urlUtils.d.ts +10 -0
  158. package/dist/utils/urlUtils.d.ts.map +1 -0
  159. package/dist/utils/urlUtils.js +66 -0
  160. package/dist/utils/urlUtils.js.map +1 -0
  161. package/dist/utils/userPromoCodeUtils.d.ts +21 -0
  162. package/dist/utils/userPromoCodeUtils.d.ts.map +1 -0
  163. package/dist/utils/userPromoCodeUtils.js +13 -0
  164. package/dist/utils/userPromoCodeUtils.js.map +1 -0
  165. package/dist/utils/userSubscriptionUtils.d.ts +36 -0
  166. package/dist/utils/userSubscriptionUtils.d.ts.map +1 -0
  167. package/dist/utils/userSubscriptionUtils.js +56 -0
  168. package/dist/utils/userSubscriptionUtils.js.map +1 -0
  169. package/dist/utils/userUtils.d.ts +2 -0
  170. package/dist/utils/userUtils.d.ts.map +1 -0
  171. package/dist/utils/userUtils.js +2 -0
  172. package/dist/utils/userUtils.js.map +1 -0
  173. package/package.json +4 -2
  174. package/prisma/schema.prisma +117 -0
  175. package/src/definitions.ts +52 -1
  176. package/src/extendedSchemas.ts +177 -6
  177. package/src/index.ts +32 -21
  178. package/src/utils/blog/blogDbUtils.ts +343 -0
  179. package/src/utils/blogUtils.ts +134 -0
  180. package/src/utils/qrCodeUtils.ts +4 -2
  181. package/src/utils/service/apiServiceBookingApiUtils.ts +0 -0
  182. package/src/utils/slugUtils.ts +60 -0
  183. package/src/utils/ticketListUtils.ts +24 -6
  184. package/src/utils/typeUtils.ts +0 -0
@@ -5,22 +5,35 @@ import {
5
5
  BashComment,
6
6
  BashEvent,
7
7
  BashEventPromoCode,
8
+ BashEventType,
9
+ BashStatus,
10
+ BlogPost,
11
+ BlogCategory,
12
+ BlogTag,
13
+ BlogComment,
8
14
  Checkout,
15
+ Competition,
16
+ CompetitionSponsor,
9
17
  Contact,
18
+ ContactList,
10
19
  Coordinates,
11
20
  Demerit,
12
- EntertainmentService,
13
- EventService,
21
+ DocumentID,
14
22
  EventTask,
15
- Exhibitor,
16
23
  Invitation,
24
+ InvestmentType,
17
25
  Link,
18
26
  Media,
19
27
  Notification,
20
- Organization,
28
+ NotificationActionType,
29
+ NotificationPriority,
30
+ NotificationType,
21
31
  Prisma,
32
+ Prize,
33
+ PrizeType,
34
+ Promoter,
35
+ PromoterStats,
22
36
  Recurrence,
23
- Reminder,
24
37
  Review,
25
38
  Service,
26
39
  ServiceAddon,
@@ -51,14 +64,28 @@ import {
51
64
  TicketTier,
52
65
  TicketTransfer,
53
66
  User,
67
+ UserFollowing,
54
68
  UserPreferences,
69
+ UserPromoCodeRedemption,
70
+ UserReport,
55
71
  UserStats,
56
72
  UserSubscription,
57
73
  Vendor,
58
74
  Venue,
59
75
  VolunteerService,
76
+ EntertainmentService,
77
+ EventService,
78
+ Exhibitor,
79
+ Organization,
80
+ Reminder,
81
+ ServiceBookingStatus,
82
+ ServiceBookingType,
83
+ ServiceCondition,
84
+ ServiceStatus,
85
+ ServiceSubscriptionStatus,
86
+ ServiceTypes,
60
87
  } from "@prisma/client";
61
- import { SERVICE_LINK_DATA_TO_INCLUDE } from "./definitions";
88
+ import { SERVICE_LINK_DATA_TO_INCLUDE, BlogStatus, CommentStatus } from "./definitions";
62
89
  import { RemoveCommonProperties, UnionFromArray } from "./utils/typeUtils";
63
90
 
64
91
  export interface ApiResponse<T> {
@@ -839,3 +866,147 @@ export type PublicStripeAccount = Pick<
839
866
  StripeAccountExt,
840
867
  keyof typeof PUBLIC_STRIPE_ACCOUNT_DATA_TO_SELECT
841
868
  >;
869
+
870
+ //-------------------------------------------------------------------------------------------------------------------------------
871
+ // Blog Extended Schemas
872
+ //-------------------------------------------------------------------------------------------------------------------------------
873
+
874
+ export const BLOG_POST_DATA_TO_INCLUDE = {
875
+ author: {
876
+ select: FRONT_END_USER_DATA_TO_SELECT,
877
+ },
878
+ category: true,
879
+ tags: true,
880
+ comments: {
881
+ include: {
882
+ author: {
883
+ select: FRONT_END_USER_DATA_TO_SELECT,
884
+ },
885
+ replies: {
886
+ include: {
887
+ author: {
888
+ select: FRONT_END_USER_DATA_TO_SELECT,
889
+ },
890
+ },
891
+ },
892
+ },
893
+ where: {
894
+ status: 'Published',
895
+ },
896
+ orderBy: {
897
+ createdAt: 'desc',
898
+ },
899
+ },
900
+ } satisfies Prisma.BlogPostInclude;
901
+
902
+ export const BLOG_POST_PREVIEW_DATA_TO_INCLUDE = {
903
+ author: {
904
+ select: FRONT_END_USER_DATA_TO_SELECT,
905
+ },
906
+ category: true,
907
+ tags: true,
908
+ _count: {
909
+ select: {
910
+ comments: {
911
+ where: {
912
+ status: 'Published',
913
+ },
914
+ },
915
+ },
916
+ },
917
+ } satisfies Prisma.BlogPostInclude;
918
+
919
+ export interface BlogPostExt extends BlogPost {
920
+ author: PublicUser;
921
+ category?: BlogCategory | null;
922
+ tags: BlogTag[];
923
+ comments?: BlogCommentExt[];
924
+ _count?: {
925
+ comments: number;
926
+ };
927
+ }
928
+
929
+ export interface BlogCommentExt extends BlogComment {
930
+ author: PublicUser;
931
+ replies?: BlogCommentExt[];
932
+ }
933
+
934
+ export interface BlogCategoryExt extends BlogCategory {
935
+ _count?: {
936
+ posts: number;
937
+ };
938
+ }
939
+
940
+ export interface BlogTagExt extends BlogTag {
941
+ _count?: {
942
+ posts: number;
943
+ };
944
+ }
945
+
946
+ // Blog search and filtering types
947
+ export interface BlogSearchParams {
948
+ query?: string;
949
+ category?: string;
950
+ tag?: string;
951
+ author?: string;
952
+ status?: BlogStatus;
953
+ page?: number;
954
+ limit?: number;
955
+ sortBy?: 'createdAt' | 'publishedAt' | 'viewCount' | 'title';
956
+ sortOrder?: 'asc' | 'desc';
957
+ }
958
+
959
+ export interface BlogSearchResult {
960
+ posts: BlogPostExt[];
961
+ totalCount: number;
962
+ totalPages: number;
963
+ currentPage: number;
964
+ categories: BlogCategoryExt[];
965
+ tags: BlogTagExt[];
966
+ }
967
+
968
+ // Blog analytics types
969
+ export interface BlogAnalytics {
970
+ totalPosts: number;
971
+ totalViews: number;
972
+ totalComments: number;
973
+ popularPosts: BlogPostExt[];
974
+ popularCategories: BlogCategoryExt[];
975
+ popularTags: BlogTagExt[];
976
+ recentActivity: {
977
+ date: string;
978
+ views: number;
979
+ comments: number;
980
+ posts: number;
981
+ }[];
982
+ }
983
+
984
+ // Blog form types
985
+ export interface BlogPostFormData {
986
+ title: string;
987
+ slug?: string;
988
+ excerpt?: string;
989
+ content: string;
990
+ coverImage?: string;
991
+ metaTitle?: string;
992
+ metaDescription?: string;
993
+ metaKeywords: string[];
994
+ categoryId?: string;
995
+ tags: string[];
996
+ status: BlogStatus;
997
+ publishedAt?: Date;
998
+ scheduledFor?: Date;
999
+ }
1000
+
1001
+ export interface BlogCategoryFormData {
1002
+ name: string;
1003
+ slug?: string;
1004
+ description?: string;
1005
+ color?: string;
1006
+ }
1007
+
1008
+ export interface BlogTagFormData {
1009
+ name: string;
1010
+ slug?: string;
1011
+ color?: string;
1012
+ }
package/src/index.ts CHANGED
@@ -1,34 +1,45 @@
1
- export * from "./extendedSchemas";
2
1
  export * from "./definitions";
3
- export * from "./utils/ticketListUtils";
4
- export * from "./utils/dateTimeUtils";
5
- export * from "./utils/recurrenceUtils";
2
+ export * from "./extendedSchemas";
6
3
  export * from "./utils/addressUtils";
7
- export * from "./utils/paymentUtils";
8
- export * from "./utils/awsS3Utils";
9
- export * from "./utils/qrCodeUtils";
10
- export * from "./utils/sortUtils";
11
4
  export * from "./utils/apiUtils";
12
- export * from "./utils/urlUtils";
13
- export * from "./utils/stringUtils";
14
5
  export * from "./utils/arrayUtils";
6
+ export * from "./utils/awsS3Utils";
7
+ export * from "./utils/dateTimeUtils";
8
+ export * from "./utils/objUtils";
9
+ export * from "./utils/paymentUtils";
15
10
  export * from "./utils/promoCodesUtils";
16
- export * from "./utils/userPromoCodeUtils";
17
- export * from "./utils/userSubscriptionUtils";
11
+ export * from "./utils/qrCodeUtils";
12
+ export * from "./utils/recurrenceUtils";
13
+ export * from "./utils/service/attendeeOptionUtils";
18
14
  export * from "./utils/service/regexUtils";
19
- export * from "./utils/objUtils";
20
15
  export * from "./utils/service/serviceUtils";
21
16
  export * from "./utils/service/venueUtils";
22
- export * from "./utils/service/attendeeOptionUtils";
17
+ export * from "./utils/slugUtils";
18
+ export * from "./utils/sortUtils";
19
+ export * from "./utils/stringUtils";
20
+ export * from "./utils/ticketListUtils";
21
+ export * from "./utils/urlUtils";
22
+ export * from "./utils/userPromoCodeUtils";
23
+ export * from "./utils/userSubscriptionUtils";
23
24
  // export * from "./utils/service/serviceRateDBUtils";
24
- export * from "./utils/service/serviceDBUtils";
25
- export * from "./utils/service/serviceRateUtils";
26
- export * from "./utils/service/frontendServiceBookingUtils";
27
- export * from "./utils/service/apiServiceBookingApiUtils";
28
- export * from "./utils/service/serviceBookingStatusUtils";
29
- export * from "./utils/stripeAccountUtils";
25
+ export * from "./utils/blog/blogDbUtils";
26
+ export * from "./utils/blogUtils";
30
27
  export * from "./utils/entityUtils";
31
28
  export * from "./utils/generalDateTimeUtils";
32
- export * from "./utils/userUtils";
33
29
  export * from "./utils/luxonUtils";
34
30
  export * from "./utils/mathUtils";
31
+ export * from "./utils/service/apiServiceBookingApiUtils";
32
+ export * from "./utils/service/frontendServiceBookingUtils";
33
+ export * from "./utils/service/serviceBookingStatusUtils";
34
+ export * from "./utils/service/serviceDBUtils";
35
+ export * from "./utils/service/serviceRateUtils";
36
+ export * from "./utils/stripeAccountUtils";
37
+ export * from "./utils/userUtils";
38
+
39
+ // Export typeUtils types individually to avoid ValueOf conflict with definitions
40
+ export { createAllTrueObject } from "./utils/typeUtils";
41
+ export type {
42
+ DeepPartial, MakeOptional,
43
+ MakeRequired, Override, RemoveCommonProperties, UnionFromArray
44
+ } from "./utils/typeUtils";
45
+
@@ -0,0 +1,343 @@
1
+ import { PrismaClient, BlogPost, BlogCategory, BlogTag, BlogStatus } from '@prisma/client';
2
+ import { generateSlug, generateUniqueSlug } from '../slugUtils';
3
+ import { generateExcerpt, calculateReadingTime } from '../blogUtils';
4
+ import { BLOG_POST_DATA_TO_INCLUDE, BLOG_POST_PREVIEW_DATA_TO_INCLUDE, BlogPostExt, BlogSearchParams, BlogSearchResult } from '../../extendedSchemas';
5
+
6
+ // Blog slug utilities
7
+ export async function checkBlogSlugExists(
8
+ prisma: PrismaClient,
9
+ slug: string,
10
+ excludeId?: string
11
+ ): Promise<boolean> {
12
+ const existingPost = await prisma.blogPost.findFirst({
13
+ where: {
14
+ slug,
15
+ ...(excludeId && { id: { not: excludeId } }),
16
+ },
17
+ });
18
+ return !!existingPost;
19
+ }
20
+
21
+ export async function generateUniqueBlogSlug(
22
+ prisma: PrismaClient,
23
+ title: string,
24
+ excludeId?: string
25
+ ): Promise<string> {
26
+ return generateUniqueSlug(
27
+ title,
28
+ (slug, excludeId) => checkBlogSlugExists(prisma, slug, excludeId),
29
+ excludeId
30
+ );
31
+ }
32
+
33
+ // Blog CRUD operations
34
+ export async function createBlogPost(
35
+ prisma: PrismaClient,
36
+ data: {
37
+ title: string;
38
+ content: string;
39
+ authorId: string;
40
+ excerpt?: string;
41
+ coverImage?: string;
42
+ categoryId?: string;
43
+ tags?: string[];
44
+ metaTitle?: string;
45
+ metaDescription?: string;
46
+ metaKeywords?: string[];
47
+ status?: BlogStatus;
48
+ publishedAt?: Date;
49
+ scheduledFor?: Date;
50
+ }
51
+ ): Promise<BlogPostExt> {
52
+ const slug = await generateUniqueBlogSlug(prisma, data.title);
53
+ const excerpt = data.excerpt || generateExcerpt(data.content);
54
+ const readTime = calculateReadingTime(data.content);
55
+
56
+ // Handle tags
57
+ const tagConnections = data.tags ? await connectOrCreateTags(prisma, data.tags) : [];
58
+
59
+ const blogPost = await prisma.blogPost.create({
60
+ data: {
61
+ title: data.title,
62
+ slug,
63
+ content: data.content,
64
+ excerpt,
65
+ readTime,
66
+ authorId: data.authorId,
67
+ coverImage: data.coverImage,
68
+ categoryId: data.categoryId,
69
+ metaTitle: data.metaTitle,
70
+ metaDescription: data.metaDescription,
71
+ metaKeywords: data.metaKeywords || [],
72
+ status: data.status || BlogStatus.Draft,
73
+ publishedAt: data.status === BlogStatus.Published ? (data.publishedAt || new Date()) : data.publishedAt,
74
+ scheduledFor: data.scheduledFor,
75
+ tags: {
76
+ connect: tagConnections,
77
+ },
78
+ },
79
+ include: BLOG_POST_DATA_TO_INCLUDE,
80
+ });
81
+
82
+ return blogPost as BlogPostExt;
83
+ }
84
+
85
+ export async function updateBlogPost(
86
+ prisma: PrismaClient,
87
+ id: string,
88
+ data: {
89
+ title?: string;
90
+ content?: string;
91
+ excerpt?: string;
92
+ coverImage?: string;
93
+ categoryId?: string;
94
+ tags?: string[];
95
+ metaTitle?: string;
96
+ metaDescription?: string;
97
+ metaKeywords?: string[];
98
+ status?: BlogStatus;
99
+ publishedAt?: Date;
100
+ scheduledFor?: Date;
101
+ }
102
+ ): Promise<BlogPostExt> {
103
+ const updateData: any = { ...data };
104
+
105
+ // Generate new slug if title changed
106
+ if (data.title) {
107
+ updateData.slug = await generateUniqueBlogSlug(prisma, data.title, id);
108
+ }
109
+
110
+ // Update excerpt if content changed
111
+ if (data.content) {
112
+ updateData.excerpt = data.excerpt || generateExcerpt(data.content);
113
+ updateData.readTime = calculateReadingTime(data.content);
114
+ }
115
+
116
+ // Handle publishing
117
+ if (data.status === BlogStatus.Published && !data.publishedAt) {
118
+ updateData.publishedAt = new Date();
119
+ }
120
+
121
+ // Handle tags
122
+ if (data.tags) {
123
+ const tagConnections = await connectOrCreateTags(prisma, data.tags);
124
+ updateData.tags = {
125
+ set: tagConnections,
126
+ };
127
+ }
128
+
129
+ const blogPost = await prisma.blogPost.update({
130
+ where: { id },
131
+ data: updateData,
132
+ include: BLOG_POST_DATA_TO_INCLUDE,
133
+ });
134
+
135
+ return blogPost as BlogPostExt;
136
+ }
137
+
138
+ export async function getBlogPost(
139
+ prisma: PrismaClient,
140
+ id: string,
141
+ includeComments: boolean = true
142
+ ): Promise<BlogPostExt | null> {
143
+ const blogPost = await prisma.blogPost.findUnique({
144
+ where: { id },
145
+ include: includeComments ? BLOG_POST_DATA_TO_INCLUDE : BLOG_POST_PREVIEW_DATA_TO_INCLUDE,
146
+ });
147
+
148
+ return blogPost as BlogPostExt | null;
149
+ }
150
+
151
+ export async function getBlogPostBySlug(
152
+ prisma: PrismaClient,
153
+ slug: string,
154
+ includeComments: boolean = true
155
+ ): Promise<BlogPostExt | null> {
156
+ const blogPost = await prisma.blogPost.findUnique({
157
+ where: { slug },
158
+ include: includeComments ? BLOG_POST_DATA_TO_INCLUDE : BLOG_POST_PREVIEW_DATA_TO_INCLUDE,
159
+ });
160
+
161
+ return blogPost as BlogPostExt | null;
162
+ }
163
+
164
+ export async function searchBlogPosts(
165
+ prisma: PrismaClient,
166
+ params: BlogSearchParams
167
+ ): Promise<BlogSearchResult> {
168
+ const {
169
+ query,
170
+ category,
171
+ tag,
172
+ author,
173
+ status = BlogStatus.Published,
174
+ page = 1,
175
+ limit = 12,
176
+ sortBy = 'publishedAt',
177
+ sortOrder = 'desc',
178
+ } = params;
179
+
180
+ const skip = (page - 1) * limit;
181
+
182
+ // Build where clause
183
+ const where: any = {
184
+ status,
185
+ };
186
+
187
+ if (query) {
188
+ where.OR = [
189
+ { title: { contains: query, mode: 'insensitive' } },
190
+ { excerpt: { contains: query, mode: 'insensitive' } },
191
+ { content: { contains: query, mode: 'insensitive' } },
192
+ ];
193
+ }
194
+
195
+ if (category) {
196
+ where.category = { slug: category };
197
+ }
198
+
199
+ if (tag) {
200
+ where.tags = { some: { slug: tag } };
201
+ }
202
+
203
+ if (author) {
204
+ where.author = { username: author };
205
+ }
206
+
207
+ // Get posts
208
+ const posts = await prisma.blogPost.findMany({
209
+ where,
210
+ include: BLOG_POST_PREVIEW_DATA_TO_INCLUDE,
211
+ orderBy: { [sortBy]: sortOrder },
212
+ skip,
213
+ take: limit,
214
+ });
215
+
216
+ // Get total count
217
+ const totalCount = await prisma.blogPost.count({ where });
218
+
219
+ // Get categories and tags for filters
220
+ const [categories, tags] = await Promise.all([
221
+ prisma.blogCategory.findMany({
222
+ include: {
223
+ _count: {
224
+ select: {
225
+ posts: {
226
+ where: { status: BlogStatus.Published },
227
+ },
228
+ },
229
+ },
230
+ },
231
+ orderBy: { name: 'asc' },
232
+ }),
233
+ prisma.blogTag.findMany({
234
+ include: {
235
+ _count: {
236
+ select: {
237
+ posts: {
238
+ where: { status: BlogStatus.Published },
239
+ },
240
+ },
241
+ },
242
+ },
243
+ orderBy: { name: 'asc' },
244
+ }),
245
+ ]);
246
+
247
+ return {
248
+ posts: posts as BlogPostExt[],
249
+ totalCount,
250
+ totalPages: Math.ceil(totalCount / limit),
251
+ currentPage: page,
252
+ categories,
253
+ tags,
254
+ };
255
+ }
256
+
257
+ export async function incrementBlogPostViews(
258
+ prisma: PrismaClient,
259
+ id: string
260
+ ): Promise<void> {
261
+ await prisma.blogPost.update({
262
+ where: { id },
263
+ data: {
264
+ viewCount: {
265
+ increment: 1,
266
+ },
267
+ },
268
+ });
269
+ }
270
+
271
+ // Category utilities
272
+ export async function createBlogCategory(
273
+ prisma: PrismaClient,
274
+ data: {
275
+ name: string;
276
+ description?: string;
277
+ color?: string;
278
+ }
279
+ ): Promise<BlogCategory> {
280
+ const slug = generateSlug(data.name);
281
+
282
+ return prisma.blogCategory.create({
283
+ data: {
284
+ name: data.name,
285
+ slug,
286
+ description: data.description,
287
+ color: data.color,
288
+ },
289
+ });
290
+ }
291
+
292
+ // Tag utilities
293
+ export async function connectOrCreateTags(
294
+ prisma: PrismaClient,
295
+ tagNames: string[]
296
+ ): Promise<{ id: string }[]> {
297
+ const connections: { id: string }[] = [];
298
+
299
+ for (const tagName of tagNames) {
300
+ const slug = generateSlug(tagName);
301
+
302
+ const tag = await prisma.blogTag.upsert({
303
+ where: { slug },
304
+ update: {},
305
+ create: {
306
+ name: tagName,
307
+ slug,
308
+ },
309
+ });
310
+
311
+ connections.push({ id: tag.id });
312
+ }
313
+
314
+ return connections;
315
+ }
316
+
317
+ export async function getPopularBlogPosts(
318
+ prisma: PrismaClient,
319
+ limit: number = 5
320
+ ): Promise<BlogPostExt[]> {
321
+ const posts = await prisma.blogPost.findMany({
322
+ where: { status: BlogStatus.Published },
323
+ include: BLOG_POST_PREVIEW_DATA_TO_INCLUDE,
324
+ orderBy: { viewCount: 'desc' },
325
+ take: limit,
326
+ });
327
+
328
+ return posts as BlogPostExt[];
329
+ }
330
+
331
+ export async function getRecentBlogPosts(
332
+ prisma: PrismaClient,
333
+ limit: number = 5
334
+ ): Promise<BlogPostExt[]> {
335
+ const posts = await prisma.blogPost.findMany({
336
+ where: { status: BlogStatus.Published },
337
+ include: BLOG_POST_PREVIEW_DATA_TO_INCLUDE,
338
+ orderBy: { publishedAt: 'desc' },
339
+ take: limit,
340
+ });
341
+
342
+ return posts as BlogPostExt[];
343
+ }