docs(10): create phase plan
Phase 10: Core Plugins - 4 plans in 2 waves - Wave 1: 10-01 (User auth), 10-03 (Blog posts) - parallel - Wave 2: 10-02 (User profiles), 10-04 (Blog categories/tags) - sequential - Ready for execution
This commit is contained in:
267
.planning/phases/10-core-plugins/10-02-PLAN.md
Normal file
267
.planning/phases/10-core-plugins/10-02-PLAN.md
Normal file
@@ -0,0 +1,267 @@
|
||||
---
|
||||
phase: 10-core-plugins
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["10-01"]
|
||||
files_modified:
|
||||
- plugins/golem15/user/components/ResetPassword.scala
|
||||
- plugins/golem15/user/services/FrontendUserService.scala
|
||||
- plugins/golem15/user/services/UserMailService.scala
|
||||
- plugins/golem15/user/resources/views/account/profile.peb
|
||||
- plugins/golem15/user/resources/views/account/avatar.peb
|
||||
- plugins/golem15/user/resources/views/resetpassword/request.peb
|
||||
- plugins/golem15/user/resources/views/resetpassword/reset.peb
|
||||
- plugins/golem15/user/resources/views/mail/activation.peb
|
||||
- plugins/golem15/user/resources/views/mail/reset.peb
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Logged-in user can view and edit their profile"
|
||||
- "User can change password with current password verification"
|
||||
- "User can upload and change avatar"
|
||||
- "Visitor can request password reset via email"
|
||||
- "User can reset password using email link with token"
|
||||
- "Activation email sent when activation mode is user"
|
||||
artifacts:
|
||||
- path: "plugins/golem15/user/components/ResetPassword.scala"
|
||||
provides: "Password reset flow component"
|
||||
exports: ["ResetPasswordComponent", "onRequest", "onReset"]
|
||||
- path: "plugins/golem15/user/services/UserMailService.scala"
|
||||
provides: "Email sending for activation and reset"
|
||||
exports: ["UserMailService", "sendActivationEmail", "sendResetEmail"]
|
||||
- path: "plugins/golem15/user/resources/views/account/profile.peb"
|
||||
provides: "Profile edit form template"
|
||||
contains: "hx-post"
|
||||
key_links:
|
||||
- from: "plugins/golem15/user/components/Account.scala"
|
||||
to: "UserMailService"
|
||||
via: "activation email on register"
|
||||
pattern: "mailService\\.sendActivationEmail"
|
||||
- from: "plugins/golem15/user/components/ResetPassword.scala"
|
||||
to: "FrontendUserService"
|
||||
via: "password reset"
|
||||
pattern: "userService\\.resetPassword"
|
||||
- from: "plugins/golem15/user/services/FrontendUserService.scala"
|
||||
to: "MediaLibrary"
|
||||
via: "avatar upload"
|
||||
pattern: "mediaLibrary\\.upload"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Complete the User plugin with profile management and password reset functionality.
|
||||
|
||||
Purpose: Enable users to manage their accounts and recover access, completing the frontend user experience.
|
||||
|
||||
Output: Working profile and reset features:
|
||||
- Profile edit form with name/surname/password change
|
||||
- Avatar upload using media library
|
||||
- Password reset request and completion flow
|
||||
- Email templates for activation and reset
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/jin/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/jin/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/10-core-plugins/10-CONTEXT.md
|
||||
@.planning/phases/10-core-plugins/10-RESEARCH.md
|
||||
|
||||
# Prior plan in this phase
|
||||
@.planning/phases/10-core-plugins/10-01-SUMMARY.md
|
||||
|
||||
# Reference: Phase 9 media library for avatar upload
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Profile management with avatar upload</name>
|
||||
<files>
|
||||
plugins/golem15/user/components/Account.scala (modify)
|
||||
plugins/golem15/user/services/FrontendUserService.scala (modify)
|
||||
plugins/golem15/user/resources/views/account/profile.peb
|
||||
plugins/golem15/user/resources/views/account/avatar.peb
|
||||
</files>
|
||||
<action>
|
||||
Add profile management to Account component:
|
||||
|
||||
**Account.scala additions:**
|
||||
- Add onUpdate HTMX handler (research Pattern 4):
|
||||
- Get current user from authService, fail if not authenticated
|
||||
- If requirePassword property set, validate current password
|
||||
- Parse ProfileUpdate from form (name, surname, newPassword optional)
|
||||
- Validate new password length if provided (min from settings)
|
||||
- Call userService.updateProfile
|
||||
- If password changed, re-authenticate user (new JWT)
|
||||
- Return success partial with "Profile updated" message
|
||||
- Add onUploadAvatar HTMX handler:
|
||||
- Get current user, fail if not authenticated
|
||||
- Get uploaded file from multipart form
|
||||
- Validate file type (image/jpeg, image/png, image/gif, image/webp)
|
||||
- Validate file size (max 2MB from settings or default)
|
||||
- Call userService.updateAvatar
|
||||
- Return updated avatar partial with new image
|
||||
|
||||
**FrontendUserService.scala additions:**
|
||||
- Add updateAvatar(userId, file: UploadedFile): FrontendUser
|
||||
- Use MediaLibrary from Phase 9 to store file
|
||||
- Generate path: users/{userId}/avatar.{ext}
|
||||
- Update user.avatarPath
|
||||
- Delete old avatar if exists
|
||||
- Modify updateProfile to handle password change:
|
||||
- If newPassword provided, hash with Argon2id
|
||||
- Update passwordHash field
|
||||
- Rotate persistCode to invalidate other sessions
|
||||
|
||||
**profile.peb template:**
|
||||
- Form with hx-post to onUpdate handler, hx-target for response
|
||||
- Name input (pre-filled from user)
|
||||
- Surname input (pre-filled from user)
|
||||
- Current password input (if requirePassword property)
|
||||
- New password input (optional)
|
||||
- Confirm new password input
|
||||
- Submit button
|
||||
- Error/success display area
|
||||
- CSRF token
|
||||
|
||||
**avatar.peb template:**
|
||||
- Current avatar display (or placeholder)
|
||||
- Form with hx-post to onUploadAvatar, hx-encoding="multipart/form-data"
|
||||
- File input with accept="image/*"
|
||||
- Upload button
|
||||
- Preview area that updates on success
|
||||
</action>
|
||||
<verify>
|
||||
./mill summercms.compile succeeds
|
||||
Account component has onUpdate and onUploadAvatar handlers
|
||||
Templates have valid Pebble syntax
|
||||
</verify>
|
||||
<done>
|
||||
Users can edit profile (name, surname, password).
|
||||
Password change requires current password if configured.
|
||||
Avatar upload stores image via media library.
|
||||
Templates render forms with proper HTMX attributes.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Password reset flow and email service</name>
|
||||
<files>
|
||||
plugins/golem15/user/components/ResetPassword.scala
|
||||
plugins/golem15/user/services/UserMailService.scala
|
||||
plugins/golem15/user/services/FrontendUserService.scala (modify)
|
||||
plugins/golem15/user/resources/views/resetpassword/request.peb
|
||||
plugins/golem15/user/resources/views/resetpassword/reset.peb
|
||||
plugins/golem15/user/resources/views/mail/activation.peb
|
||||
plugins/golem15/user/resources/views/mail/reset.peb
|
||||
</files>
|
||||
<action>
|
||||
Create password reset component and email service:
|
||||
|
||||
**UserMailService.scala:**
|
||||
- Trait with ZIO effects:
|
||||
- sendActivationEmail(user: FrontendUser, code: String): IO[MailError, Unit]
|
||||
- sendResetEmail(user: FrontendUser, code: String): IO[MailError, Unit]
|
||||
- Live implementation using ZIO email service (or stub for now):
|
||||
- Load Pebble template for email body
|
||||
- Include activation/reset URL with code
|
||||
- Send via configured SMTP or email service
|
||||
- MailError ADT: SendFailed, TemplateError, ConfigurationError
|
||||
|
||||
**FrontendUserService.scala additions:**
|
||||
- requestPasswordReset(email: String): Unit
|
||||
- Find user by email (don't reveal if not found - timing attack)
|
||||
- Generate secure random reset code (32 bytes hex)
|
||||
- Set resetPasswordCode on user with expiry tracking
|
||||
- Call mailService.sendResetEmail
|
||||
- Always succeed (don't reveal user existence)
|
||||
- resetPassword(code: String, newPassword: String): FrontendUser
|
||||
- Find user by resetPasswordCode
|
||||
- Validate code not expired (24 hours)
|
||||
- Hash new password with Argon2id
|
||||
- Clear resetPasswordCode
|
||||
- Rotate persistCode (invalidate other sessions)
|
||||
- Return updated user
|
||||
|
||||
**ResetPassword.scala component:**
|
||||
- Extend SummerComponent trait
|
||||
- ComponentDetails: name "ResetPassword", description "Password recovery flow"
|
||||
- Property schema:
|
||||
- paramCode: String, URL parameter for reset code, default "code"
|
||||
- redirect: Dropdown, page after successful reset
|
||||
- HTMX handlers:
|
||||
- onRequest: Parse email from form, call userService.requestPasswordReset,
|
||||
always show success message (no email enumeration)
|
||||
- onReset: Get code from URL param, parse new password + confirmation,
|
||||
validate password length and match, call userService.resetPassword,
|
||||
auto-login user, redirect to configured page
|
||||
|
||||
**Templates:**
|
||||
- resetpassword/request.peb:
|
||||
- Form with hx-post to onRequest
|
||||
- Email input
|
||||
- Submit button "Send Reset Link"
|
||||
- Success message: "If an account exists, you will receive an email"
|
||||
- resetpassword/reset.peb:
|
||||
- Form with hx-post to onReset
|
||||
- Hidden input with reset code from URL
|
||||
- New password input
|
||||
- Confirm password input
|
||||
- Submit button "Reset Password"
|
||||
- Error display for invalid/expired code
|
||||
- mail/activation.peb (Pebble email template):
|
||||
- Subject: "Activate your account"
|
||||
- Body: Welcome message, activation link with code
|
||||
- Plain text fallback
|
||||
- mail/reset.peb (Pebble email template):
|
||||
- Subject: "Reset your password"
|
||||
- Body: Reset link with code, expiry notice (24 hours)
|
||||
- Plain text fallback
|
||||
|
||||
**Register ResetPassword component in Plugin.scala**
|
||||
</action>
|
||||
<verify>
|
||||
./mill summercms.compile succeeds
|
||||
ResetPassword component registered in Plugin.scala
|
||||
Email templates have valid Pebble syntax
|
||||
Reset code generation uses SecureRandom
|
||||
</verify>
|
||||
<done>
|
||||
Password reset request sends email (or stubs if no SMTP).
|
||||
Reset link with token allows setting new password.
|
||||
Activation email template ready for registration flow.
|
||||
No email enumeration possible (generic success messages).
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
After all tasks complete:
|
||||
1. User plugin compiles: `./mill summercms.compile`
|
||||
2. Account component has all handlers: onSignin, onRegister, onActivate, onUpdate, onUploadAvatar
|
||||
3. ResetPassword component handles: onRequest, onReset
|
||||
4. Email service can send activation and reset emails
|
||||
5. Templates render valid forms with HTMX
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Logged-in user can view profile form pre-filled with their data
|
||||
- Profile update saves name/surname changes
|
||||
- Password change requires current password confirmation
|
||||
- Avatar upload stores image and updates user record
|
||||
- Password reset request always shows success (no enumeration)
|
||||
- Reset link with valid code allows new password entry
|
||||
- User automatically logged in after successful reset
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/10-core-plugins/10-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user