mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
162 lines
5.3 KiB
TypeScript
162 lines
5.3 KiB
TypeScript
'use server'
|
|
|
|
import type { Payload } from 'payload'
|
|
|
|
import {
|
|
municipalityAdvisorsAssignmentSchema,
|
|
municipalityExpectedVotesSchema,
|
|
municipalityPoliticalTrendSchema,
|
|
municipalityStrategyUpdateSchema,
|
|
type MunicipalityAdvisorsAssignmentInput,
|
|
type MunicipalityExpectedVotesInput,
|
|
type MunicipalityPoliticalTrendInput,
|
|
type MunicipalityStrategyUpdateInput,
|
|
} from '@/lib/schemas/municipality'
|
|
import { normalizeVoteEstimateOnSave, toVoteEstimateScenarioViewModel } from '@/lib/voteEstimate'
|
|
import type { CampaignUser } from '@/payload-types'
|
|
import {
|
|
getCampaignActionContext,
|
|
reloadStaffActor,
|
|
reloadUnrestrictedActor,
|
|
} from '@/utilities/campaignActionContext'
|
|
import type { PayloadTransactionRequest } from '@/utilities/payloadTransaction'
|
|
import { withPayloadTransaction } from '@/utilities/payloadTransaction'
|
|
import { acquireTextAdvisoryLocks } from '@/utilities/postgresTransactionLocks'
|
|
|
|
const getFreshStaffActor = (
|
|
payload: Payload,
|
|
actor: CampaignUser,
|
|
req?: PayloadTransactionRequest,
|
|
): Promise<CampaignUser> =>
|
|
reloadStaffActor(
|
|
payload,
|
|
actor,
|
|
'Somente a coordenação e a assessoria podem editar o município.',
|
|
req,
|
|
)
|
|
|
|
/** Staff strategy fields: priority and intel notes. Access enforced by row scope. */
|
|
export const updateMunicipalityStrategyRecord = async (
|
|
payload: Payload,
|
|
actor: CampaignUser,
|
|
input: MunicipalityStrategyUpdateInput,
|
|
) => {
|
|
const { municipality, strengths, risks, stateDeputies, ...fields } =
|
|
municipalityStrategyUpdateSchema.parse(input)
|
|
const currentActor = await getFreshStaffActor(payload, actor)
|
|
|
|
return payload.update({
|
|
collection: 'municipality',
|
|
id: municipality,
|
|
data: {
|
|
...fields,
|
|
...(strengths === undefined ? {} : { strengths: strengths.map((text) => ({ text })) }),
|
|
...(risks === undefined ? {} : { risks: risks.map((text) => ({ text })) }),
|
|
...(stateDeputies === undefined ? {} : { stateDeputies }),
|
|
},
|
|
depth: 0,
|
|
user: currentActor,
|
|
overrideAccess: false,
|
|
})
|
|
}
|
|
|
|
export const updateMunicipalityStrategy = async (input: MunicipalityStrategyUpdateInput) => {
|
|
const { payload, actor } = await getCampaignActionContext()
|
|
return updateMunicipalityStrategyRecord(payload, actor, input)
|
|
}
|
|
|
|
/** Political trend: manual conjuncture reading by staff (author/date derived by hook). */
|
|
export const setMunicipalityPoliticalTrendRecord = async (
|
|
payload: Payload,
|
|
actor: CampaignUser,
|
|
input: MunicipalityPoliticalTrendInput,
|
|
) => {
|
|
const { municipality, status, note } = municipalityPoliticalTrendSchema.parse(input)
|
|
const currentActor = await getFreshStaffActor(payload, actor)
|
|
|
|
return payload.update({
|
|
collection: 'municipality',
|
|
id: municipality,
|
|
data: {
|
|
politicalTrend: { status, note },
|
|
},
|
|
depth: 0,
|
|
user: currentActor,
|
|
overrideAccess: false,
|
|
})
|
|
}
|
|
|
|
export const setMunicipalityPoliticalTrend = async (input: MunicipalityPoliticalTrendInput) => {
|
|
const { payload, actor } = await getCampaignActionContext()
|
|
return setMunicipalityPoliticalTrendRecord(payload, actor, input)
|
|
}
|
|
|
|
/** Staff-only total expected votes for the municipality (distinct from pledge aggregates). */
|
|
export const setMunicipalityExpectedVotesRecord = async (
|
|
payload: Payload,
|
|
actor: CampaignUser,
|
|
input: MunicipalityExpectedVotesInput,
|
|
) => {
|
|
const normalizedExpectedVotes = normalizeVoteEstimateOnSave(
|
|
toVoteEstimateScenarioViewModel(input.expectedVotes),
|
|
)
|
|
const { municipality, expectedVotes } = municipalityExpectedVotesSchema.parse({
|
|
municipality: input.municipality,
|
|
expectedVotes: normalizedExpectedVotes,
|
|
})
|
|
const currentActor = await getFreshStaffActor(payload, actor)
|
|
|
|
return payload.update({
|
|
collection: 'municipality',
|
|
id: municipality,
|
|
data: { expectedVotes },
|
|
depth: 0,
|
|
user: currentActor,
|
|
overrideAccess: false,
|
|
})
|
|
}
|
|
|
|
export const setMunicipalityExpectedVotes = async (input: MunicipalityExpectedVotesInput) => {
|
|
const { payload, actor } = await getCampaignActionContext()
|
|
return setMunicipalityExpectedVotesRecord(payload, actor, input)
|
|
}
|
|
|
|
/** Advisor assignment is unrestricted staff (coordinator + candidate); the hook validates eligibility. */
|
|
export const assignMunicipalityAdvisorsRecord = async (
|
|
payload: Payload,
|
|
actor: CampaignUser,
|
|
input: MunicipalityAdvisorsAssignmentInput,
|
|
) => {
|
|
const { municipality, advisors } = municipalityAdvisorsAssignmentSchema.parse(input)
|
|
|
|
return withPayloadTransaction(
|
|
payload,
|
|
async ({ req }) => {
|
|
await reloadUnrestrictedActor(
|
|
payload,
|
|
actor,
|
|
'Somente a coordenação geral ou o candidato designa assessores.',
|
|
req,
|
|
)
|
|
|
|
await acquireTextAdvisoryLocks(payload, req, [`municipality-advisors:${municipality}`])
|
|
|
|
// Intentional admin bypass: unrestricted role was freshly verified above;
|
|
// the advisors field is admin-only by field access.
|
|
return payload.update({
|
|
collection: 'municipality',
|
|
id: municipality,
|
|
data: { advisors },
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
req,
|
|
})
|
|
},
|
|
{ beginFailureMessage: 'Não foi possível iniciar a designação de assessores.' },
|
|
)
|
|
}
|
|
|
|
export const assignMunicipalityAdvisors = async (input: MunicipalityAdvisorsAssignmentInput) => {
|
|
const { payload, actor } = await getCampaignActionContext()
|
|
return assignMunicipalityAdvisorsRecord(payload, actor, input)
|
|
}
|