Skip to main content

LessonThread

Lesson Q&A forum β€” thread/reply payload validation + 1-level reply depth + action policy (Layer 3 Domain).

Status​

KeyValue
Layerdomain
TierL1
Statuswip
Version0.9.0
PriceFree (free)
CategoryCommunity

Overview​

Overview​

LessonThread is multi-saas-kit's first Layer 3 Domain Plugin. It standardizes per-lesson Q&A forum thread/reply payload validation and action policy.

Reuses Annotation's AuthorRole enum for permission branching.

Core Components​

ThreadPayloadValidator (Pure)​

Extracted from academy.how's LessonThreadController::store / storeReply.

Thread validation (validateThread):

  • title 1~max(255), trim, empty rejected
  • body 1~max(20_000)
  • Multibyte-friendly (mb_strlen)

Reply validation (validateReply):

  • Thread is_locked rejected
  • body 1~max(20_000)
  • parent_id from different thread β†’ parent_invalid
  • parent already a child reply β†’ reply_depth_exceeded (1-level only, configurable)
  • Staff reply auto-marked is_teacher_reply=true
  • Empty/blank parent_id treated as top-level

ThreadActionPolicy (Pure)​

Extracted from academy's policies. Decides on role + ownership only (tenant/saas isolation is caller's responsibility).

ActionPolicy
canCreateThreadRole required
canUpdateThread / canDeleteThreadOwner OR staff
canTogglePin / canToggleLockStaff only by default (configurable)
canCreateReplyOpen = anyone, locked = staff
canUpdateReply / canDeleteReplyOwner OR staff
canToggleAcceptStaff always. Thread owner also if accept_staff_only=false

Exception​

InvalidThreadPayloadException β€” reason field for i18n.

Configuration​

return [
'enabled' => env('PLG_LESSON_THREAD_ENABLED', true),
'limits' => [
'title_max' => 255,
'thread_body_max' => 20_000,
'reply_body_max' => 20_000,
],
'replies' => [
'max_depth' => 1,
'accept_staff_only' => true,
],
'admin_actions' => [
'pin_staff_only' => true,
'lock_staff_only' => true,
],
];

Usage​

$validator = ThreadPayloadValidator::fromConfig(config('lesson-thread'));
$policy = ThreadActionPolicy::fromConfig(config('lesson-thread'));

if (! $policy->canCreateThread($role)) abort(403);
$normalized = $validator->validateThread($request->all(), $role);

Origin​

Extracted from academy.how's LessonThread + LessonThreadReply Model + Controller + Policies. Eloquent dependencies removed.

Dependencies​

  • Annotation β€” reuses AuthorRole enum

Roadmap (Phase 3+)​

  • Standard Thread + ThreadReply Model + Migration (opt-in)
  • Filament Resource
  • RichEditorSanitizer integration
  • Cross-link with Webbook plugin

License​

MIT

Dependencies​

Demos​


πŸ›’ View on Plugin Store: store.codebase.how/plugins/lesson-thread