Skip to content

UpsertProfile + ToggleAvailability actions #253

@danielhe4rt

Description

@danielhe4rt

Parent

Parte da PRD #250 — Módulo Profile Fase 1.

Contexto

Com o módulo Profile scaffolded (#251) e o model/enums prontos, esta issue entrega a lógica de domínio — as actions que criam, atualizam e manipulam profiles.

Hoje o fluxo de edição de perfil vive no módulo Identity (UpdateProfile action + InformationUserAction + UpsertInformationDTO). Esse código será removido na issue de legado. Aqui construímos o substituto.

São duas actions distintas porque têm responsabilidades diferentes:

  • UpsertProfile: dados pessoais e profissionais (headline, bio, senioridade, social links, etc.)
  • ToggleAvailability: liga/desliga disponibilidade + prazo de início. Separada porque é uma operação atômica que pode ser chamada isoladamente (ex: bot Discord, toggle rápido na UI).

Decisões relevantes

  • social_links é JSONB validado contra o enum SocialPlatform. Chaves fora do enum são rejeitadas.
  • bio tem limite de 500 caracteres.
  • start_availability só é validado/relevante quando available_for_proposals = true.
  • Links de OAuth (GitHub, Discord) não passam por aqui — vêm do ExternalIdentity.
  • Campos de conta (name, email, username) não passam por aqui — vivem em Settings/Identity.

O que construir

1. UpsertProfileDTO

DTO com os campos editáveis do profile:

  • nickname (string, nullable, max 100)
  • birthdate (Carbon/date, nullable)
  • about (string, nullable, max 500)
  • headline (string, nullable, max 100)
  • seniorityLevel (SeniorityLevel enum, nullable)
  • yearsExperience (int, nullable, 0-50)
  • socialLinks (array, nullable — validado contra SocialPlatform enum)

Factory method fromArray(array $data): self pra facilitar uso em Filament e testes.

2. UpsertProfile action

handle(Profile $profile, UpsertProfileDTO $dto): Profile
  • Recebe um Profile existente (já criado eager no tenant join)
  • Atualiza os campos com os valores do DTO
  • Valida socialLinks: só chaves presentes no enum SocialPlatform são permitidas. Lança exception se inválido.
  • Retorna o Profile atualizado

Não faz updateOrCreate — o Profile já existe (decisão da issue #251). É um update puro.

3. ToggleAvailability action

handle(Profile $profile, bool $available, ?StartAvailability $startAvailability = null): Profile
  • Se $available = true, start_availability é obrigatório
  • Se $available = false, start_availability mantém o valor anterior (não apaga)
  • Atualiza available_for_proposals e start_availability
  • Retorna o Profile atualizado

4. Validação de social_links

A validação de social_links acontece dentro da UpsertProfile action (ou em um value object/rule dedicado):

  • Cada chave do array deve ser um caso válido do enum SocialPlatform
  • Cada valor deve ser string não-vazia
  • Chaves desconhecidas são rejeitadas com exception

Cenários BDD

Funcionalidade: Atualizar perfil profissional

  Cenário: Atualizar todos os campos do perfil
    Dado que existe um Profile para "danielhe4rt" no tenant "He4rt"
    Quando chamo UpsertProfile com headline "Backend Developer", seniority "pleno", years_experience 5, about "Dev PHP", nickname "Dan"
    Então o Profile é atualizado com todos os campos
    E o Profile retornado reflete as mudanças

  Cenário: Atualizar parcialmente (só headline)
    Dado que existe um Profile com nickname "Dan" e bio "Dev PHP"
    Quando chamo UpsertProfile com apenas headline "Senior Dev"
    Então headline muda para "Senior Dev"
    E nickname e bio permanecem inalterados

  Cenário: Bio acima de 500 caracteres é rejeitada
    Dado que existe um Profile
    Quando chamo UpsertProfile com about de 501 caracteres
    Então uma exception de validação é lançada
    E o Profile não é alterado

  Cenário: Headline acima de 100 caracteres é rejeitada
    Dado que existe um Profile
    Quando chamo UpsertProfile com headline de 101 caracteres
    Então uma exception de validação é lançada

  Cenário: social_links com plataforma válida
    Dado que existe um Profile
    Quando chamo UpsertProfile com socialLinks {"instagram": "@dan", "website": "https://dan.dev"}
    Então social_links é salvo como {"instagram": "@dan", "website": "https://dan.dev"}

  Cenário: social_links com plataforma inválida é rejeitada
    Dado que existe um Profile
    Quando chamo UpsertProfile com socialLinks {"tiktok": "@dan"}
    E "tiktok" não está no enum SocialPlatform
    Então uma exception de validação é lançada
    E o Profile não é alterado

  Cenário: years_experience fora do range é rejeitado
    Dado que existe um Profile
    Quando chamo UpsertProfile com yearsExperience 51
    Então uma exception de validação é lançada

Funcionalidade: Toggle de disponibilidade

  Cenário: Ativar disponibilidade com prazo
    Dado que "danielhe4rt" tem available_for_proposals false
    Quando chamo ToggleAvailability com available true e startAvailability "immediate"
    Então available_for_proposals é true
    E start_availability é "immediate"

  Cenário: Ativar sem prazo é rejeitado
    Dado que "danielhe4rt" tem available_for_proposals false
    Quando chamo ToggleAvailability com available true e sem startAvailability
    Então uma exception de validação é lançada
    E available_for_proposals permanece false

  Cenário: Desativar disponibilidade mantém prazo anterior
    Dado que "danielhe4rt" tem available_for_proposals true e start_availability "1_week"
    Quando chamo ToggleAvailability com available false
    Então available_for_proposals é false
    E start_availability permanece "1_week"

  Cenário: Alterar prazo sem mudar disponibilidade
    Dado que "danielhe4rt" tem available_for_proposals true e start_availability "immediate"
    Quando chamo ToggleAvailability com available true e startAvailability "2_weeks"
    Então start_availability muda para "2_weeks"
    E available_for_proposals continua true

Acceptance Criteria

  • UpsertProfileDTO com factory method fromArray()
  • UpsertProfile action atualiza todos os campos do profile
  • Validação de about (max 500), headline (max 100), years_experience (0-50)
  • Validação de social_links contra enum SocialPlatform
  • ToggleAvailability action com toggle + prazo obrigatório ao ativar
  • Desativar disponibilidade preserva o start_availability anterior
  • Testes de feature cobrem todos os cenários BDD
  • Nenhum código Filament nesta issue — puro domínio
  • vendor/bin/pint --dirty --format agent passa sem erros

Blocked by

Metadata

Metadata

Assignees

No one assigned

    Labels

    profileProfile moduleready-for-agentFully specified, ready for an AFK agent

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions