@newcms/database 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -2692,4 +2692,72 @@ declare class RevisionRepository {
2692
2692
  private hasChanges;
2693
2693
  }
2694
2694
 
2695
- export { type CacheGroupConfig, type CreatePostInput, type CreateTermInput, type Database, type DatabaseConfig, type MetaColumnNames, type MetaEntry, MetaRepository, type MetaTableColumns, ObjectCache, type ObjectCacheConfig, OptionsRepository, PostRepository, type PostRow, RevisionRepository, TaxonomyRepository, type TermRow, type UpdatePostInput, commentmeta, comments, createConnection, links, options, postmeta, posts, scheduledEvents, sessions, termRelationships, termTaxonomy, termmeta, terms, usermeta, users };
2695
+ interface CreateCommentInput {
2696
+ commentPostId: number;
2697
+ commentAuthor: string;
2698
+ commentAuthorEmail?: string;
2699
+ commentAuthorUrl?: string;
2700
+ commentAuthorIp?: string;
2701
+ commentContent: string;
2702
+ commentType?: string;
2703
+ commentParent?: number;
2704
+ userId?: number;
2705
+ commentApproved?: string;
2706
+ }
2707
+ interface CommentRow {
2708
+ commentId: number;
2709
+ commentPostId: number;
2710
+ commentAuthor: string;
2711
+ commentAuthorEmail: string;
2712
+ commentAuthorUrl: string;
2713
+ commentAuthorIp: string;
2714
+ commentDate: Date;
2715
+ commentDateGmt: Date;
2716
+ commentContent: string;
2717
+ commentKarma: number;
2718
+ commentApproved: string;
2719
+ commentAgent: string;
2720
+ commentType: string;
2721
+ commentParent: number;
2722
+ userId: number;
2723
+ }
2724
+ interface CommentModerationRules {
2725
+ blockedWords?: string[];
2726
+ moderationWords?: string[];
2727
+ maxLinks?: number;
2728
+ floodSeconds?: number;
2729
+ }
2730
+ declare class CommentRepository {
2731
+ private db;
2732
+ readonly meta: MetaRepository;
2733
+ constructor(db: Database);
2734
+ create(input: CreateCommentInput): Promise<CommentRow>;
2735
+ getById(id: number): Promise<CommentRow | undefined>;
2736
+ getByPost(postId: number, status?: string): Promise<CommentRow[]>;
2737
+ /**
2738
+ * Get threaded comments — returns flat list, client builds the tree
2739
+ * using commentParent references.
2740
+ */
2741
+ getThreaded(postId: number, _depth?: number): Promise<CommentRow[]>;
2742
+ approve(id: number): Promise<boolean>;
2743
+ unapprove(id: number): Promise<boolean>;
2744
+ spam(id: number): Promise<boolean>;
2745
+ trash(id: number): Promise<boolean>;
2746
+ deletePermanently(id: number): Promise<boolean>;
2747
+ /**
2748
+ * Check moderation rules before approving a comment.
2749
+ */
2750
+ moderate(content: string, _authorEmail: string, rules: CommentModerationRules): 'approve' | 'hold' | 'spam';
2751
+ /**
2752
+ * Check for duplicate comment.
2753
+ */
2754
+ isDuplicate(postId: number, authorEmail: string, content: string): Promise<boolean>;
2755
+ /**
2756
+ * Check flood protection — has user commented too recently?
2757
+ */
2758
+ isFlooding(authorIp: string, floodSeconds: number): Promise<boolean>;
2759
+ countByStatus(postId?: number): Promise<Record<string, number>>;
2760
+ private updatePostCommentCount;
2761
+ }
2762
+
2763
+ export { type CacheGroupConfig, type CommentModerationRules, CommentRepository, type CommentRow, type CreateCommentInput, type CreatePostInput, type CreateTermInput, type Database, type DatabaseConfig, type MetaColumnNames, type MetaEntry, MetaRepository, type MetaTableColumns, ObjectCache, type ObjectCacheConfig, OptionsRepository, PostRepository, type PostRow, RevisionRepository, TaxonomyRepository, type TermRow, type UpdatePostInput, commentmeta, comments, createConnection, links, options, postmeta, posts, scheduledEvents, sessions, termRelationships, termTaxonomy, termmeta, terms, usermeta, users };
package/dist/index.d.ts CHANGED
@@ -2692,4 +2692,72 @@ declare class RevisionRepository {
2692
2692
  private hasChanges;
2693
2693
  }
2694
2694
 
2695
- export { type CacheGroupConfig, type CreatePostInput, type CreateTermInput, type Database, type DatabaseConfig, type MetaColumnNames, type MetaEntry, MetaRepository, type MetaTableColumns, ObjectCache, type ObjectCacheConfig, OptionsRepository, PostRepository, type PostRow, RevisionRepository, TaxonomyRepository, type TermRow, type UpdatePostInput, commentmeta, comments, createConnection, links, options, postmeta, posts, scheduledEvents, sessions, termRelationships, termTaxonomy, termmeta, terms, usermeta, users };
2695
+ interface CreateCommentInput {
2696
+ commentPostId: number;
2697
+ commentAuthor: string;
2698
+ commentAuthorEmail?: string;
2699
+ commentAuthorUrl?: string;
2700
+ commentAuthorIp?: string;
2701
+ commentContent: string;
2702
+ commentType?: string;
2703
+ commentParent?: number;
2704
+ userId?: number;
2705
+ commentApproved?: string;
2706
+ }
2707
+ interface CommentRow {
2708
+ commentId: number;
2709
+ commentPostId: number;
2710
+ commentAuthor: string;
2711
+ commentAuthorEmail: string;
2712
+ commentAuthorUrl: string;
2713
+ commentAuthorIp: string;
2714
+ commentDate: Date;
2715
+ commentDateGmt: Date;
2716
+ commentContent: string;
2717
+ commentKarma: number;
2718
+ commentApproved: string;
2719
+ commentAgent: string;
2720
+ commentType: string;
2721
+ commentParent: number;
2722
+ userId: number;
2723
+ }
2724
+ interface CommentModerationRules {
2725
+ blockedWords?: string[];
2726
+ moderationWords?: string[];
2727
+ maxLinks?: number;
2728
+ floodSeconds?: number;
2729
+ }
2730
+ declare class CommentRepository {
2731
+ private db;
2732
+ readonly meta: MetaRepository;
2733
+ constructor(db: Database);
2734
+ create(input: CreateCommentInput): Promise<CommentRow>;
2735
+ getById(id: number): Promise<CommentRow | undefined>;
2736
+ getByPost(postId: number, status?: string): Promise<CommentRow[]>;
2737
+ /**
2738
+ * Get threaded comments — returns flat list, client builds the tree
2739
+ * using commentParent references.
2740
+ */
2741
+ getThreaded(postId: number, _depth?: number): Promise<CommentRow[]>;
2742
+ approve(id: number): Promise<boolean>;
2743
+ unapprove(id: number): Promise<boolean>;
2744
+ spam(id: number): Promise<boolean>;
2745
+ trash(id: number): Promise<boolean>;
2746
+ deletePermanently(id: number): Promise<boolean>;
2747
+ /**
2748
+ * Check moderation rules before approving a comment.
2749
+ */
2750
+ moderate(content: string, _authorEmail: string, rules: CommentModerationRules): 'approve' | 'hold' | 'spam';
2751
+ /**
2752
+ * Check for duplicate comment.
2753
+ */
2754
+ isDuplicate(postId: number, authorEmail: string, content: string): Promise<boolean>;
2755
+ /**
2756
+ * Check flood protection — has user commented too recently?
2757
+ */
2758
+ isFlooding(authorIp: string, floodSeconds: number): Promise<boolean>;
2759
+ countByStatus(postId?: number): Promise<Record<string, number>>;
2760
+ private updatePostCommentCount;
2761
+ }
2762
+
2763
+ export { type CacheGroupConfig, type CommentModerationRules, CommentRepository, type CommentRow, type CreateCommentInput, type CreatePostInput, type CreateTermInput, type Database, type DatabaseConfig, type MetaColumnNames, type MetaEntry, MetaRepository, type MetaTableColumns, ObjectCache, type ObjectCacheConfig, OptionsRepository, PostRepository, type PostRow, RevisionRepository, TaxonomyRepository, type TermRow, type UpdatePostInput, commentmeta, comments, createConnection, links, options, postmeta, posts, scheduledEvents, sessions, termRelationships, termTaxonomy, termmeta, terms, usermeta, users };
package/dist/index.js CHANGED
@@ -1503,7 +1503,161 @@ var RevisionRepository = class {
1503
1503
  return false;
1504
1504
  }
1505
1505
  };
1506
+
1507
+ // src/repositories/comment-repository.ts
1508
+ import { eq as eq6, and as and5, sql as sql5 } from "drizzle-orm";
1509
+ var CommentRepository = class {
1510
+ constructor(db) {
1511
+ this.db = db;
1512
+ const metaCols = {
1513
+ metaId: commentmeta.metaId,
1514
+ objectId: commentmeta.commentId,
1515
+ metaKey: commentmeta.metaKey,
1516
+ metaValue: commentmeta.metaValue,
1517
+ metaValueJson: commentmeta.metaValueJson
1518
+ };
1519
+ const colNames = {
1520
+ table: "commentmeta",
1521
+ sql: { metaId: "meta_id", objectId: "comment_id", metaKey: "meta_key", metaValue: "meta_value", metaValueJson: "meta_value_json" },
1522
+ ts: { metaId: "metaId", objectId: "commentId", metaKey: "metaKey", metaValue: "metaValue", metaValueJson: "metaValueJson" }
1523
+ };
1524
+ this.meta = new MetaRepository(db, commentmeta, metaCols, colNames);
1525
+ }
1526
+ db;
1527
+ meta;
1528
+ async create(input) {
1529
+ const now = /* @__PURE__ */ new Date();
1530
+ const [row] = await this.db.insert(comments).values({
1531
+ commentPostId: input.commentPostId,
1532
+ commentAuthor: input.commentAuthor,
1533
+ commentAuthorEmail: input.commentAuthorEmail ?? "",
1534
+ commentAuthorUrl: input.commentAuthorUrl ?? "",
1535
+ commentAuthorIp: input.commentAuthorIp ?? "",
1536
+ commentContent: input.commentContent,
1537
+ commentType: input.commentType ?? "comment",
1538
+ commentParent: input.commentParent ?? 0,
1539
+ userId: input.userId ?? 0,
1540
+ commentApproved: input.commentApproved ?? "1",
1541
+ commentDate: now,
1542
+ commentDateGmt: now
1543
+ }).returning();
1544
+ await this.updatePostCommentCount(input.commentPostId);
1545
+ return row;
1546
+ }
1547
+ async getById(id) {
1548
+ const rows = await this.db.select().from(comments).where(eq6(comments.commentId, id)).limit(1);
1549
+ return rows[0];
1550
+ }
1551
+ async getByPost(postId, status) {
1552
+ const conditions = [eq6(comments.commentPostId, postId)];
1553
+ if (status) conditions.push(eq6(comments.commentApproved, status));
1554
+ return await this.db.select().from(comments).where(and5(...conditions)).orderBy(comments.commentDate);
1555
+ }
1556
+ /**
1557
+ * Get threaded comments — returns flat list, client builds the tree
1558
+ * using commentParent references.
1559
+ */
1560
+ async getThreaded(postId, _depth = 5) {
1561
+ return this.getByPost(postId, "1");
1562
+ }
1563
+ async approve(id) {
1564
+ const comment = await this.getById(id);
1565
+ if (!comment) return false;
1566
+ await this.db.update(comments).set({ commentApproved: "1" }).where(eq6(comments.commentId, id));
1567
+ await this.updatePostCommentCount(comment.commentPostId);
1568
+ return true;
1569
+ }
1570
+ async unapprove(id) {
1571
+ const comment = await this.getById(id);
1572
+ if (!comment) return false;
1573
+ await this.db.update(comments).set({ commentApproved: "0" }).where(eq6(comments.commentId, id));
1574
+ await this.updatePostCommentCount(comment.commentPostId);
1575
+ return true;
1576
+ }
1577
+ async spam(id) {
1578
+ const comment = await this.getById(id);
1579
+ if (!comment) return false;
1580
+ await this.db.update(comments).set({ commentApproved: "spam" }).where(eq6(comments.commentId, id));
1581
+ await this.updatePostCommentCount(comment.commentPostId);
1582
+ return true;
1583
+ }
1584
+ async trash(id) {
1585
+ const comment = await this.getById(id);
1586
+ if (!comment) return false;
1587
+ await this.meta.update(id, "_trash_status", comment.commentApproved);
1588
+ await this.db.update(comments).set({ commentApproved: "trash" }).where(eq6(comments.commentId, id));
1589
+ await this.updatePostCommentCount(comment.commentPostId);
1590
+ return true;
1591
+ }
1592
+ async deletePermanently(id) {
1593
+ const comment = await this.getById(id);
1594
+ if (!comment) return false;
1595
+ await this.meta.deleteAllForObject(id);
1596
+ const result = await this.db.delete(comments).where(eq6(comments.commentId, id)).returning({ commentId: comments.commentId });
1597
+ if (result.length > 0) await this.updatePostCommentCount(comment.commentPostId);
1598
+ return result.length > 0;
1599
+ }
1600
+ /**
1601
+ * Check moderation rules before approving a comment.
1602
+ */
1603
+ moderate(content, _authorEmail, rules) {
1604
+ const lower = content.toLowerCase();
1605
+ if (rules.blockedWords) {
1606
+ for (const word of rules.blockedWords) {
1607
+ if (lower.includes(word.toLowerCase())) return "spam";
1608
+ }
1609
+ }
1610
+ if (rules.moderationWords) {
1611
+ for (const word of rules.moderationWords) {
1612
+ if (lower.includes(word.toLowerCase())) return "hold";
1613
+ }
1614
+ }
1615
+ if (rules.maxLinks !== void 0) {
1616
+ const linkCount = (content.match(/https?:\/\//g) ?? []).length;
1617
+ if (linkCount > rules.maxLinks) return "hold";
1618
+ }
1619
+ return "approve";
1620
+ }
1621
+ /**
1622
+ * Check for duplicate comment.
1623
+ */
1624
+ async isDuplicate(postId, authorEmail, content) {
1625
+ const rows = await this.db.select({ commentId: comments.commentId }).from(comments).where(
1626
+ and5(
1627
+ eq6(comments.commentPostId, postId),
1628
+ eq6(comments.commentAuthorEmail, authorEmail),
1629
+ eq6(comments.commentContent, content)
1630
+ )
1631
+ ).limit(1);
1632
+ return rows.length > 0;
1633
+ }
1634
+ /**
1635
+ * Check flood protection — has user commented too recently?
1636
+ */
1637
+ async isFlooding(authorIp, floodSeconds) {
1638
+ const cutoff = new Date(Date.now() - floodSeconds * 1e3);
1639
+ const rows = await this.db.select({ commentId: comments.commentId }).from(comments).where(
1640
+ and5(
1641
+ eq6(comments.commentAuthorIp, authorIp),
1642
+ sql5`${comments.commentDate} > ${cutoff}`
1643
+ )
1644
+ ).limit(1);
1645
+ return rows.length > 0;
1646
+ }
1647
+ async countByStatus(postId) {
1648
+ const conditions = postId !== void 0 ? [eq6(comments.commentPostId, postId)] : [];
1649
+ const rows = await this.db.select({ status: comments.commentApproved, count: sql5`count(*)::int` }).from(comments).where(conditions.length > 0 ? and5(...conditions) : void 0).groupBy(comments.commentApproved);
1650
+ const result = {};
1651
+ for (const row of rows) result[row.status] = row.count;
1652
+ return result;
1653
+ }
1654
+ async updatePostCommentCount(postId) {
1655
+ const [result] = await this.db.select({ count: sql5`count(*)::int` }).from(comments).where(and5(eq6(comments.commentPostId, postId), eq6(comments.commentApproved, "1")));
1656
+ await this.db.update(posts).set({ commentCount: result.count }).where(eq6(posts.id, postId));
1657
+ }
1658
+ };
1506
1659
  export {
1660
+ CommentRepository,
1507
1661
  MetaRepository,
1508
1662
  ObjectCache,
1509
1663
  OptionsRepository,