This commit is contained in:
parent
e9bcfe4fa6
commit
913d67b978
19 changed files with 732 additions and 499 deletions
|
|
@ -83,7 +83,18 @@ lib/
|
|||
│ ├── member.ex # Member resource
|
||||
│ ├── custom_field_value.ex # Custom field value resource
|
||||
│ ├── custom_field.ex # CustomFieldValue type resource
|
||||
│ ├── setting.ex # Global settings (singleton resource)
|
||||
│ └── email.ex # Email custom type
|
||||
├── membership_fees/ # MembershipFees domain
|
||||
│ ├── membership_fees.ex # Domain definition
|
||||
│ ├── membership_fee_type.ex # Membership fee type resource
|
||||
│ ├── membership_fee_cycle.ex # Membership fee cycle resource
|
||||
│ └── changes/ # Ash changes for membership fees
|
||||
├── mv/authorization/ # Authorization domain
|
||||
│ ├── authorization.ex # Domain definition
|
||||
│ ├── role.ex # Role resource
|
||||
│ ├── permission_sets.ex # Hardcoded permission sets
|
||||
│ └── checks/ # Authorization checks
|
||||
├── mv/ # Core application modules
|
||||
│ ├── accounts/ # Domain-specific logic
|
||||
│ │ └── user/
|
||||
|
|
@ -107,7 +118,7 @@ lib/
|
|||
│ │ ├── table_components.ex
|
||||
│ │ ├── layouts.ex
|
||||
│ │ └── layouts/ # Layout templates
|
||||
│ │ ├── navbar.ex
|
||||
│ │ ├── sidebar.ex
|
||||
│ │ └── root.html.heex
|
||||
│ ├── controllers/ # HTTP controllers
|
||||
│ │ ├── auth_controller.ex
|
||||
|
|
@ -123,7 +134,12 @@ lib/
|
|||
│ │ ├── member_live/ # Member CRUD LiveViews
|
||||
│ │ ├── custom_field_value_live/ # CustomFieldValue CRUD LiveViews
|
||||
│ │ ├── custom_field_live/
|
||||
│ │ └── user_live/ # User management LiveViews
|
||||
│ │ ├── user_live/ # User management LiveViews
|
||||
│ │ ├── role_live/ # Role management LiveViews
|
||||
│ │ ├── membership_fee_type_live/ # Membership fee type LiveViews
|
||||
│ │ ├── membership_fee_settings_live.ex # Membership fee settings
|
||||
│ │ ├── global_settings_live.ex # Global settings
|
||||
│ │ └── contribution_type_live/ # Contribution types (mock-up)
|
||||
│ ├── auth_overrides.ex # AshAuthentication overrides
|
||||
│ ├── endpoint.ex # Phoenix endpoint
|
||||
│ ├── gettext.ex # I18n configuration
|
||||
|
|
@ -818,14 +834,17 @@ end
|
|||
|
||||
```heex
|
||||
<!-- Leverage DaisyUI component classes -->
|
||||
<div class="navbar bg-base-100">
|
||||
<div class="navbar-start">
|
||||
<a class="btn btn-ghost text-xl">Mila</a>
|
||||
<!-- Note: Navbar has been replaced with Sidebar (see lib/mv_web/components/layouts/sidebar.ex) -->
|
||||
<div class="drawer lg:drawer-open">
|
||||
<input id="drawer-toggle" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content">
|
||||
<!-- Page content -->
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<.link navigate={~p"/members"} class="btn btn-primary">
|
||||
Members
|
||||
</.link>
|
||||
<div class="drawer-side">
|
||||
<label for="drawer-toggle" class="drawer-overlay"></label>
|
||||
<aside class="w-64 min-h-full bg-base-200">
|
||||
<!-- Sidebar content -->
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
|
@ -1535,15 +1554,57 @@ policies do
|
|||
authorize_if always()
|
||||
end
|
||||
|
||||
# Specific permissions
|
||||
policy action_type([:read, :update]) do
|
||||
authorize_if relates_to_actor_via(:user)
|
||||
# Use HasPermission check for role-based authorization
|
||||
policy action_type([:read, :update, :create, :destroy]) do
|
||||
authorize_if Mv.Authorization.Checks.HasPermission
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
policy action_type(:destroy) do
|
||||
authorize_if actor_attribute_equals(:role, :admin)
|
||||
**Actor Handling in LiveViews:**
|
||||
|
||||
Always use the `current_actor/1` helper for consistent actor access:
|
||||
|
||||
```elixir
|
||||
# In LiveView modules
|
||||
import MvWeb.LiveHelpers, only: [current_actor: 1, ash_actor_opts: 1, submit_form: 3]
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
actor = current_actor(socket)
|
||||
|
||||
case Ash.read(Mv.Membership.Member, ash_actor_opts(actor)) do
|
||||
{:ok, members} ->
|
||||
{:ok, assign(socket, :members, members)}
|
||||
{:error, error} ->
|
||||
{:ok, put_flash(socket, :error, "Failed to load members")}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("save", %{"member" => params}, socket) do
|
||||
actor = current_actor(socket)
|
||||
form = AshPhoenix.Form.for_create(Mv.Membership.Member, :create)
|
||||
|
||||
case submit_form(form, params, actor) do
|
||||
{:ok, member} ->
|
||||
{:noreply, push_navigate(socket, to: ~p"/members/#{member.id}")}
|
||||
{:error, form} ->
|
||||
{:noreply, assign(socket, :form, form)}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Never use bang calls (`Ash.read!`, `Ash.get!`) without error handling:**
|
||||
|
||||
```elixir
|
||||
# Bad - will crash on authorization errors
|
||||
members = Ash.read!(Mv.Membership.Member, actor: actor)
|
||||
|
||||
# Good - proper error handling
|
||||
case Ash.read(Mv.Membership.Member, actor: actor) do
|
||||
{:ok, members} -> # success
|
||||
{:error, %Ash.Error.Forbidden{}} -> # handle authorization error
|
||||
{:error, error} -> # handle other errors
|
||||
end
|
||||
```
|
||||
|
||||
### 5.2 Password Security
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue