Yomu
Java Backend

Forum Diskusi

Backend forum Java - komentar artikel, nested replies, reaction toggle, dan integrasi frontend

Modul forum menyediakan diskusi per artikel melalui komentar berjenjang, edit/delete komentar, dan reaction toggle. Implementasinya berada di package forum pada backend Java dan diakses frontend melalui route handler Next.js.

Overview backend Java sudah menyebut fitur forum, tetapi sebelumnya belum ada halaman khusus di sidebar. Halaman ini mendokumentasikan implementasi aktual di yomu-backend-java/src/main/java/id/ac/ui/cs/advprog/yomubackendjava/forum.

Ringkasan Arsitektur

Komponen utama:

KomponenTanggung jawab
CommentControllerMengekspos endpoint REST /api/v1/forums dan membungkus response dengan ApiResponse<T>
CommentServiceValidasi artikel, create/list/update/delete komentar, sanitasi HTML, dan mapping response nested
CommentReactionServiceToggle reaction per user, pessimistic lock, dan penghapusan vote berlawanan
CommentRepositoryQuery root comment per artikel, urut terbaru
CommentReactionRepositoryCount reaction, lookup reaction user, dan lock baris saat toggle
ReactionStrategyFactoryMemilih strategy berdasarkan ReactionType

Endpoint

MethodPathAuthKeterangan
GET/api/v1/forums/{articleId}/commentsPublicAmbil root comments dan replies nested
POST/api/v1/forums/{articleId}/commentsJWTBuat root comment atau reply
PUT/api/v1/forums/comments/{commentId}JWT pemilikUbah isi komentar
DELETE/api/v1/forums/comments/{commentId}JWT pemilik/AdminHapus komentar
POST/api/v1/forums/comments/{commentId}/reactionsJWTToggle reaction
GET/api/v1/forums/comments/{commentId}/reactionsPublicAmbil count UPVOTE untuk komentar

Semua response mengikuti kontrak umum:

{
  "success": true,
  "message": "Komentar berhasil diambil",
  "data": {}
}

Endpoint tulis mengambil user dari CurrentUser, sehingga token JWT wajib valid. Frontend tidak mengirim token dari JavaScript client; route handler Next.js membaca cookie yomu_access_token lalu meneruskan header Authorization: Bearer <token> ke backend Java.

Model Data

Comment

Comment disimpan di tabel comments.

FieldTypeKeterangan
idUUIDPrimary key auto-generated
article_idStringID artikel dari modul bacaan/kuis
user_idUUIDPemilik komentar dari JWT
parent_comment_idUUID/nullParent untuk reply
contentTEXTIsi komentar, disanitasi dengan SecuritySanitizer.html
created_atInstantDiisi otomatis saat insert
repliesList of CommentRelasi child comment, cascade delete dari parent

Sebelum membuat atau mengambil komentar, CommentService memanggil ArticleService.findById(articleId). Jika artikel tidak ada, request gagal sebagai not found dari modul artikel.

CommentReaction

CommentReaction disimpan di tabel comment_reactions.

FieldTypeKeterangan
idUUIDPrimary key auto-generated
comment_idUUIDID komentar
user_idUUIDUser pemberi reaction
reaction_typeEnumUPVOTE, DOWNVOTE, atau EMOJI

Constraint unik comment_id + user_id + reaction_type memastikan satu user hanya punya satu reaction untuk tipe yang sama pada satu komentar.

Komentar

Request create comment:

{
  "parent_comment_id": null,
  "content": "Menurut saya bagian ini menarik."
}

Validasi:

FieldRule
contentWajib, tidak blank, maksimal 5000 karakter
parent_comment_idOpsional, harus menunjuk komentar yang ada dan berada di artikel yang sama

Response comment:

{
  "id": "7f64e7a8-7f38-4a82-92be-62d3d0f0b1ef",
  "article_id": "art-eco-hutan-kota",
  "user_id": "3b9d1a20-37d2-4dd3-bf0e-0e04d36f16ef",
  "parent_comment_id": null,
  "content": "Menurut saya bagian ini menarik.",
  "created_at": "2026-05-22T09:00:00Z",
  "reaction_count": 2,
  "upvote_count": 2,
  "downvote_count": 0,
  "emoji_count": 1,
  "clan_name": "Aksara",
  "tier": "Gold",
  "replies": []
}

reaction_count saat ini berisi jumlah UPVOTE agar kompatibel dengan kontrak awal. Count lengkap tersedia lewat upvote_count, downvote_count, dan emoji_count.

Rules Akses

OperasiRule
List komentarPublic, tetap memvalidasi artikel
Create komentarUser login
ReplyUser login, parent harus satu artikel
Update komentarHanya pemilik komentar
Delete komentarPemilik komentar atau user dengan role ADMIN

Delete memakai commentRepository.delete(comment). Replies ikut terhapus karena relasi Comment.replies memakai cascade = CascadeType.ALL dan orphanRemoval = true.

Reaction

Request toggle reaction:

{
  "reaction_type": "UPVOTE"
}

Nilai reaction_type yang didukung implementasi saat ini:

TypePerilaku
UPVOTEToggle upvote dan menghapus DOWNVOTE user yang sama jika ada
DOWNVOTEToggle downvote dan menghapus UPVOTE user yang sama jika ada
EMOJIToggle emoji secara independen

Response toggle:

{
  "comment_id": "7f64e7a8-7f38-4a82-92be-62d3d0f0b1ef",
  "reaction_type": "UPVOTE",
  "reacted": true,
  "reaction_count": 3
}

CommentReactionService memakai query findByCommentIdAndUserIdAndReactionTypeForUpdate dengan PESSIMISTIC_WRITE agar toggle reaction aman saat request paralel.

Endpoint GET /api/v1/forums/comments/{commentId}/reactions pada controller masih mengembalikan count UPVOTE. Untuk tampilan multi-reaction, frontend memakai count yang sudah tersedia pada CommentResponse.

Integrasi Frontend

Frontend forum berada di yomu-frontend:

FilePeran
src/app/forums/[articleId]/page.tsxHalaman diskusi artikel
src/components/forum/CommentList.tsxRender nested comments, edit/delete, reply, upvote/downvote
src/components/forum/CommentForm.tsxForm create comment dan reply
src/lib/api/forum.tsClient typed untuk endpoint forum
src/app/api/v1/forums/**/route.tsRoute handlers yang berkomunikasi dengan Java

Alur request:

CommentList juga memanggil endpoint me() untuk mengetahui user saat ini. Data tersebut dipakai untuk menentukan tombol edit/delete: pemilik komentar bisa edit, sedangkan pemilik atau ADMIN bisa delete.

Testing

Backend Java memiliki test khusus forum:

TestFokus
CommentControllerTestKontrak endpoint komentar
CommentReactionControllerTestKontrak endpoint reaction
CommentServiceTestValidasi artikel, ownership, nested replies, dan mapping response
CommentReactionServiceTestToggle reaction, count, dan opposite vote
ReactionStrategyTestResolusi strategy berdasarkan ReactionType

On this page