Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/app/access/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from enum import StrEnum

class SessionRoles(StrEnum):
CHAIR = "chair"
DELEGATION = "delegate"
4 changes: 2 additions & 2 deletions backend/app/access/models.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from dataclasses import dataclass
from typing import Literal
from uuid import UUID

from . import enums

@dataclass(frozen=True)
class CommitteeAssignment:
"""Object that holds info about an UUID to a commitee and Delegation / Chair"""

user_id: UUID
committee_id: int # TODO: remove this to map out to committees/conferences
role: Literal["chair", "delegate"]
role: enums.SessionRoles
representation_id: int | None
6 changes: 6 additions & 0 deletions backend/app/access/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from pydantic import BaseModel
from . import enums

class SessionRepresentation(BaseModel):
role: enums.SessionRoles
representation_id: int | None
8 changes: 5 additions & 3 deletions backend/app/access/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@
from app.core.database import get_db_session

from .service import AccessDenied, resolve_session_assignment
from .schemas import SessionRepresentation

router = APIRouter()


@router.get("/sessions/{session_id}/me")

@router.get("/sessions/{session_id}/me", response_model=SessionRepresentation)
async def get_my_session_access(
session_id: int,
db_session: Annotated[AsyncSession, Depends(get_db_session)],
current_user: Annotated[AuthUser, Depends(get_current_user)],
):
)->SessionRepresentation:
"""Return the authenticated user's actor context for a session."""
try:
assignment = await resolve_session_assignment(
Expand All @@ -32,4 +34,4 @@ async def get_my_session_access(
return {
"role": assignment.role,
"representation_id": assignment.representation_id,
}
}
12 changes: 6 additions & 6 deletions backend/app/session/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ def handle_open_informal_voting(
)

state.voting = VotingContext(
target_type="INFORMAL",
target_type=enums.VotingType.INFORMAL,
title=event.payload.title,
return_state=state.current_state,
voting_registry={},
Expand All @@ -473,7 +473,7 @@ def handle_close_informal_voting(

if (
state.current_state != States.VOTING_EXECUTION
or state.voting.target_type != "INFORMAL"
or state.voting.target_type != enums.VotingType.INFORMAL
):
raise InvalidProceduralMove("Can't close voting")

Expand All @@ -497,7 +497,7 @@ def handle_close_procedural_voting(

if (
state.current_state != States.VOTING_EXECUTION
or state.voting.target_type != "PROCEDURAL"
or state.voting.target_type != enums.VotingType.PROCEDURAL
):
raise InvalidProceduralMove("Can't close voting")

Expand Down Expand Up @@ -631,13 +631,13 @@ def handle_resolve_motion(
if motion is None:
raise InvalidProceduralMove("Motion not found")

if payload.action == "ACCEPT":
if payload.action:
state.voting = VotingContext(
target_type="PROCEDURAL",
target_type=enums.VotingType.PROCEDURAL,
motion_in_vote=motion,
return_state=state.current_state,
voting_registry={},
majority="QUALIFIED", # TODO: change depending on type of motion
majority=enums.MajorityTypes.QUALIFIED, # TODO: change depending on type of motion
veto_power=True,
)

Expand Down
14 changes: 14 additions & 0 deletions backend/app/session/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,22 @@ class RollCallChoice(StrEnum):
PRESENT_AND_VOTING = "Present and Voting"
ABSENT = "Absent"

class MajorityTypes(StrEnum):
SIMPLE = "Maioria Simples"
QUALIFIED = "Maioria Qualificada"
ABSOLUTE = "Consenso"

class SessionRole(StrEnum):
CHAIR = "CHAIR"
DELEGATE = "DELEGATE"
# further roles are put here

class VotingChoice(StrEnum):
FAVOUR = "Favour"
AGAINST = "Against"
ABSTAIN = "Abstain"

class VotingType(StrEnum):
INFORMAL = "Informal"
PROCEDURAL = "Procedural"
SUBSTANTIVE = "Substantive"
6 changes: 3 additions & 3 deletions backend/app/session/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ class QuestionContext(BaseModel):


class VotingContext(BaseModel):
target_type: Literal["PROCEDURAL", "SUBSTANTIVE", "INFORMAL"]
target_type: enums.VotingType
motion_in_vote: MotionContext | None = None
title: str | None = None
return_state: enums.States
voting_registry: dict[int, Literal["FAVOUR", "AGAINST", "ABSTAIN"]] = {}
voting_registry: dict[int, enums.VotingChoice] = {}

# additional fields
majority: Literal["SIMPLE", "QUALIFIED", "ABSOLUTE"]
majority: enums.MajorityTypes
veto_power: bool


Expand Down
23 changes: 7 additions & 16 deletions backend/app/session/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,7 @@ class DelegateQuestionPayload(BaseModel):


class DelegateVotingPayload(BaseModel):
# other types of voting must be put in here
type: Literal[
"FORMAL",
"INFORMAL",
] # perhaps not needed
motion_id: int | None = (
None # perhaps not needed, unless we pass the voting context to UI to validate?
)
title: str | None = None # perhaps not needed
vote: Literal["FAVOUR", "AGAINST", "ABSTAIN"]
vote: enums.VotingChoice


# TODO: should be better implemented
Expand Down Expand Up @@ -108,13 +99,13 @@ class ChairToggleTimerPayload(BaseModel):
class ChairOpenInformalVotingPayload(BaseModel):
# For informal Votings
title: str | None = None
majority: Literal["SIMPLE", "QUALIFIED", "ABSOLUTE"]
majority: enums.MajorityTypes
veto_power: bool


class ChairResolveMotionPayload(BaseModel):
motion_id: int # or motion_id if possible
action: Literal["ACCEPT", "DENY"]
action: bool


class ChairForceSpeakerPayload(BaseModel):
Expand Down Expand Up @@ -148,10 +139,10 @@ class MarkAgendaItemPayload(BaseModel):
class DeleteAgendaItemPayload(BaseModel):
index: str # Agenda Item Id


#Removed: Unnecessary
# These two normally don't need to have an id
class ChairCloseInformalVotingPayload(BaseModel):
voting_id: int | None = None
# class ChairCloseInformalVotingPayload(BaseModel):
# voting_id: int | None = None


class EmptyPayload(BaseModel): ...
Expand Down Expand Up @@ -214,7 +205,7 @@ class SetPhaseEvent(BaseModel):

class CloseInformalVotingEvent(BaseModel):
type: Literal[enums.ChairEvents.CLOSE_INFORMAL_VOTING]
payload: ChairCloseInformalVotingPayload
payload: EmptyPayload


class CloseProceduralVotingEvent(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion backend/app/tests/session/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def open_informal_voting_event() -> sch.OpenInformalVotingEvent:
def close_informal_voting_event() -> sch.CloseInformalVotingEvent:
return sch.CloseInformalVotingEvent(
type=enums.ChairEvents.CLOSE_INFORMAL_VOTING,
payload=sch.ChairCloseInformalVotingPayload(),
payload=sch.EmptyPayload(),
)


Expand Down
10 changes: 4 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ services:
watch:
- action: restart
path: ./backend/app/session/schemas.py
- action: restart
path: ./backend/app/session/models.py
- action: restart
path: ./backend/app/session/enums.py

backend:
build:
Expand All @@ -50,12 +54,6 @@ services:
retries: 3
start_period: 50s
start_interval: 1s
develop:
watch:
- action: restart
path: ./backend/app/session/schemas.py
- action: restart
path: ./backend/app/session/models.py

volumes:
packages:
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/session/Agenda.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { sendMessage } from "@/pages/Session"
import { sendMessage, useSession } from "@/context/SessionContext"
import type {SetAgendaItemEvent, MarkAgendaItemEvent, DeleteAgendaItemEvent} from "@/schemas/types.gen"
import {ChairEvents } from "@/schemas/types.gen"
import {ChairEvents, SessionRoles } from "@/schemas/types.gen"
import { Button } from "@/components/ui/button"
import { Separator } from "@/components/ui/separator"
import { ScrollArea } from "@/components/ui/scroll-area"
Expand Down Expand Up @@ -31,8 +31,8 @@ import { useRef } from "react"

export default function Agenda() {

const isChair = true // Replace with actual logic to determine if the user is the chair

const {role} = useSession()
const isChair = role===SessionRoles.CHAIR
const agendaTopics = useCommitteeStore((state) => state.agenda_topics)

const numinput = useRef<HTMLInputElement>(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Label } from "@/components/ui/label"
import { useCommitteeStore } from "@/store/useCommitteeStore"
import { States } from "@/schemas/types.gen"

const isChair = true // Replace with actual logic to determine if the user is the chair
import { useSession } from "@/context/SessionContext"
import { SessionRoles } from "@/schemas/types.gen"

const motions = [
"Moção para Adiamento de Sessão",
Expand Down Expand Up @@ -89,6 +89,10 @@ function QuestionsMotionsList(type: MotionKind) {
}

export default function TestButton() {

const {role} = useSession()
const isChair = role===SessionRoles.CHAIR

const currentState = useCommitteeStore((state) => state.current_state)
const [motionKind, setMotionKind] = useState<MotionKind>("moção")
const [selectedMotion, setSelectedMotion] = useState("")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from "@/components/ui/select"
import ManualQuorum from "@/components/session/manual-quorum"
import { RollCallChoice, ChairEvents, States, type CloseRollCallEvent, type CloseSessionEvent, type OpenSessionEvent } from "@/schemas/types.gen"
import { sendMessage } from "@/pages/Session"
import { sendMessage } from "@/context/SessionContext"
import { useCommitteeStore } from "@/store/useCommitteeStore"


Expand Down
23 changes: 16 additions & 7 deletions frontend/src/components/session/bottom-bar-buttons/VoteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,17 @@ import {
import { Vote } from "lucide-react"
import { States } from "@/schemas/types.gen"
import { useCommitteeStore } from "@/store/useCommitteeStore"
import { sendMessage } from "@/context/SessionContext"
import { MajorityTypes, ChairEvents, type OpenInformalVotingEvent, type CloseInformalVotingEvent } from "@/schemas/types.gen"
import { useRef } from "react"

export default function VoteButton() {
const currentState = useCommitteeStore((state) => state.current_state)
const title = useRef<HTMLInputElement>(null)
const majority = useRef<HTMLSelectElement>(null)
const veto = useRef<HTMLInputElement>(null)


return (
<Dialog>
<DialogTrigger asChild>
Expand All @@ -32,27 +40,28 @@ export default function VoteButton() {
<div className="grid gap-4">
<div className="grid gap-2">
<label className="text-sm font-medium">Título da Votação</label>
<input className="h-9 rounded-md border px-3" placeholder="Estamos de acordo com o paragrafo X?" />
<input ref={title} className="h-9 rounded-md border px-3" placeholder="Estamos de acordo com o paragrafo X?" />
<p className="text-xs text-muted-foreground">O que vai ser votado</p>
</div>
<div className="grid gap-2">
<label className="text-sm font-medium">Maioria necessária:</label>
<select className="h-9 rounded-md border px-3">
<option>Maioria Simples</option>
<option>Maioria Qualificada</option>
<option>Consenso</option>
<select ref={majority} className="h-9 rounded-md border px-3">
<option>{MajorityTypes.MAIORIA_SIMPLES}</option>
<option>{MajorityTypes.MAIORIA_QUALIFICADA}</option>
<option>{MajorityTypes.CONSENSO}</option>
</select>
</div>
<div className="flex items-start gap-3">
<input type="checkbox" className="mt-1" defaultChecked />
<input ref={veto} type="checkbox" className="mt-1" defaultChecked />
<div className="grid gap-1">
<label className="text-sm font-medium">P5 tem poder de veto nesta votacao?</label>
<p className="text-xs text-muted-foreground">
Os membros permanentes do conselho de seguranca podem exprimir que vetarão a proposição na votação final da proposta.
</p>
</div>
</div>
<Button className="w-full bg-green-800 text-white hover:bg-green-700">Iniciar Votação</Button>
<Button onClick={() => {sendMessage({type: ChairEvents.OPEN_INFORMAL_VOTING_EVENT, payload:{title: title.current?.value, veto_power: veto.current?.checked, majority: majority.current?.value}} as OpenInformalVotingEvent)}} className="w-full bg-green-800 text-white hover:bg-green-700">Iniciar Votação</Button>
<Button onClick={() => {sendMessage({type: ChairEvents.CLOSE_INFORMAL_VOTING_EVENT, payload:{} } as CloseInformalVotingEvent)}} className="w-full bg-red-800 text-white hover:bg-red-700">Fechar Votação</Button>
</div>
</div>
</DialogContent>
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/session/bottom-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import SessionButton from "./bottom-bar-buttons/SessionButton"
import ExitButton from "./bottom-bar-buttons/ExitButton"
import BRBButton from "./bottom-bar-buttons/BRB"
import IncidentHelp from "./bottom-bar-buttons/IncidentHelp"

const isChair = true // Replace with actual logic to determine if the user is the chair
import { useSession } from "@/context/SessionContext"
import { SessionRoles } from "@/schemas/types.gen"


export default function BottomBar() {

const {role} = useSession()
const isChair = role===SessionRoles.CHAIR

return (
<>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/session/delegation-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from "@/components/ui/context-menu"
import { useCommitteeStore } from "@/store/useCommitteeStore"
import { CircleFlag } from 'react-circle-flags'
import { sendMessage } from "@/pages/Session"
import { sendMessage } from "@/context/SessionContext"
import { type ChairInsertQueueEvent, type MarkRollCallEvent, type SpeakerEvent , ChairEvents, RollCallChoice } from "@/schemas/types.gen"
import {
Tooltip,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/session/manual-quorum.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { useCommitteeStore } from "@/store/useCommitteeStore"
import { ScrollArea } from "@/components/ui/scroll-area"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { sendMessage } from "@/pages/Session"
import { sendMessage } from "@/context/SessionContext"
import {
Item,
ItemContent,
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/session/moderated-debate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { useSession } from "@/context/SessionContext"
import { SessionRoles } from "@/schemas/types.gen"

const isChair = true // Replace with actual logic to determine if the user is the chair
//TODO determine if queue is open, if not obscure the button and show a message that the queue is closed

export default function ModeratedDebate() {

const {role} = useSession()
const isChair = role===SessionRoles.CHAIR
const gslQueue = useCommitteeStore((state) => state.gsl_queue ?? [])
const currentSpeaker = useCommitteeStore((state) => state.current_speaker)
const delegationsById = useCommitteeStore((state) => state.delegations)
Expand Down
Loading
Loading