refactor: clean up code formatting and improve readability across multiple files
This commit is contained in:
@@ -229,9 +229,7 @@ async def _fetch_agent_events(
|
||||
return list(await session.exec(statement))
|
||||
|
||||
|
||||
async def _require_user_context(
|
||||
session: AsyncSession, user: User | None
|
||||
) -> OrganizationContext:
|
||||
async def _require_user_context(session: AsyncSession, user: User | None) -> OrganizationContext:
|
||||
if user is None:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||
member = await get_active_membership(session, user)
|
||||
|
||||
@@ -33,6 +33,7 @@ from app.models.gateways import Gateway
|
||||
from app.models.users import User
|
||||
from app.schemas.board_group_memory import BoardGroupMemoryCreate, BoardGroupMemoryRead
|
||||
from app.schemas.pagination import DefaultLimitOffsetPage
|
||||
from app.services.mentions import extract_mentions, matches_agent_mention
|
||||
from app.services.organizations import (
|
||||
OrganizationContext,
|
||||
is_org_admin,
|
||||
@@ -40,7 +41,6 @@ from app.services.organizations import (
|
||||
member_all_boards_read,
|
||||
member_all_boards_write,
|
||||
)
|
||||
from app.services.mentions import extract_mentions, matches_agent_mention
|
||||
|
||||
router = APIRouter(tags=["board-group-memory"])
|
||||
|
||||
|
||||
@@ -83,9 +83,7 @@ async def list_board_groups(
|
||||
ctx=Depends(require_org_member),
|
||||
) -> DefaultLimitOffsetPage[BoardGroupRead]:
|
||||
if member_all_boards_read(ctx.member):
|
||||
statement = select(BoardGroup).where(
|
||||
col(BoardGroup.organization_id) == ctx.organization.id
|
||||
)
|
||||
statement = select(BoardGroup).where(col(BoardGroup.organization_id) == ctx.organization.id)
|
||||
else:
|
||||
accessible_boards = select(Board.board_group_id).where(
|
||||
board_access_filter(ctx.member, write=False)
|
||||
|
||||
@@ -129,7 +129,9 @@ async def _apply_board_update(
|
||||
) -> Board:
|
||||
updates = payload.model_dump(exclude_unset=True)
|
||||
if "gateway_id" in updates:
|
||||
await _require_gateway(session, updates["gateway_id"], organization_id=board.organization_id)
|
||||
await _require_gateway(
|
||||
session, updates["gateway_id"], organization_id=board.organization_id
|
||||
)
|
||||
if "board_group_id" in updates and updates["board_group_id"] is not None:
|
||||
await _require_board_group(
|
||||
session,
|
||||
|
||||
@@ -11,9 +11,10 @@ from app.core.auth import AuthContext, get_auth_context, get_auth_context_option
|
||||
from app.db.session import get_session
|
||||
from app.models.agents import Agent
|
||||
from app.models.boards import Board
|
||||
from app.models.organizations import Organization
|
||||
from app.models.tasks import Task
|
||||
from app.models.users import User
|
||||
from app.models.organizations import Organization
|
||||
from app.services.admin_access import require_admin
|
||||
from app.services.organizations import (
|
||||
OrganizationContext,
|
||||
ensure_member_for_user,
|
||||
@@ -21,7 +22,6 @@ from app.services.organizations import (
|
||||
is_org_admin,
|
||||
require_board_access,
|
||||
)
|
||||
from app.services.admin_access import require_admin
|
||||
|
||||
|
||||
def require_admin_auth(auth: AuthContext = Depends(get_auth_context)) -> AuthContext:
|
||||
|
||||
@@ -21,7 +21,6 @@ from app.integrations.openclaw_gateway_protocol import (
|
||||
)
|
||||
from app.models.boards import Board
|
||||
from app.models.gateways import Gateway
|
||||
from app.services.organizations import OrganizationContext, require_board_access
|
||||
from app.schemas.common import OkResponse
|
||||
from app.schemas.gateway_api import (
|
||||
GatewayCommandsResponse,
|
||||
@@ -32,6 +31,7 @@ from app.schemas.gateway_api import (
|
||||
GatewaySessionsResponse,
|
||||
GatewaysStatusResponse,
|
||||
)
|
||||
from app.services.organizations import OrganizationContext, require_board_access
|
||||
|
||||
router = APIRouter(prefix="/gateways", tags=["gateways"])
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ from app.models.organizations import Organization
|
||||
from app.models.users import User
|
||||
from app.schemas.organizations import (
|
||||
OrganizationActiveUpdate,
|
||||
OrganizationBoardAccessRead,
|
||||
OrganizationCreate,
|
||||
OrganizationInviteAccept,
|
||||
OrganizationInviteCreate,
|
||||
@@ -31,7 +32,6 @@ from app.schemas.organizations import (
|
||||
OrganizationMemberAccessUpdate,
|
||||
OrganizationMemberRead,
|
||||
OrganizationMemberUpdate,
|
||||
OrganizationBoardAccessRead,
|
||||
OrganizationRead,
|
||||
OrganizationUserRead,
|
||||
)
|
||||
@@ -39,8 +39,8 @@ from app.schemas.pagination import DefaultLimitOffsetPage
|
||||
from app.services.organizations import (
|
||||
OrganizationContext,
|
||||
accept_invite,
|
||||
apply_invite_to_member,
|
||||
apply_invite_board_access,
|
||||
apply_invite_to_member,
|
||||
apply_member_access_update,
|
||||
get_active_membership,
|
||||
get_member,
|
||||
@@ -298,9 +298,7 @@ async def create_org_invite(
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
|
||||
existing_user = (
|
||||
await session.exec(
|
||||
select(User).where(func.lower(col(User.email)) == email)
|
||||
)
|
||||
await session.exec(select(User).where(func.lower(col(User.email)) == email))
|
||||
).first()
|
||||
if existing_user is not None:
|
||||
existing_member = await get_member(
|
||||
@@ -380,7 +378,9 @@ async def accept_org_invite(
|
||||
if invite is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
if invite.invited_email and auth.user.email:
|
||||
if normalize_invited_email(invite.invited_email) != normalize_invited_email(auth.user.email):
|
||||
if normalize_invited_email(invite.invited_email) != normalize_invited_email(
|
||||
auth.user.email
|
||||
):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
existing = await get_member(
|
||||
|
||||
@@ -20,9 +20,7 @@ class OrganizationBoardAccess(SQLModel, table=True):
|
||||
)
|
||||
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
organization_member_id: UUID = Field(
|
||||
foreign_key="organization_members.id", index=True
|
||||
)
|
||||
organization_member_id: UUID = Field(foreign_key="organization_members.id", index=True)
|
||||
board_id: UUID = Field(foreign_key="boards.id", index=True)
|
||||
can_read: bool = Field(default=True)
|
||||
can_write: bool = Field(default=False)
|
||||
|
||||
@@ -20,9 +20,7 @@ class OrganizationInviteBoardAccess(SQLModel, table=True):
|
||||
)
|
||||
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
organization_invite_id: UUID = Field(
|
||||
foreign_key="organization_invites.id", index=True
|
||||
)
|
||||
organization_invite_id: UUID = Field(foreign_key="organization_invites.id", index=True)
|
||||
board_id: UUID = Field(foreign_key="boards.id", index=True)
|
||||
can_read: bool = Field(default=True)
|
||||
can_write: bool = Field(default=False)
|
||||
|
||||
@@ -79,9 +79,7 @@ async def set_active_organization(
|
||||
user: User,
|
||||
organization_id: UUID,
|
||||
) -> OrganizationMember:
|
||||
member = await get_member(
|
||||
session, user_id=user.id, organization_id=organization_id
|
||||
)
|
||||
member = await get_member(session, user_id=user.id, organization_id=organization_id)
|
||||
if member is None:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="No org access")
|
||||
if user.active_organization_id != organization_id:
|
||||
@@ -199,8 +197,7 @@ async def ensure_member_for_user(session: AsyncSession, user: User) -> Organizat
|
||||
now = utcnow()
|
||||
member_count = (
|
||||
await session.exec(
|
||||
select(func.count())
|
||||
.where(col(OrganizationMember.organization_id) == org.id)
|
||||
select(func.count()).where(col(OrganizationMember.organization_id) == org.id)
|
||||
)
|
||||
).one()
|
||||
is_first = int(member_count or 0) == 0
|
||||
|
||||
@@ -162,6 +162,7 @@ Clerk should be **off** unless you set a real `pk_test_...` or `pk_live_...` pub
|
||||
If you see repeated proxy errors (often `ECONNRESET`), make sure your dev server hostname and browser URL match (e.g. `localhost` vs `127.0.0.1`), and that your origin is included in `allowedDevOrigins`.
|
||||
|
||||
Notes:
|
||||
|
||||
- Local dev should work via `http://localhost:3000` and `http://127.0.0.1:3000`.
|
||||
- LAN dev should work via the configured LAN IP (e.g. `http://192.168.1.101:3000`) **only** if you bind the dev server to all interfaces (`npm run dev:lan`).
|
||||
- If you bind Next to `127.0.0.1` only, remote LAN clients won’t connect.
|
||||
|
||||
@@ -225,7 +225,10 @@ export default function AgentDetailPage() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" onClick={() => router.push("/agents")}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => router.push("/agents")}
|
||||
>
|
||||
Back to agents
|
||||
</Button>
|
||||
{agent ? (
|
||||
|
||||
@@ -286,7 +286,10 @@ export default function NewAgentPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{EMOJI_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<SelectItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
>
|
||||
{option.glyph} {option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -323,7 +326,9 @@ export default function NewAgentPage() {
|
||||
</label>
|
||||
<Textarea
|
||||
value={soulTemplate}
|
||||
onChange={(event) => setSoulTemplate(event.target.value)}
|
||||
onChange={(event) =>
|
||||
setSoulTemplate(event.target.value)
|
||||
}
|
||||
rows={10}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
|
||||
@@ -412,8 +412,8 @@ export default function AgentsPage() {
|
||||
No agents yet
|
||||
</h3>
|
||||
<p className="mb-6 max-w-md text-sm text-slate-500">
|
||||
Create your first agent to start executing tasks
|
||||
on this board.
|
||||
Create your first agent to start executing
|
||||
tasks on this board.
|
||||
</p>
|
||||
<Link
|
||||
href="/agents/new"
|
||||
|
||||
@@ -710,7 +710,13 @@ export default function BoardGroupDetailPage() {
|
||||
} finally {
|
||||
setIsHeartbeatApplying(false);
|
||||
}
|
||||
}, [canManageHeartbeat, groupId, heartbeatEvery, includeBoardLeads, isSignedIn]);
|
||||
}, [
|
||||
canManageHeartbeat,
|
||||
groupId,
|
||||
heartbeatEvery,
|
||||
includeBoardLeads,
|
||||
isSignedIn,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DashboardShell>
|
||||
@@ -850,7 +856,8 @@ export default function BoardGroupDetailPage() {
|
||||
heartbeatEvery === value
|
||||
? "bg-slate-900 text-white"
|
||||
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
|
||||
!canManageHeartbeat && "opacity-50 cursor-not-allowed",
|
||||
!canManageHeartbeat &&
|
||||
"opacity-50 cursor-not-allowed",
|
||||
)}
|
||||
disabled={!canManageHeartbeat}
|
||||
onClick={() => {
|
||||
@@ -913,7 +920,9 @@ export default function BoardGroupDetailPage() {
|
||||
variant="outline"
|
||||
onClick={() => void applyHeartbeat()}
|
||||
disabled={
|
||||
isHeartbeatApplying || !heartbeatEvery || !canManageHeartbeat
|
||||
isHeartbeatApplying ||
|
||||
!heartbeatEvery ||
|
||||
!canManageHeartbeat
|
||||
}
|
||||
title={
|
||||
canManageHeartbeat
|
||||
|
||||
@@ -206,7 +206,9 @@ const resolveBoardAccess = (
|
||||
if (member.all_boards_read) {
|
||||
return { canRead: true, canWrite: false };
|
||||
}
|
||||
const entry = member.board_access?.find((access) => access.board_id === boardId);
|
||||
const entry = member.board_access?.find(
|
||||
(access) => access.board_id === boardId,
|
||||
);
|
||||
if (!entry) {
|
||||
return { canRead: false, canWrite: false };
|
||||
}
|
||||
@@ -2199,7 +2201,9 @@ export default function BoardDetailPage() {
|
||||
async (approvalId: string, status: "approved" | "rejected") => {
|
||||
if (!isSignedIn || !boardId) return;
|
||||
if (!canWrite) {
|
||||
pushToast("Read-only access. You do not have permission to update approvals.");
|
||||
pushToast(
|
||||
"Read-only access. You do not have permission to update approvals.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
setApprovalsUpdatingId(approvalId);
|
||||
@@ -3033,7 +3037,9 @@ export default function BoardDetailPage() {
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handlePostComment}
|
||||
disabled={!canWrite || isPostingComment || !newComment.trim()}
|
||||
disabled={
|
||||
!canWrite || isPostingComment || !newComment.trim()
|
||||
}
|
||||
title={canWrite ? "Send message" : "Read-only access"}
|
||||
>
|
||||
{isPostingComment ? "Sending…" : "Send message"}
|
||||
@@ -3516,7 +3522,10 @@ export default function BoardDetailPage() {
|
||||
<Button variant="outline" onClick={() => setIsDialogOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleCreateTask} disabled={!canWrite || isCreating}>
|
||||
<Button
|
||||
onClick={handleCreateTask}
|
||||
disabled={!canWrite || isCreating}
|
||||
>
|
||||
{isCreating ? "Creating…" : "Create task"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -3611,9 +3620,7 @@ export default function BoardDetailPage() {
|
||||
toast.tone === "error" ? "bg-rose-500" : "bg-emerald-500",
|
||||
)}
|
||||
/>
|
||||
<p className="flex-1 text-sm text-slate-700">
|
||||
{toast.message}
|
||||
</p>
|
||||
<p className="flex-1 text-sm text-slate-700">{toast.message}</p>
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs text-slate-400 hover:text-slate-600"
|
||||
|
||||
@@ -286,7 +286,9 @@ export default function EditGatewayPage() {
|
||||
onBlur={runGatewayCheck}
|
||||
placeholder="ws://gateway:18789"
|
||||
disabled={isLoading}
|
||||
className={gatewayUrlError ? "border-red-500" : undefined}
|
||||
className={
|
||||
gatewayUrlError ? "border-red-500" : undefined
|
||||
}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -247,7 +247,9 @@ export default function NewGatewayPage() {
|
||||
onBlur={runGatewayCheck}
|
||||
placeholder="ws://gateway:18789"
|
||||
disabled={isLoading}
|
||||
className={gatewayUrlError ? "border-red-500" : undefined}
|
||||
className={
|
||||
gatewayUrlError ? "border-red-500" : undefined
|
||||
}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -350,8 +350,8 @@ export default function GatewaysPage() {
|
||||
No gateways yet
|
||||
</h3>
|
||||
<p className="mb-6 max-w-md text-sm text-slate-500">
|
||||
Create your first gateway to connect boards and
|
||||
start managing your OpenClaw connections.
|
||||
Create your first gateway to connect boards
|
||||
and start managing your OpenClaw connections.
|
||||
</p>
|
||||
<Link
|
||||
href="/gateways/new"
|
||||
|
||||
@@ -114,8 +114,15 @@ export default function InvitePage() {
|
||||
</SignedOut>
|
||||
|
||||
<SignedIn>
|
||||
<form className="flex flex-wrap items-center gap-3" onSubmit={handleAccept}>
|
||||
<Button type="submit" size="md" disabled={!isReady || isSubmitting || accepted}>
|
||||
<form
|
||||
className="flex flex-wrap items-center gap-3"
|
||||
onSubmit={handleAccept}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
size="md"
|
||||
disabled={!isReady || isSubmitting || accepted}
|
||||
>
|
||||
{accepted
|
||||
? "Invite accepted"
|
||||
: isSubmitting
|
||||
|
||||
@@ -76,7 +76,9 @@ type AccessScope = "all" | "custom";
|
||||
|
||||
type BoardAccessState = Record<string, { read: boolean; write: boolean }>;
|
||||
|
||||
const buildAccessList = (access: BoardAccessState): OrganizationBoardAccessSpec[] =>
|
||||
const buildAccessList = (
|
||||
access: BoardAccessState,
|
||||
): OrganizationBoardAccessSpec[] =>
|
||||
Object.entries(access)
|
||||
.filter(([, entry]) => entry.read || entry.write)
|
||||
.map(([boardId, entry]) => ({
|
||||
@@ -319,9 +321,8 @@ export default function OrganizationPage() {
|
||||
const [inviteScope, setInviteScope] = useState<AccessScope>("all");
|
||||
const [inviteAllRead, setInviteAllRead] = useState(true);
|
||||
const [inviteAllWrite, setInviteAllWrite] = useState(false);
|
||||
const [inviteAccess, setInviteAccess] = useState<BoardAccessState>(
|
||||
defaultBoardAccess,
|
||||
);
|
||||
const [inviteAccess, setInviteAccess] =
|
||||
useState<BoardAccessState>(defaultBoardAccess);
|
||||
const [inviteError, setInviteError] = useState<string | null>(null);
|
||||
const [copiedInviteId, setCopiedInviteId] = useState<string | null>(null);
|
||||
|
||||
@@ -331,9 +332,8 @@ export default function OrganizationPage() {
|
||||
const [accessAllRead, setAccessAllRead] = useState(false);
|
||||
const [accessAllWrite, setAccessAllWrite] = useState(false);
|
||||
const [accessRole, setAccessRole] = useState("member");
|
||||
const [accessMap, setAccessMap] = useState<BoardAccessState>(
|
||||
defaultBoardAccess,
|
||||
);
|
||||
const [accessMap, setAccessMap] =
|
||||
useState<BoardAccessState>(defaultBoardAccess);
|
||||
const [accessError, setAccessError] = useState<string | null>(null);
|
||||
|
||||
const orgQuery = useGetMyOrgApiV1OrganizationsMeGet<
|
||||
@@ -426,9 +426,8 @@ export default function OrganizationPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const createInviteMutation = useCreateOrgInviteApiV1OrganizationsMeInvitesPost<
|
||||
ApiError
|
||||
>({
|
||||
const createInviteMutation =
|
||||
useCreateOrgInviteApiV1OrganizationsMeInvitesPost<ApiError>({
|
||||
mutation: {
|
||||
onSuccess: (result) => {
|
||||
if (result.status === 200) {
|
||||
@@ -440,9 +439,11 @@ export default function OrganizationPage() {
|
||||
setInviteAccess(defaultBoardAccess);
|
||||
setInviteError(null);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: getListOrgInvitesApiV1OrganizationsMeInvitesGetQueryKey({
|
||||
queryKey: getListOrgInvitesApiV1OrganizationsMeInvitesGetQueryKey(
|
||||
{
|
||||
limit: 200,
|
||||
}),
|
||||
},
|
||||
),
|
||||
});
|
||||
setInviteDialogOpen(false);
|
||||
}
|
||||
@@ -472,9 +473,11 @@ export default function OrganizationPage() {
|
||||
mutation: {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: getListOrgMembersApiV1OrganizationsMeMembersGetQueryKey({
|
||||
queryKey: getListOrgMembersApiV1OrganizationsMeMembersGetQueryKey(
|
||||
{
|
||||
limit: 200,
|
||||
}),
|
||||
},
|
||||
),
|
||||
});
|
||||
if (activeMemberId) {
|
||||
queryClient.invalidateQueries({
|
||||
@@ -535,9 +538,7 @@ export default function OrganizationPage() {
|
||||
}, [inviteDialogOpen]);
|
||||
|
||||
const orgName =
|
||||
orgQuery.data?.status === 200
|
||||
? orgQuery.data.data.name
|
||||
: "Organization";
|
||||
orgQuery.data?.status === 200 ? orgQuery.data.data.name : "Organization";
|
||||
|
||||
const handleInviteSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
@@ -710,7 +711,10 @@ export default function OrganizationPage() {
|
||||
<h1 className="text-2xl font-semibold tracking-tight text-slate-900">
|
||||
Organization
|
||||
</h1>
|
||||
<Badge variant="outline" className="flex items-center gap-2">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Building2 className="h-3.5 w-3.5" />
|
||||
{orgName}
|
||||
</Badge>
|
||||
|
||||
@@ -15,7 +15,10 @@ import {
|
||||
|
||||
import { useAuth } from "@/auth/clerk";
|
||||
import { ApiError } from "@/api/mutator";
|
||||
import { type getMyMembershipApiV1OrganizationsMeMemberGetResponse, useGetMyMembershipApiV1OrganizationsMeMemberGet } from "@/api/generated/organizations/organizations";
|
||||
import {
|
||||
type getMyMembershipApiV1OrganizationsMeMemberGetResponse,
|
||||
useGetMyMembershipApiV1OrganizationsMeMemberGet,
|
||||
} from "@/api/generated/organizations/organizations";
|
||||
import {
|
||||
type healthzHealthzGetResponse,
|
||||
useHealthzHealthzGet,
|
||||
|
||||
@@ -91,8 +91,8 @@ export function OrgSwitcher() {
|
||||
},
|
||||
});
|
||||
|
||||
const createOrgMutation = useCreateOrganizationApiV1OrganizationsPost<ApiError>(
|
||||
{
|
||||
const createOrgMutation =
|
||||
useCreateOrganizationApiV1OrganizationsPost<ApiError>({
|
||||
mutation: {
|
||||
onSuccess: () => {
|
||||
setOrgName("");
|
||||
@@ -110,8 +110,7 @@ export function OrgSwitcher() {
|
||||
setOrgError(err.message || "Unable to create organization.");
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const handleOrgChange = (value: string) => {
|
||||
if (value === "__create__") {
|
||||
|
||||
Reference in New Issue
Block a user