Skip to content

(modeling commons): Comments#43

Merged
omargfh merged 51 commits into
staging/modeling-commonsfrom
feature/modeling-commons/comments
Jul 21, 2026
Merged

(modeling commons): Comments#43
omargfh merged 51 commits into
staging/modeling-commonsfrom
feature/modeling-commons/comments

Conversation

@omargfh

@omargfh omargfh commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

omargfh added 30 commits July 10, 2026 16:54
- createComment factory (uuid id, trimmed content validation via
  rules.limits.comment.content), assertNotDeleted,
  assertParentMatchesModel, assertCanEdit, assertCanDelete
- CommentNotFoundError, CommentDeletedError, CommentBodyInvalidError,
  ParentCommentMismatchError
- COMMENT_TREE_DEFAULTS (2 replies x 3 levels) for the future query layer
- create/update-comment request DTOs, sourcing content bounds from
  rules.ts (limits.comment.content) instead of a duplicated literal
- list-comments query DTO intersecting the shared paginated query with
  a createdAt/updatedAt/likes sort
- recursive comment response DTO (Type.Cyclic + Type.Ref, this
  typebox version has no Type.Recursive) with a nested Paginated<Comment>
  replies envelope, plus its paginated-list wrapper
- model-comment.schemas.ts route params, naming the model param `id`
  (not `modelId`) to match resolveModel()'s hard-read of request.params.id
- Prisma-backed ModelCommentRepository (port, impl, mock) mirroring
  model-author's file split; every read hydrates user + likedByMe
  via a single filtered _count include, no per-row round trips.
- listTopLevel/listReplies use paginate() with one findMany + count;
  addLikeTx/removeLikeTx keep likesCount in sync with the like rows.
- Extend ModelCommentEntity with optional user/likedByMe so the
  repository's hydrated reads and plain writes share one type.
…ations

- Add create/updateContent/softDelete/like/unlike, each transactional
  with model_comment.created/updated/deleted event audit; likes are
  not audited.
- Post-commit, fire-and-forget email notification to a model's
  authors (minus the commenter, plus a differing parent author on
  replies) via the mail module.
- Add model_comment.created/updated/deleted to the shared KnownEvents
  union.
- Add src/modules/model-comment/index.ts to wire the module's DI types
  (repository, domain, service) into the global Dependencies interface.
- Register all seven /v1/models/:id/comments... routes, mirroring
  model-author.route.ts's fastify.get/post/patch/delete style.
- Complete the awilix Dependencies declaration in model-comment/index.ts
  (modelCommentMapper, listCommentsQuery, getCommentQuery), moving the
  mapper's inline declare global into it so there's a single source.
Covers create/list/get/edit/delete/like routes under
/v1/models/:id/comments per doc/discussion-plan.md §13: tombstones,
bounded reply-tree embedding (2 x 3 levels), auth/permission checks,
per-author email notification (mail-capture spy on mailService), and
Event audit rows for create/update/delete with none for likes.

Not executed in this environment: Docker/postgres is unavailable here,
so the hand-authored add_model_comment migration remains unverified
and this suite has not been run against a real database.
…access

Add resolveComment preHandler hook that 404s when :commentId doesn't
belong to the :id model, closing an IDOR where a comment from a
private model could be read via a public model's path.
sendMail's param type was accidentally the nodemailer Mail class
(instance methods/props) instead of Mail.Options, so no plain content
object could ever satisfy it structurally, forcing the `as never`
cast. Point unsubscribeUrl at the real support inbox instead of a
non-existent /unsubscribe route.
expandCommentTree exposed replies.count from the raw DB page.count even
after childless tombstone replies were filtered from replies.data. A
deleted node whose only reply was itself a childless tombstone kept
replies.count:1 with an empty data array, so the top-level filter (which
keys off replies !== undefined) wrongly kept the tombstone, and the
frontend rendered a "Continue this thread (1)" that fetched nothing.

Now the count is corrected for replies dropped from the fetched page,
and replies is left unset when the corrected count is 0 - consistent
for both deleted and live nodes, and shared by get-comment.query.ts via
the same expandCommentTree.
omargfh added 21 commits July 20, 2026 15:22
…n openapi.json

Registers the recursive comment schema via fastify.addSchema with a stable
$id (mirrors the ApiErrorResponse pattern) instead of inlining the cyclic
Type.Cyclic schema into both the single-comment and paginated-list routes.
@fastify/swagger only hoists addSchema'd schemas into components/schemas,
so the inlined copies left the internal replies.data $ref dangling and
broke generate:types (openapi-typescript couldn't resolve it).
… likes

- expandCommentTree no longer drops childless tombstones; reports raw reply count
- sort default is likes (desc); drop the unused updatedAt option
- reconcile query specs to the always-keep behavior
- migrateLegacyComments maps postings -> ModelComment via legacyId (idempotent)
- two-pass parent linking; soft-deleted postings imported as tombstones
- skips postings whose model was not migrated; drops Q&A flags and legacy title
- adds ModelComment(+Like) to WIPE_TARGET truncate list and report counters
- legacy postings stored <->&lt; and >->&rt; (non-standard) with CRLF endings
- decodeLegacyBody unescapes entities and normalizes newlines to LF
- new frontend renders plaintext, so raw entities/CRLF showed literally
- new sort=newest maps to createdAt desc
- repository toOrderBy now honors orderBy.param instead of hardcoding asc
- createdAt stays asc (oldest-first), likes stays desc (default)
@omargfh
omargfh marked this pull request as ready for review July 21, 2026 21:12
@omargfh
omargfh merged commit 58f7512 into staging/modeling-commons Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant