- );
-}
diff --git a/apps/console/src/pages/organizations/meetings/MeetingsPageLoader.tsx b/apps/console/src/pages/organizations/meetings/MeetingsPageLoader.tsx
deleted file mode 100644
index 26775daf3..000000000
--- a/apps/console/src/pages/organizations/meetings/MeetingsPageLoader.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-import { useEffect } from "react";
-import { useQueryLoader } from "react-relay";
-
-import type { MeetingsPageQuery } from "#/__generated__/core/MeetingsPageQuery.graphql";
-import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton";
-import { useOrganizationId } from "#/hooks/useOrganizationId";
-import { CoreRelayProvider } from "#/providers/CoreRelayProvider";
-
-import MeetingsPage, { meetingsPageQuery } from "./MeetingsPage";
-
-function MeetingsPageQueryLoader() {
- const organizationId = useOrganizationId();
- const [queryRef, loadQuery] = useQueryLoader(meetingsPageQuery);
-
- useEffect(() => {
- loadQuery({ organizationId });
- }, [organizationId, loadQuery]);
-
- if (!queryRef) return ;
-
- return ;
-}
-
-export default function MeetingsPageLoader() {
- return (
-
-
-
- );
-}
diff --git a/apps/console/src/pages/organizations/meetings/dialogs/CreateMeetingDialog.tsx b/apps/console/src/pages/organizations/meetings/dialogs/CreateMeetingDialog.tsx
deleted file mode 100644
index 30e8b5dd1..000000000
--- a/apps/console/src/pages/organizations/meetings/dialogs/CreateMeetingDialog.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-import { formatDatetime } from "@probo/helpers";
-import { useTranslate } from "@probo/i18n";
-import {
- Button,
- Dialog,
- DialogContent,
- DialogFooter,
- Field,
- Input,
- Spinner,
- useDialogRef,
-} from "@probo/ui";
-import { useMutation } from "react-relay";
-import { graphql } from "relay-runtime";
-import { z } from "zod";
-
-import type { CreateMeetingDialogCreateMutation } from "#/__generated__/core/CreateMeetingDialogCreateMutation.graphql";
-import { PeopleMultiSelectField } from "#/components/form/PeopleMultiSelectField";
-import { useFormWithSchema } from "#/hooks/useFormWithSchema";
-import { useOrganizationId } from "#/hooks/useOrganizationId";
-
-const createMeetingMutation = graphql`
- mutation CreateMeetingDialogCreateMutation(
- $input: CreateMeetingInput!
- $connections: [ID!]!
- ) {
- createMeeting(input: $input) {
- meetingEdge @prependEdge(connections: $connections) {
- node {
- id
- name
- date
- minutes
- attendees {
- id
- fullName
- }
- canDelete: permission(action: "core:meeting:delete")
- }
- }
- }
- }
-`;
-
-type Props = {
- children: React.ReactElement;
- connectionId: string;
-};
-
-const meetingSchema = z.object({
- name: z.string().min(1, "Meeting name is required"),
- date: z.string().min(1, "Date is required"),
- attendeeIds: z.array(z.string()).optional(),
-});
-
-export function CreateMeetingDialog({ children, connectionId }: Props) {
- const { __ } = useTranslate();
- const dialogRef = useDialogRef();
- const organizationId = useOrganizationId();
- const [createMeeting, isCreating]
- = useMutation(createMeetingMutation);
- const { handleSubmit, register, control } = useFormWithSchema(
- meetingSchema,
- {},
- );
-
- const onSubmit = (data: z.infer) => {
- createMeeting({
- variables: {
- input: {
- organizationId,
- name: data.name,
- date: formatDatetime(data.date)!,
- attendeeIds: data.attendeeIds || null,
- },
- connections: [connectionId],
- },
- onCompleted: () => {
- dialogRef.current?.close();
- },
- });
- };
-
- return (
-
- );
-}
diff --git a/apps/console/src/pages/organizations/meetings/dialogs/UpdateMeetingMinutesDialog.tsx b/apps/console/src/pages/organizations/meetings/dialogs/UpdateMeetingMinutesDialog.tsx
deleted file mode 100644
index d47ca4ab6..000000000
--- a/apps/console/src/pages/organizations/meetings/dialogs/UpdateMeetingMinutesDialog.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-import { useTranslate } from "@probo/i18n";
-import {
- Breadcrumb,
- Button,
- Dialog,
- DialogContent,
- DialogFooter,
- Spinner,
- Textarea,
- useDialogRef,
-} from "@probo/ui";
-import { forwardRef, useImperativeHandle } from "react";
-import { graphql } from "relay-runtime";
-import { z } from "zod";
-
-import type { MeetingDetailPageMeetingFragment$data } from "#/__generated__/core/MeetingDetailPageMeetingFragment.graphql";
-import type { UpdateMeetingMinutesDialogMutation } from "#/__generated__/core/UpdateMeetingMinutesDialogMutation.graphql";
-import { useFormWithSchema } from "#/hooks/useFormWithSchema";
-import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
-
-const updateMeetingMutation = graphql`
- mutation UpdateMeetingMinutesDialogMutation($input: UpdateMeetingInput!) {
- updateMeeting(input: $input) {
- meeting {
- id
- name
- date
- minutes
- attendees {
- id
- fullName
- }
- }
- }
- }
-`;
-
-type Props = {
- meeting: MeetingDetailPageMeetingFragment$data;
-};
-
-export type UpdateMeetingMinutesDialogRef = {
- open: () => void;
-};
-
-const minutesSchema = z.object({
- minutes: z.string(),
-});
-
-export const UpdateMeetingMinutesDialog = forwardRef<
- UpdateMeetingMinutesDialogRef,
- Props
->(function UpdateMeetingMinutesDialog({ meeting }, ref) {
- const { __ } = useTranslate();
- const dialogRef = useDialogRef();
- const [updateMeeting, isUpdating]
- = useMutationWithToasts(
- updateMeetingMutation,
- {
- successMessage: __("Meeting updated successfully."),
- errorMessage: __("Failed to update meeting"),
- },
- );
- const { handleSubmit, register, reset } = useFormWithSchema(minutesSchema, {
- defaultValues: {
- minutes: meeting.minutes || "",
- },
- });
-
- useImperativeHandle(ref, () => ({
- open: () => {
- reset({
- minutes: meeting.minutes || "",
- });
- dialogRef.current?.open();
- },
- }));
-
- const onSubmit = async (data: z.infer) => {
- await updateMeeting({
- variables: {
- input: {
- meetingId: meeting.id,
- minutes: data.minutes,
- },
- },
- onSuccess: () => {
- dialogRef.current?.close();
- },
- });
- };
-
- return (
- }
- >
-
-
- );
-});
diff --git a/apps/console/src/pages/organizations/meetings/tabs/MeetingMinutesTab.tsx b/apps/console/src/pages/organizations/meetings/tabs/MeetingMinutesTab.tsx
deleted file mode 100644
index eed75448f..000000000
--- a/apps/console/src/pages/organizations/meetings/tabs/MeetingMinutesTab.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-import { Markdown } from "@probo/ui";
-import { useOutletContext } from "react-router";
-
-import type { MeetingDetailPageMeetingFragment$data } from "#/__generated__/core/MeetingDetailPageMeetingFragment.graphql";
-
-export default function MeetingMinutesTab() {
- const { meeting } = useOutletContext<{
- meeting: MeetingDetailPageMeetingFragment$data;
- }>();
-
- return (
-
- {meeting.minutes
- ? (
-
- )
- : (
-
- {"No minutes recorded yet. Click \"Edit minutes\" to add meeting minutes."}
-
- )}
-
- );
-}
diff --git a/apps/console/src/pages/organizations/settings/WebhooksSettingsPage.tsx b/apps/console/src/pages/organizations/settings/WebhooksSettingsPage.tsx
index 65e95e98c..6f7798896 100644
--- a/apps/console/src/pages/organizations/settings/WebhooksSettingsPage.tsx
+++ b/apps/console/src/pages/organizations/settings/WebhooksSettingsPage.tsx
@@ -155,9 +155,6 @@ const deleteWebhookSubscriptionMutation = graphql`
`;
const EVENT_TYPES = [
- { value: "MEETING_CREATED", label: "meeting:created" },
- { value: "MEETING_UPDATED", label: "meeting:updated" },
- { value: "MEETING_DELETED", label: "meeting:deleted" },
{ value: "VENDOR_CREATED", label: "vendor:created" },
{ value: "VENDOR_UPDATED", label: "vendor:updated" },
{ value: "VENDOR_DELETED", label: "vendor:deleted" },
diff --git a/apps/console/src/routes/contextRoutes.ts b/apps/console/src/routes/contextRoutes.ts
index 0402388f6..307eced79 100644
--- a/apps/console/src/routes/contextRoutes.ts
+++ b/apps/console/src/routes/contextRoutes.ts
@@ -33,29 +33,6 @@ export const contextRoutes = [
() => import("#/pages/organizations/context/ContextPageLoader"),
),
},
- {
- path: "meetings",
- Fallback: LinkCardSkeleton,
- Component: lazy(
- () => import("#/pages/organizations/meetings/MeetingsPageLoader"),
- ),
- },
- {
- path: "meetings/:meetingId",
- Fallback: PageSkeleton,
- Component: lazy(
- () => import("#/pages/organizations/meetings/MeetingDetailPageLoader"),
- ),
- children: [
- {
- index: true,
- Fallback: LinkCardSkeleton,
- Component: lazy(
- () => import("#/pages/organizations/meetings/tabs/MeetingMinutesTab"),
- ),
- },
- ],
- },
],
},
] satisfies AppRoute[];
diff --git a/e2e/console/meeting_test.go b/e2e/console/meeting_test.go
deleted file mode 100644
index 8e16f0830..000000000
--- a/e2e/console/meeting_test.go
+++ /dev/null
@@ -1,1327 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-package console_test
-
-import (
- "fmt"
- "maps"
- "strings"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "go.probo.inc/probo/e2e/internal/factory"
- "go.probo.inc/probo/e2e/internal/testutil"
-)
-
-func TestMeeting_Create(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- tests := []struct {
- name string
- input map[string]any
- assertField string
- assertValue string
- }{
- {
- name: "with full details",
- input: map[string]any{
- "name": "Security Review Meeting",
- "date": time.Now().Format(time.RFC3339Nano),
- "minutes": "Meeting notes here",
- },
- assertField: "name",
- assertValue: "Security Review Meeting",
- },
- {
- name: "without minutes",
- input: map[string]any{
- "name": "Quick Standup",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- assertField: "name",
- assertValue: "Quick Standup",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- query := `
- mutation CreateMeeting($input: CreateMeetingInput!) {
- createMeeting(input: $input) {
- meetingEdge {
- node {
- id
- name
- date
- minutes
- }
- }
- }
- }
- `
-
- input := map[string]any{
- "organizationId": owner.GetOrganizationID().String(),
- }
- maps.Copy(input, tt.input)
-
- var result struct {
- CreateMeeting struct {
- MeetingEdge struct {
- Node struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Date string `json:"date"`
- Minutes *string `json:"minutes"`
- } `json:"node"`
- } `json:"meetingEdge"`
- } `json:"createMeeting"`
- }
-
- err := owner.Execute(query, map[string]any{"input": input}, &result)
- require.NoError(t, err)
-
- node := result.CreateMeeting.MeetingEdge.Node
- assert.NotEmpty(t, node.ID)
-
- switch tt.assertField {
- case "name":
- assert.Equal(t, tt.assertValue, node.Name)
- }
- })
- }
-}
-
-func TestMeeting_Create_Validation(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- tests := []struct {
- name string
- input map[string]any
- skipOrganization bool
- wantErrorContains string
- }{
- {
- name: "missing organizationId",
- input: map[string]any{
- "name": "Test Meeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- skipOrganization: true,
- wantErrorContains: "organizationId",
- },
- {
- name: "name with HTML tags",
- input: map[string]any{
- "name": "",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- wantErrorContains: "HTML",
- },
- {
- name: "name with newline",
- input: map[string]any{
- "name": "Test\nMeeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- wantErrorContains: "newline",
- },
- {
- name: "name with carriage return",
- input: map[string]any{
- "name": "Test\rMeeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- wantErrorContains: "carriage return",
- },
- {
- name: "name with null byte",
- input: map[string]any{
- "name": "Test\x00Meeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- wantErrorContains: "control character",
- },
- {
- name: "name with tab character",
- input: map[string]any{
- "name": "Test\tMeeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- wantErrorContains: "control character",
- },
- {
- name: "name with zero-width space",
- input: map[string]any{
- "name": "Test\u200BMeeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- wantErrorContains: "zero-width",
- },
- {
- name: "name with zero-width joiner",
- input: map[string]any{
- "name": "Test\u200DMeeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- wantErrorContains: "zero-width",
- },
- {
- name: "name with right-to-left override",
- input: map[string]any{
- "name": "Test\u202EMeeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- wantErrorContains: "bidirectional",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- query := `
- mutation CreateMeeting($input: CreateMeetingInput!) {
- createMeeting(input: $input) {
- meetingEdge {
- node {
- id
- }
- }
- }
- }
- `
-
- input := make(map[string]any)
- if !tt.skipOrganization {
- input["organizationId"] = owner.GetOrganizationID().String()
- }
- maps.Copy(input, tt.input)
-
- _, err := owner.Do(query, map[string]any{"input": input})
- require.Error(t, err)
- assert.Contains(t, err.Error(), tt.wantErrorContains)
- })
- }
-}
-
-func TestMeeting_Update(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- tests := []struct {
- name string
- setup func() string
- input func(id string) map[string]any
- assertField string
- assertValue string
- }{
- {
- name: "update name",
- setup: func() string {
- return factory.NewMeeting(owner).
- WithName("Meeting to Update").
- Create()
- },
- input: func(id string) map[string]any {
- return map[string]any{
- "meetingId": id,
- "name": "Updated Meeting Name",
- }
- },
- assertField: "name",
- assertValue: "Updated Meeting Name",
- },
- {
- name: "update minutes",
- setup: func() string {
- return factory.NewMeeting(owner).
- WithName("Minutes Test Meeting").
- Create()
- },
- input: func(id string) map[string]any {
- return map[string]any{
- "meetingId": id,
- "minutes": "Updated meeting notes",
- }
- },
- assertField: "minutes",
- assertValue: "Updated meeting notes",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- meetingID := tt.setup()
-
- query := `
- mutation UpdateMeeting($input: UpdateMeetingInput!) {
- updateMeeting(input: $input) {
- meeting {
- id
- name
- minutes
- }
- }
- }
- `
-
- var result struct {
- UpdateMeeting struct {
- Meeting struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Minutes *string `json:"minutes"`
- } `json:"meeting"`
- } `json:"updateMeeting"`
- }
-
- err := owner.Execute(query, map[string]any{"input": tt.input(meetingID)}, &result)
- require.NoError(t, err)
-
- meeting := result.UpdateMeeting.Meeting
- switch tt.assertField {
- case "name":
- assert.Equal(t, tt.assertValue, meeting.Name)
- case "minutes":
- assert.NotNil(t, meeting.Minutes)
- assert.Equal(t, tt.assertValue, *meeting.Minutes)
- }
- })
- }
-}
-
-func TestMeeting_Update_Validation(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
- baseMeetingID := factory.NewMeeting(owner).WithName("Validation Test Meeting").Create()
-
- tests := []struct {
- name string
- setup func() string
- input func(id string) map[string]any
- wantErrorContains string
- }{
- {
- name: "invalid ID format",
- setup: func() string { return "invalid-id-format" },
- input: func(id string) map[string]any {
- return map[string]any{"meetingId": id, "name": "Test"}
- },
- wantErrorContains: "base64",
- },
- {
- name: "name with HTML tags",
- setup: func() string { return baseMeetingID },
- input: func(id string) map[string]any {
- return map[string]any{"meetingId": id, "name": ""}
- },
- wantErrorContains: "HTML",
- },
- {
- name: "name with newline",
- setup: func() string { return baseMeetingID },
- input: func(id string) map[string]any {
- return map[string]any{"meetingId": id, "name": "Test\nMeeting"}
- },
- wantErrorContains: "newline",
- },
- {
- name: "name with carriage return",
- setup: func() string { return baseMeetingID },
- input: func(id string) map[string]any {
- return map[string]any{"meetingId": id, "name": "Test\rMeeting"}
- },
- wantErrorContains: "carriage return",
- },
- {
- name: "name with null byte",
- setup: func() string { return baseMeetingID },
- input: func(id string) map[string]any {
- return map[string]any{"meetingId": id, "name": "Test\x00Meeting"}
- },
- wantErrorContains: "control character",
- },
- {
- name: "name with zero-width space",
- setup: func() string { return baseMeetingID },
- input: func(id string) map[string]any {
- return map[string]any{"meetingId": id, "name": "Test\u200BMeeting"}
- },
- wantErrorContains: "zero-width",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- meetingID := tt.setup()
-
- query := `
- mutation UpdateMeeting($input: UpdateMeetingInput!) {
- updateMeeting(input: $input) {
- meeting {
- id
- }
- }
- }
- `
-
- _, err := owner.Do(query, map[string]any{"input": tt.input(meetingID)})
- require.Error(t, err)
- assert.Contains(t, err.Error(), tt.wantErrorContains)
- })
- }
-}
-
-func TestMeeting_Delete(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- t.Run("delete existing meeting", func(t *testing.T) {
- meetingID := factory.NewMeeting(owner).WithName("Meeting to Delete").Create()
-
- query := `
- mutation DeleteMeeting($input: DeleteMeetingInput!) {
- deleteMeeting(input: $input) {
- deletedMeetingId
- }
- }
- `
-
- var result struct {
- DeleteMeeting struct {
- DeletedMeetingID string `json:"deletedMeetingId"`
- } `json:"deleteMeeting"`
- }
-
- err := owner.Execute(query, map[string]any{
- "input": map[string]any{"meetingId": meetingID},
- }, &result)
- require.NoError(t, err)
- assert.Equal(t, meetingID, result.DeleteMeeting.DeletedMeetingID)
- })
-}
-
-func TestMeeting_Delete_Validation(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- tests := []struct {
- name string
- meetingID string
- wantErrorContains string
- }{
- {
- name: "invalid ID format",
- meetingID: "invalid-id-format",
- wantErrorContains: "base64",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- query := `
- mutation DeleteMeeting($input: DeleteMeetingInput!) {
- deleteMeeting(input: $input) {
- deletedMeetingId
- }
- }
- `
-
- _, err := owner.Do(query, map[string]any{
- "input": map[string]any{"meetingId": tt.meetingID},
- })
- require.Error(t, err)
- assert.Contains(t, err.Error(), tt.wantErrorContains)
- })
- }
-}
-
-func TestMeeting_List(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- meetingNames := []string{"Meeting A", "Meeting B", "Meeting C"}
- for _, name := range meetingNames {
- factory.NewMeeting(owner).WithName(name).Create()
- }
-
- query := `
- query GetMeetings($id: ID!) {
- node(id: $id) {
- ... on Organization {
- meetings(first: 10) {
- edges {
- node {
- id
- name
- }
- }
- totalCount
- }
- }
- }
- }
- `
-
- var result struct {
- Node struct {
- Meetings struct {
- Edges []struct {
- Node struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"node"`
- } `json:"edges"`
- TotalCount int `json:"totalCount"`
- } `json:"meetings"`
- } `json:"node"`
- }
-
- err := owner.Execute(query, map[string]any{
- "id": owner.GetOrganizationID().String(),
- }, &result)
- require.NoError(t, err)
- assert.GreaterOrEqual(t, result.Node.Meetings.TotalCount, 3)
-}
-
-func TestMeeting_Query(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- t.Run("query with non-existent ID returns error", func(t *testing.T) {
- query := `
- query($id: ID!) {
- node(id: $id) {
- ... on Meeting {
- id
- name
- }
- }
- }
- `
-
- err := owner.ExecuteShouldFail(query, map[string]any{
- "id": "V0wtM0tMNmJBQ1lBQUFBQUFackhLSTJfbXJJRUFZVXo",
- })
- require.Error(t, err, "Non-existent ID should return error")
- })
-}
-
-func TestMeeting_Timestamps(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- t.Run("createdAt and updatedAt are set on create", func(t *testing.T) {
- beforeCreate := time.Now().Add(-time.Second)
-
- query := `
- mutation CreateMeeting($input: CreateMeetingInput!) {
- createMeeting(input: $input) {
- meetingEdge {
- node {
- id
- createdAt
- updatedAt
- }
- }
- }
- }
- `
-
- var result struct {
- CreateMeeting struct {
- MeetingEdge struct {
- Node struct {
- ID string `json:"id"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
- } `json:"node"`
- } `json:"meetingEdge"`
- } `json:"createMeeting"`
- }
-
- err := owner.Execute(query, map[string]any{
- "input": map[string]any{
- "organizationId": owner.GetOrganizationID().String(),
- "name": "Timestamp Test Meeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- }, &result)
- require.NoError(t, err)
-
- node := result.CreateMeeting.MeetingEdge.Node
- testutil.AssertTimestampsOnCreate(t, node.CreatedAt, node.UpdatedAt, beforeCreate)
- })
-
- t.Run("updatedAt changes on update", func(t *testing.T) {
- meetingID := factory.NewMeeting(owner).WithName("Timestamp Update Test").Create()
-
- getQuery := `
- query($id: ID!) {
- node(id: $id) {
- ... on Meeting {
- createdAt
- updatedAt
- }
- }
- }
- `
-
- var getResult struct {
- Node struct {
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
- } `json:"node"`
- }
-
- err := owner.Execute(getQuery, map[string]any{"id": meetingID}, &getResult)
- require.NoError(t, err)
-
- initialCreatedAt := getResult.Node.CreatedAt
- initialUpdatedAt := getResult.Node.UpdatedAt
-
- // Wait long enough for timestamp to change (database may have second precision)
- time.Sleep(1100 * time.Millisecond)
-
- updateQuery := `
- mutation UpdateMeeting($input: UpdateMeetingInput!) {
- updateMeeting(input: $input) {
- meeting {
- createdAt
- updatedAt
- }
- }
- }
- `
-
- var updateResult struct {
- UpdateMeeting struct {
- Meeting struct {
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
- } `json:"meeting"`
- } `json:"updateMeeting"`
- }
-
- err = owner.Execute(updateQuery, map[string]any{
- "input": map[string]any{
- "meetingId": meetingID,
- "name": "Updated Timestamp Test",
- },
- }, &updateResult)
- require.NoError(t, err)
-
- meeting := updateResult.UpdateMeeting.Meeting
- testutil.AssertTimestampsOnUpdate(t, meeting.CreatedAt, meeting.UpdatedAt, initialCreatedAt, initialUpdatedAt)
- })
-}
-
-func TestMeeting_SubResolvers(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
- meetingID := factory.NewMeeting(owner).WithName("SubResolver Test Meeting").Create()
-
- t.Run("organization sub-resolver", func(t *testing.T) {
- query := `
- query($id: ID!) {
- node(id: $id) {
- ... on Meeting {
- id
- organization {
- id
- name
- }
- }
- }
- }
- `
-
- var result struct {
- Node struct {
- ID string `json:"id"`
- Organization struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"organization"`
- } `json:"node"`
- }
-
- err := owner.Execute(query, map[string]any{"id": meetingID}, &result)
- require.NoError(t, err)
- assert.Equal(t, owner.GetOrganizationID().String(), result.Node.Organization.ID)
- assert.NotEmpty(t, result.Node.Organization.Name)
- })
-}
-
-func TestMeeting_SubResolvers_WithData(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- attendee1ProfileID := factory.CreateUser(owner)
- attendee2ProfileID := factory.CreateUser(owner)
-
- var result struct {
- CreateMeeting struct {
- MeetingEdge struct {
- Node struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Attendees []struct {
- ID string `json:"id"`
- FullName string `json:"fullName"`
- } `json:"attendees"`
- } `json:"node"`
- } `json:"meetingEdge"`
- } `json:"createMeeting"`
- }
-
- query := `
- mutation($input: CreateMeetingInput!) {
- createMeeting(input: $input) {
- meetingEdge {
- node {
- id
- name
- attendees {
- id
- fullName
- }
- }
- }
- }
- }
- `
-
- err := owner.Execute(query, map[string]any{
- "input": map[string]any{
- "organizationId": owner.GetOrganizationID().String(),
- "name": "Meeting With Attendees",
- "date": time.Now().Format(time.RFC3339Nano),
- "attendeeIds": []string{attendee1ProfileID, attendee2ProfileID},
- },
- }, &result)
- require.NoError(t, err)
- assert.Equal(t, 2, len(result.CreateMeeting.MeetingEdge.Node.Attendees))
-}
-
-func TestMeeting_RBAC(t *testing.T) {
- t.Parallel()
-
- t.Run("create", func(t *testing.T) {
- t.Run("owner can create", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- _, err := owner.Do(`
- mutation CreateMeeting($input: CreateMeetingInput!) {
- createMeeting(input: $input) {
- meetingEdge { node { id } }
- }
- }
- `, map[string]any{
- "input": map[string]any{
- "organizationId": owner.GetOrganizationID().String(),
- "name": "RBAC Test Meeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- })
- require.NoError(t, err, "owner should be able to create meeting")
- })
-
- t.Run("admin can create", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- admin := testutil.NewClientInOrg(t, testutil.RoleAdmin, owner)
-
- _, err := admin.Do(`
- mutation CreateMeeting($input: CreateMeetingInput!) {
- createMeeting(input: $input) {
- meetingEdge { node { id } }
- }
- }
- `, map[string]any{
- "input": map[string]any{
- "organizationId": admin.GetOrganizationID().String(),
- "name": "RBAC Test Meeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- })
- require.NoError(t, err, "admin should be able to create meeting")
- })
-
- t.Run("viewer cannot create", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
-
- _, err := viewer.Do(`
- mutation CreateMeeting($input: CreateMeetingInput!) {
- createMeeting(input: $input) {
- meetingEdge { node { id } }
- }
- }
- `, map[string]any{
- "input": map[string]any{
- "organizationId": viewer.GetOrganizationID().String(),
- "name": "RBAC Test Meeting",
- "date": time.Now().Format(time.RFC3339Nano),
- },
- })
- testutil.RequireForbiddenError(t, err, "viewer should not be able to create meeting")
- })
- })
-
- t.Run("update", func(t *testing.T) {
- t.Run("owner can update", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- meetingID := factory.NewMeeting(owner).WithName("RBAC Update Test").Create()
-
- _, err := owner.Do(`
- mutation UpdateMeeting($input: UpdateMeetingInput!) {
- updateMeeting(input: $input) {
- meeting { id }
- }
- }
- `, map[string]any{
- "input": map[string]any{
- "meetingId": meetingID,
- "name": "Updated by Owner",
- },
- })
- require.NoError(t, err, "owner should be able to update meeting")
- })
-
- t.Run("admin can update", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- admin := testutil.NewClientInOrg(t, testutil.RoleAdmin, owner)
- meetingID := factory.NewMeeting(owner).WithName("RBAC Update Test").Create()
-
- _, err := admin.Do(`
- mutation UpdateMeeting($input: UpdateMeetingInput!) {
- updateMeeting(input: $input) {
- meeting { id }
- }
- }
- `, map[string]any{
- "input": map[string]any{
- "meetingId": meetingID,
- "name": "Updated by Admin",
- },
- })
- require.NoError(t, err, "admin should be able to update meeting")
- })
-
- t.Run("viewer cannot update", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
- meetingID := factory.NewMeeting(owner).WithName("RBAC Update Test").Create()
-
- _, err := viewer.Do(`
- mutation UpdateMeeting($input: UpdateMeetingInput!) {
- updateMeeting(input: $input) {
- meeting { id }
- }
- }
- `, map[string]any{
- "input": map[string]any{
- "meetingId": meetingID,
- "name": "Updated by Viewer",
- },
- })
- testutil.RequireForbiddenError(t, err, "viewer should not be able to update meeting")
- })
- })
-
- t.Run("delete", func(t *testing.T) {
- t.Run("owner can delete", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- meetingID := factory.NewMeeting(owner).WithName("RBAC Delete Test").Create()
-
- _, err := owner.Do(`
- mutation DeleteMeeting($input: DeleteMeetingInput!) {
- deleteMeeting(input: $input) {
- deletedMeetingId
- }
- }
- `, map[string]any{
- "input": map[string]any{"meetingId": meetingID},
- })
- require.NoError(t, err, "owner should be able to delete meeting")
- })
-
- t.Run("admin can delete", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- admin := testutil.NewClientInOrg(t, testutil.RoleAdmin, owner)
- meetingID := factory.NewMeeting(owner).WithName("RBAC Delete Test").Create()
-
- _, err := admin.Do(`
- mutation DeleteMeeting($input: DeleteMeetingInput!) {
- deleteMeeting(input: $input) {
- deletedMeetingId
- }
- }
- `, map[string]any{
- "input": map[string]any{"meetingId": meetingID},
- })
- require.NoError(t, err, "admin should be able to delete meeting")
- })
-
- t.Run("viewer cannot delete", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
- meetingID := factory.NewMeeting(owner).WithName("RBAC Delete Test").Create()
-
- _, err := viewer.Do(`
- mutation DeleteMeeting($input: DeleteMeetingInput!) {
- deleteMeeting(input: $input) {
- deletedMeetingId
- }
- }
- `, map[string]any{
- "input": map[string]any{"meetingId": meetingID},
- })
- testutil.RequireForbiddenError(t, err, "viewer should not be able to delete meeting")
- })
- })
-
- t.Run("read", func(t *testing.T) {
- t.Run("owner can read", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- meetingID := factory.NewMeeting(owner).WithName("RBAC Read Test").Create()
-
- var result struct {
- Node *struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"node"`
- }
-
- err := owner.Execute(`
- query($id: ID!) {
- node(id: $id) {
- ... on Meeting { id name }
- }
- }
- `, map[string]any{"id": meetingID}, &result)
- require.NoError(t, err, "owner should be able to read meeting")
- require.NotNil(t, result.Node, "owner should receive meeting data")
- })
-
- t.Run("admin can read", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- admin := testutil.NewClientInOrg(t, testutil.RoleAdmin, owner)
- meetingID := factory.NewMeeting(owner).WithName("RBAC Read Test").Create()
-
- var result struct {
- Node *struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"node"`
- }
-
- err := admin.Execute(`
- query($id: ID!) {
- node(id: $id) {
- ... on Meeting { id name }
- }
- }
- `, map[string]any{"id": meetingID}, &result)
- require.NoError(t, err, "admin should be able to read meeting")
- require.NotNil(t, result.Node, "admin should receive meeting data")
- })
-
- t.Run("viewer can read", func(t *testing.T) {
- owner := testutil.NewClient(t, testutil.RoleOwner)
- viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
- meetingID := factory.NewMeeting(owner).WithName("RBAC Read Test").Create()
-
- var result struct {
- Node *struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"node"`
- }
-
- err := viewer.Execute(`
- query($id: ID!) {
- node(id: $id) {
- ... on Meeting { id name }
- }
- }
- `, map[string]any{"id": meetingID}, &result)
- require.NoError(t, err, "viewer should be able to read meeting")
- require.NotNil(t, result.Node, "viewer should receive meeting data")
- })
- })
-}
-
-func TestMeeting_MaxLength_Validation(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- longName := strings.Repeat("a", 1001)
-
- t.Run("create", func(t *testing.T) {
- query := `
- mutation CreateMeeting($input: CreateMeetingInput!) {
- createMeeting(input: $input) {
- meetingEdge {
- node { id }
- }
- }
- }
- `
-
- _, err := owner.Do(query, map[string]any{
- "input": map[string]any{
- "organizationId": owner.GetOrganizationID().String(),
- "name": longName,
- "date": time.Now().Format(time.RFC3339Nano),
- },
- })
- require.Error(t, err)
- assert.Contains(t, err.Error(), "name")
- })
-
- t.Run("update", func(t *testing.T) {
- meetingID := factory.NewMeeting(owner).WithName("Max Length Test").Create()
-
- query := `
- mutation UpdateMeeting($input: UpdateMeetingInput!) {
- updateMeeting(input: $input) {
- meeting { id }
- }
- }
- `
-
- _, err := owner.Do(query, map[string]any{
- "input": map[string]any{
- "meetingId": meetingID,
- "name": longName,
- },
- })
- require.Error(t, err)
- assert.Contains(t, err.Error(), "name")
- })
-}
-
-func TestMeeting_Pagination(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- for i := range 5 {
- factory.NewMeeting(owner).
- WithName(fmt.Sprintf("Pagination Meeting %d", i)).
- Create()
- }
-
- t.Run("first/after pagination", func(t *testing.T) {
- query := `
- query($id: ID!) {
- node(id: $id) {
- ... on Organization {
- meetings(first: 2) {
- edges {
- node { id name }
- cursor
- }
- pageInfo {
- hasNextPage
- hasPreviousPage
- startCursor
- endCursor
- }
- totalCount
- }
- }
- }
- }
- `
-
- var result struct {
- Node struct {
- Meetings struct {
- Edges []struct {
- Node struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"node"`
- Cursor string `json:"cursor"`
- } `json:"edges"`
- PageInfo testutil.PageInfo `json:"pageInfo"`
- TotalCount int `json:"totalCount"`
- } `json:"meetings"`
- } `json:"node"`
- }
-
- err := owner.Execute(
- query,
- map[string]any{
- "id": owner.GetOrganizationID().String(),
- },
- &result,
- )
- require.NoError(t, err)
-
- testutil.AssertFirstPage(t, len(result.Node.Meetings.Edges), result.Node.Meetings.PageInfo, 2, true)
- assert.GreaterOrEqual(t, result.Node.Meetings.TotalCount, 5)
-
- testutil.AssertHasMorePages(t, result.Node.Meetings.PageInfo)
- queryAfter := `
- query($id: ID!, $after: CursorKey) {
- node(id: $id) {
- ... on Organization {
- meetings(first: 2, after: $after) {
- edges {
- node { id name }
- }
- pageInfo {
- hasNextPage
- hasPreviousPage
- }
- }
- }
- }
- }
- `
-
- var resultAfter struct {
- Node struct {
- Meetings struct {
- Edges []struct {
- Node struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"node"`
- } `json:"edges"`
- PageInfo testutil.PageInfo `json:"pageInfo"`
- } `json:"meetings"`
- } `json:"node"`
- }
-
- err = owner.Execute(queryAfter, map[string]any{
- "id": owner.GetOrganizationID().String(),
- "after": *result.Node.Meetings.PageInfo.EndCursor,
- }, &resultAfter)
- require.NoError(t, err)
-
- testutil.AssertMiddlePage(t, len(resultAfter.Node.Meetings.Edges), resultAfter.Node.Meetings.PageInfo, 2)
- })
-
- t.Run("last/before pagination", func(t *testing.T) {
- query := `
- query($id: ID!) {
- node(id: $id) {
- ... on Organization {
- meetings(last: 2) {
- edges {
- node { id name }
- }
- pageInfo {
- hasNextPage
- hasPreviousPage
- }
- }
- }
- }
- }
- `
-
- var result struct {
- Node struct {
- Meetings struct {
- Edges []struct {
- Node struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"node"`
- } `json:"edges"`
- PageInfo testutil.PageInfo `json:"pageInfo"`
- } `json:"meetings"`
- } `json:"node"`
- }
-
- err := owner.Execute(query, map[string]any{
- "id": owner.GetOrganizationID().String(),
- }, &result)
- require.NoError(t, err)
-
- testutil.AssertLastPage(t, len(result.Node.Meetings.Edges), result.Node.Meetings.PageInfo, 2, true)
- })
-}
-
-func TestMeeting_TenantIsolation(t *testing.T) {
- t.Parallel()
-
- org1Owner := testutil.NewClient(t, testutil.RoleOwner)
- org2Owner := testutil.NewClient(t, testutil.RoleOwner)
-
- meetingID := factory.NewMeeting(org1Owner).WithName("Org1 Meeting").Create()
-
- t.Run("cannot read meeting from another organization", func(t *testing.T) {
- query := `
- query($id: ID!) {
- node(id: $id) {
- ... on Meeting {
- id
- name
- }
- }
- }
- `
-
- var result struct {
- Node *struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"node"`
- }
-
- err := org2Owner.Execute(query, map[string]any{"id": meetingID}, &result)
- testutil.AssertNodeNotAccessible(t, err, result.Node == nil, "meeting")
- })
-
- t.Run("cannot update meeting from another organization", func(t *testing.T) {
- query := `
- mutation UpdateMeeting($input: UpdateMeetingInput!) {
- updateMeeting(input: $input) {
- meeting { id }
- }
- }
- `
-
- _, err := org2Owner.Do(query, map[string]any{
- "input": map[string]any{
- "meetingId": meetingID,
- "name": "Hijacked Meeting",
- },
- })
- require.Error(t, err, "Should not be able to update meeting from another org")
- })
-
- t.Run("cannot delete meeting from another organization", func(t *testing.T) {
- query := `
- mutation DeleteMeeting($input: DeleteMeetingInput!) {
- deleteMeeting(input: $input) {
- deletedMeetingId
- }
- }
- `
-
- _, err := org2Owner.Do(query, map[string]any{
- "input": map[string]any{
- "meetingId": meetingID,
- },
- })
- require.Error(t, err, "Should not be able to delete meeting from another org")
- })
-
- t.Run("cannot list meetings from another organization", func(t *testing.T) {
- query := `
- query($id: ID!) {
- node(id: $id) {
- ... on Organization {
- meetings(first: 100) {
- edges {
- node {
- id
- name
- }
- }
- }
- }
- }
- }
- `
-
- var result struct {
- Node struct {
- Meetings struct {
- Edges []struct {
- Node struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"node"`
- } `json:"edges"`
- } `json:"meetings"`
- } `json:"node"`
- }
-
- err := org2Owner.Execute(query, map[string]any{
- "id": org1Owner.GetOrganizationID().String(),
- }, &result)
-
- if err == nil {
- for _, edge := range result.Node.Meetings.Edges {
- assert.NotEqual(t, meetingID, edge.Node.ID, "Should not see meeting from another org")
- }
- }
- })
-}
-
-func TestMeeting_Ordering(t *testing.T) {
- t.Parallel()
- owner := testutil.NewClient(t, testutil.RoleOwner)
-
- factory.NewMeeting(owner).WithName("AAA Order Test").Create()
- factory.NewMeeting(owner).WithName("ZZZ Order Test").Create()
-
- t.Run("order by created_at descending", func(t *testing.T) {
- query := `
- query($id: ID!, $orderBy: MeetingOrder) {
- node(id: $id) {
- ... on Organization {
- meetings(first: 100, orderBy: $orderBy) {
- edges {
- node {
- id
- createdAt
- }
- }
- }
- }
- }
- }
- `
-
- var result struct {
- Node struct {
- Meetings struct {
- Edges []struct {
- Node struct {
- ID string `json:"id"`
- CreatedAt time.Time `json:"createdAt"`
- } `json:"node"`
- } `json:"edges"`
- } `json:"meetings"`
- } `json:"node"`
- }
-
- err := owner.Execute(query, map[string]any{
- "id": owner.GetOrganizationID().String(),
- "orderBy": map[string]any{
- "field": "CREATED_AT",
- "direction": "DESC",
- },
- }, &result)
- require.NoError(t, err)
-
- times := make([]time.Time, len(result.Node.Meetings.Edges))
- for i, edge := range result.Node.Meetings.Edges {
- times[i] = edge.Node.CreatedAt
- }
- testutil.AssertTimesOrderedDescending(t, times, "createdAt")
- })
-}
diff --git a/e2e/internal/factory/factory.go b/e2e/internal/factory/factory.go
index 47649605f..d279f84a0 100644
--- a/e2e/internal/factory/factory.go
+++ b/e2e/internal/factory/factory.go
@@ -755,77 +755,6 @@ func (b *DatumBuilder) Create() string {
return CreateDatum(b.client, b.ownerID, b.attrs)
}
-func CreateMeeting(c *testutil.Client, attrs ...Attrs) string {
- c.T.Helper()
-
- var a Attrs
- if len(attrs) > 0 {
- a = attrs[0]
- }
-
- const query = `
- mutation($input: CreateMeetingInput!) {
- createMeeting(input: $input) {
- meetingEdge {
- node { id }
- }
- }
- }
- `
-
- input := map[string]any{
- "organizationId": c.GetOrganizationID().String(),
- "name": a.getString("name", SafeName("Meeting")),
- "date": a.getString("date", "2025-01-15T10:00:00Z"),
- }
- if minutes := a.getStringPtr("minutes"); minutes != nil {
- input["minutes"] = *minutes
- }
-
- var result struct {
- CreateMeeting struct {
- MeetingEdge struct {
- Node struct {
- ID string `json:"id"`
- } `json:"node"`
- } `json:"meetingEdge"`
- } `json:"createMeeting"`
- }
-
- err := c.Execute(query, map[string]any{"input": input}, &result)
- require.NoError(c.T, err, "createMeeting mutation failed")
-
- return result.CreateMeeting.MeetingEdge.Node.ID
-}
-
-type MeetingBuilder struct {
- client *testutil.Client
- attrs Attrs
-}
-
-func NewMeeting(c *testutil.Client) *MeetingBuilder {
- return &MeetingBuilder{client: c, attrs: Attrs{}}
-}
-
-func (b *MeetingBuilder) WithName(name string) *MeetingBuilder {
- b.attrs["name"] = name
- return b
-}
-
-func (b *MeetingBuilder) WithDate(date string) *MeetingBuilder {
- b.attrs["date"] = date
- return b
-}
-
-func (b *MeetingBuilder) WithMinutes(minutes string) *MeetingBuilder {
- b.attrs["minutes"] = minutes
- return b
-}
-
-func (b *MeetingBuilder) Create() string {
- return CreateMeeting(b.client, b.attrs)
-}
-
type DocumentBuilder struct {
client *testutil.Client
attrs Attrs
diff --git a/packages/n8n-node/nodes/Probo/Probo.node.ts b/packages/n8n-node/nodes/Probo/Probo.node.ts
index 341973349..2aafb1749 100644
--- a/packages/n8n-node/nodes/Probo/Probo.node.ts
+++ b/packages/n8n-node/nodes/Probo/Probo.node.ts
@@ -116,11 +116,6 @@ export class Probo implements INodeType {
value: 'measure',
description: 'Manage measures',
},
- {
- name: 'Meeting',
- value: 'meeting',
- description: 'Manage meetings',
- },
{
name: 'Organization',
value: 'organization',
diff --git a/packages/n8n-node/nodes/Probo/actions/index.ts b/packages/n8n-node/nodes/Probo/actions/index.ts
index 9f206ba47..02fb55bce 100644
--- a/packages/n8n-node/nodes/Probo/actions/index.ts
+++ b/packages/n8n-node/nodes/Probo/actions/index.ts
@@ -21,7 +21,6 @@ import * as document from './document';
import * as execute from './execute';
import * as framework from './framework';
import * as measure from './measure';
-import * as meeting from './meeting';
import * as organization from './organization';
import * as user from './user';
import * as risk from './risk';
@@ -48,7 +47,6 @@ export const resources: Record = {
execute: execute as ResourceModule,
framework: framework as ResourceModule,
measure: measure as ResourceModule,
- meeting: meeting as ResourceModule,
organization: organization as ResourceModule,
user: user as ResourceModule,
risk: risk as ResourceModule,
diff --git a/packages/n8n-node/nodes/Probo/actions/meeting/create.operation.ts b/packages/n8n-node/nodes/Probo/actions/meeting/create.operation.ts
deleted file mode 100644
index 632937b2f..000000000
--- a/packages/n8n-node/nodes/Probo/actions/meeting/create.operation.ts
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
-import { proboApiRequest } from '../../GenericFunctions';
-
-export const description: INodeProperties[] = [
- {
- displayName: 'Organization ID',
- name: 'organizationId',
- type: 'string',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['create'],
- },
- },
- default: '',
- description: 'The ID of the organization',
- required: true,
- },
- {
- displayName: 'Name',
- name: 'name',
- type: 'string',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['create'],
- },
- },
- default: '',
- description: 'The name of the meeting',
- required: true,
- },
- {
- displayName: 'Date',
- name: 'date',
- type: 'dateTime',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['create'],
- },
- },
- default: '',
- description: 'The date and time of the meeting',
- required: true,
- },
- {
- displayName: 'Attendee IDs',
- name: 'attendeeIds',
- type: 'string',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['create'],
- },
- },
- default: '',
- description: 'Comma-separated list of attendee IDs (People IDs)',
- },
- {
- displayName: 'Minutes',
- name: 'minutes',
- type: 'string',
- typeOptions: {
- rows: 4,
- },
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['create'],
- },
- },
- default: '',
- description: 'The minutes of the meeting',
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['create'],
- },
- },
- options: [
- {
- displayName: 'Include Attendees',
- name: 'includeAttendees',
- type: 'boolean',
- default: false,
- description: 'Whether to include attendees in the response',
- },
- {
- displayName: 'Include Organization',
- name: 'includeOrganization',
- type: 'boolean',
- default: false,
- description: 'Whether to include organization in the response',
- },
- ],
- },
-];
-
-export async function execute(
- this: IExecuteFunctions,
- itemIndex: number,
-): Promise {
- const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
- const name = this.getNodeParameter('name', itemIndex) as string;
- const date = this.getNodeParameter('date', itemIndex) as string;
- const attendeeIdsStr = this.getNodeParameter('attendeeIds', itemIndex, '') as string;
- const minutes = this.getNodeParameter('minutes', itemIndex, '') as string;
- const options = this.getNodeParameter('options', itemIndex, {}) as {
- includeAttendees?: boolean;
- includeOrganization?: boolean;
- };
-
- const attendeesFragment = options.includeAttendees
- ? `attendees {
- id
- fullName
- }`
- : '';
-
- const organizationFragment = options.includeOrganization
- ? `organization {
- id
- name
- }`
- : '';
-
- const query = `
- mutation CreateMeeting($input: CreateMeetingInput!) {
- createMeeting(input: $input) {
- meetingEdge {
- node {
- id
- name
- date
- minutes
- ${attendeesFragment}
- ${organizationFragment}
- createdAt
- updatedAt
- }
- }
- }
- }
- `;
-
- const attendeeIds = attendeeIdsStr ? attendeeIdsStr.split(',').map((id) => id.trim()).filter(Boolean) : undefined;
-
- const input: Record = {
- organizationId,
- name,
- date: new Date(date).toISOString(),
- };
- if (attendeeIds && attendeeIds.length > 0) {
- input.attendeeIds = attendeeIds;
- }
- if (minutes) {
- input.minutes = minutes;
- }
-
- const responseData = await proboApiRequest.call(this, query, { input });
-
- return {
- json: responseData,
- pairedItem: { item: itemIndex },
- };
-}
diff --git a/packages/n8n-node/nodes/Probo/actions/meeting/delete.operation.ts b/packages/n8n-node/nodes/Probo/actions/meeting/delete.operation.ts
deleted file mode 100644
index 6df5cd8c9..000000000
--- a/packages/n8n-node/nodes/Probo/actions/meeting/delete.operation.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
-import { proboApiRequest } from '../../GenericFunctions';
-
-export const description: INodeProperties[] = [
- {
- displayName: 'Meeting ID',
- name: 'meetingId',
- type: 'string',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['delete'],
- },
- },
- default: '',
- description: 'The ID of the meeting to delete',
- required: true,
- },
-];
-
-export async function execute(
- this: IExecuteFunctions,
- itemIndex: number,
-): Promise {
- const meetingId = this.getNodeParameter('meetingId', itemIndex) as string;
-
- const query = `
- mutation DeleteMeeting($input: DeleteMeetingInput!) {
- deleteMeeting(input: $input) {
- deletedMeetingId
- }
- }
- `;
-
- const responseData = await proboApiRequest.call(this, query, { input: { meetingId } });
-
- return {
- json: responseData,
- pairedItem: { item: itemIndex },
- };
-}
diff --git a/packages/n8n-node/nodes/Probo/actions/meeting/get.operation.ts b/packages/n8n-node/nodes/Probo/actions/meeting/get.operation.ts
deleted file mode 100644
index bcab32df1..000000000
--- a/packages/n8n-node/nodes/Probo/actions/meeting/get.operation.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
-import { proboApiRequest } from '../../GenericFunctions';
-
-export const description: INodeProperties[] = [
- {
- displayName: 'Meeting ID',
- name: 'meetingId',
- type: 'string',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['get'],
- },
- },
- default: '',
- description: 'The ID of the meeting',
- required: true,
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['get'],
- },
- },
- options: [
- {
- displayName: 'Include Attendees',
- name: 'includeAttendees',
- type: 'boolean',
- default: false,
- description: 'Whether to include attendees in the response',
- },
- {
- displayName: 'Include Organization',
- name: 'includeOrganization',
- type: 'boolean',
- default: false,
- description: 'Whether to include organization in the response',
- },
- ],
- },
-];
-
-export async function execute(
- this: IExecuteFunctions,
- itemIndex: number,
-): Promise {
- const meetingId = this.getNodeParameter('meetingId', itemIndex) as string;
- const options = this.getNodeParameter('options', itemIndex, {}) as {
- includeAttendees?: boolean;
- includeOrganization?: boolean;
- };
-
- const attendeesFragment = options.includeAttendees
- ? `attendees {
- id
- fullName
- }`
- : '';
-
- const organizationFragment = options.includeOrganization
- ? `organization {
- id
- name
- }`
- : '';
-
- const query = `
- query GetMeeting($meetingId: ID!) {
- node(id: $meetingId) {
- ... on Meeting {
- id
- name
- date
- minutes
- ${attendeesFragment}
- ${organizationFragment}
- createdAt
- updatedAt
- }
- }
- }
- `;
-
- const variables = {
- meetingId,
- };
-
- const responseData = await proboApiRequest.call(this, query, variables);
-
- return {
- json: responseData,
- pairedItem: { item: itemIndex },
- };
-}
diff --git a/packages/n8n-node/nodes/Probo/actions/meeting/getAll.operation.ts b/packages/n8n-node/nodes/Probo/actions/meeting/getAll.operation.ts
deleted file mode 100644
index 9ad0e6858..000000000
--- a/packages/n8n-node/nodes/Probo/actions/meeting/getAll.operation.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-import type { INodeProperties, IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
-import { proboApiRequestAllItems } from '../../GenericFunctions';
-
-export const description: INodeProperties[] = [
- {
- displayName: 'Organization ID',
- name: 'organizationId',
- type: 'string',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['getAll'],
- },
- },
- default: '',
- description: 'The ID of the organization',
- required: true,
- },
- {
- displayName: 'Return All',
- name: 'returnAll',
- type: 'boolean',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['getAll'],
- },
- },
- default: false,
- description: 'Whether to return all results or only up to a given limit',
- },
- {
- displayName: 'Limit',
- name: 'limit',
- type: 'number',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['getAll'],
- returnAll: [false],
- },
- },
- typeOptions: {
- minValue: 1,
- },
- default: 50,
- description: 'Max number of results to return',
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['getAll'],
- },
- },
- options: [
- {
- displayName: 'Include Attendees',
- name: 'includeAttendees',
- type: 'boolean',
- default: false,
- description: 'Whether to include attendees in the response',
- },
- {
- displayName: 'Include Organization',
- name: 'includeOrganization',
- type: 'boolean',
- default: false,
- description: 'Whether to include organization in the response',
- },
- ],
- },
-];
-
-export async function execute(
- this: IExecuteFunctions,
- itemIndex: number,
-): Promise {
- const organizationId = this.getNodeParameter('organizationId', itemIndex) as string;
- const returnAll = this.getNodeParameter('returnAll', itemIndex) as boolean;
- const limit = this.getNodeParameter('limit', itemIndex, 50) as number;
- const options = this.getNodeParameter('options', itemIndex, {}) as {
- includeAttendees?: boolean;
- includeOrganization?: boolean;
- };
-
- const attendeesFragment = options.includeAttendees
- ? `attendees {
- id
- fullName
- }`
- : '';
-
- const organizationFragment = options.includeOrganization
- ? `organization {
- id
- name
- }`
- : '';
-
- const query = `
- query GetMeetings($organizationId: ID!, $first: Int, $after: CursorKey) {
- node(id: $organizationId) {
- ... on Organization {
- meetings(first: $first, after: $after) {
- edges {
- node {
- id
- name
- date
- minutes
- ${attendeesFragment}
- ${organizationFragment}
- createdAt
- updatedAt
- }
- }
- pageInfo {
- hasNextPage
- endCursor
- }
- }
- }
- }
- }
- `;
-
- const meetings = await proboApiRequestAllItems.call(
- this,
- query,
- { organizationId },
- (response) => {
- const data = response?.data as IDataObject | undefined;
- const node = data?.node as IDataObject | undefined;
- return node?.meetings as IDataObject | undefined;
- },
- returnAll,
- limit,
- );
-
- return {
- json: { meetings },
- pairedItem: { item: itemIndex },
- };
-}
diff --git a/packages/n8n-node/nodes/Probo/actions/meeting/index.ts b/packages/n8n-node/nodes/Probo/actions/meeting/index.ts
deleted file mode 100644
index a43ea6513..000000000
--- a/packages/n8n-node/nodes/Probo/actions/meeting/index.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-import type { INodeProperties } from 'n8n-workflow';
-import * as createOp from './create.operation';
-import * as updateOp from './update.operation';
-import * as deleteOp from './delete.operation';
-import * as getOp from './get.operation';
-import * as getAllOp from './getAll.operation';
-
-export const description: INodeProperties[] = [
- {
- displayName: 'Operation',
- name: 'operation',
- type: 'options',
- noDataExpression: true,
- displayOptions: {
- show: {
- resource: ['meeting'],
- },
- },
- options: [
- {
- name: 'Create',
- value: 'create',
- description: 'Create a new meeting',
- action: 'Create a meeting',
- },
- {
- name: 'Delete',
- value: 'delete',
- description: 'Delete a meeting',
- action: 'Delete a meeting',
- },
- {
- name: 'Get',
- value: 'get',
- description: 'Get a meeting',
- action: 'Get a meeting',
- },
- {
- name: 'Get Many',
- value: 'getAll',
- description: 'Get many meetings',
- action: 'Get many meetings',
- },
- {
- name: 'Update',
- value: 'update',
- description: 'Update an existing meeting',
- action: 'Update a meeting',
- },
- ],
- default: 'create',
- },
- ...createOp.description,
- ...updateOp.description,
- ...deleteOp.description,
- ...getOp.description,
- ...getAllOp.description,
-];
-
-export { createOp as create, updateOp as update, deleteOp as delete, getOp as get, getAllOp as getAll };
diff --git a/packages/n8n-node/nodes/Probo/actions/meeting/update.operation.ts b/packages/n8n-node/nodes/Probo/actions/meeting/update.operation.ts
deleted file mode 100644
index 2ab207845..000000000
--- a/packages/n8n-node/nodes/Probo/actions/meeting/update.operation.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
-import { proboApiRequest } from '../../GenericFunctions';
-
-export const description: INodeProperties[] = [
- {
- displayName: 'Meeting ID',
- name: 'meetingId',
- type: 'string',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['update'],
- },
- },
- default: '',
- description: 'The ID of the meeting to update',
- required: true,
- },
- {
- displayName: 'Name',
- name: 'name',
- type: 'string',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['update'],
- },
- },
- default: '',
- description: 'The name of the meeting',
- },
- {
- displayName: 'Date',
- name: 'date',
- type: 'dateTime',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['update'],
- },
- },
- default: '',
- description: 'The date and time of the meeting',
- },
- {
- displayName: 'Attendee IDs',
- name: 'attendeeIds',
- type: 'string',
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['update'],
- },
- },
- default: '',
- description: 'Comma-separated list of attendee IDs (People IDs)',
- },
- {
- displayName: 'Minutes',
- name: 'minutes',
- type: 'string',
- typeOptions: {
- rows: 4,
- },
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['update'],
- },
- },
- default: '',
- description: 'The minutes of the meeting',
- },
- {
- displayName: 'Options',
- name: 'options',
- type: 'collection',
- placeholder: 'Add Option',
- default: {},
- displayOptions: {
- show: {
- resource: ['meeting'],
- operation: ['update'],
- },
- },
- options: [
- {
- displayName: 'Include Attendees',
- name: 'includeAttendees',
- type: 'boolean',
- default: false,
- description: 'Whether to include attendees in the response',
- },
- {
- displayName: 'Include Organization',
- name: 'includeOrganization',
- type: 'boolean',
- default: false,
- description: 'Whether to include organization in the response',
- },
- ],
- },
-];
-
-export async function execute(
- this: IExecuteFunctions,
- itemIndex: number,
-): Promise {
- const meetingId = this.getNodeParameter('meetingId', itemIndex) as string;
- const name = this.getNodeParameter('name', itemIndex, '') as string;
- const date = this.getNodeParameter('date', itemIndex, '') as string;
- const attendeeIdsStr = this.getNodeParameter('attendeeIds', itemIndex, '') as string;
- const minutes = this.getNodeParameter('minutes', itemIndex, '') as string;
- const options = this.getNodeParameter('options', itemIndex, {}) as {
- includeAttendees?: boolean;
- includeOrganization?: boolean;
- };
-
- const attendeesFragment = options.includeAttendees
- ? `attendees {
- id
- fullName
- }`
- : '';
-
- const organizationFragment = options.includeOrganization
- ? `organization {
- id
- name
- }`
- : '';
-
- const query = `
- mutation UpdateMeeting($input: UpdateMeetingInput!) {
- updateMeeting(input: $input) {
- meeting {
- id
- name
- date
- minutes
- ${attendeesFragment}
- ${organizationFragment}
- createdAt
- updatedAt
- }
- }
- }
- `;
-
- const attendeeIds = attendeeIdsStr ? attendeeIdsStr.split(',').map((id) => id.trim()).filter(Boolean) : undefined;
-
- const input: Record = { meetingId };
- if (name) input.name = name;
- if (date) input.date = new Date(date).toISOString();
- if (attendeeIds && attendeeIds.length > 0) {
- input.attendeeIds = attendeeIds;
- }
- if (minutes !== undefined && minutes !== null) {
- input.minutes = minutes;
- }
-
- const responseData = await proboApiRequest.call(this, query, { input });
-
- return {
- json: responseData,
- pairedItem: { item: itemIndex },
- };
-}
diff --git a/pkg/cmd/webhook/create/create.go b/pkg/cmd/webhook/create/create.go
index e3cde9ac6..e10fa1568 100644
--- a/pkg/cmd/webhook/create/create.go
+++ b/pkg/cmd/webhook/create/create.go
@@ -62,11 +62,11 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Create a webhook subscription",
- Example: ` # Create a webhook for all meeting events
- prb webhook create --url https://example.com/webhook --event MEETING_CREATED --event MEETING_UPDATED
+ Example: ` # Create a webhook for vendor events
+ prb webhook create --url https://example.com/webhook --event VENDOR_CREATED --event VENDOR_UPDATED
# Create a webhook for all supported events
- prb webhook create --url https://example.com/webhook --event MEETING_CREATED --event MEETING_UPDATED --event MEETING_DELETED --event VENDOR_CREATED --event VENDOR_UPDATED --event VENDOR_DELETED`,
+ prb webhook create --url https://example.com/webhook --event VENDOR_CREATED --event VENDOR_UPDATED --event VENDOR_DELETED --event USER_CREATED --event USER_UPDATED --event USER_DELETED --event OBLIGATION_CREATED --event OBLIGATION_UPDATED --event OBLIGATION_DELETED`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
for _, e := range flagEvents {
diff --git a/pkg/coredata/audit_log_resource_type.go b/pkg/coredata/audit_log_resource_type.go
index 615fb1c46..7884148ab 100644
--- a/pkg/coredata/audit_log_resource_type.go
+++ b/pkg/coredata/audit_log_resource_type.go
@@ -89,8 +89,6 @@ func ResourceTypeName(entityType uint16) string {
return "Membership"
case TrustCenterFileEntityType:
return "TrustCenterFile"
- case MeetingEntityType:
- return "Meeting"
case DataProtectionImpactAssessmentEntityType:
return "DataProtectionImpactAssessment"
case TransferImpactAssessmentEntityType:
diff --git a/pkg/coredata/entity_type_reg.go b/pkg/coredata/entity_type_reg.go
index cf31b102b..62c07647e 100644
--- a/pkg/coredata/entity_type_reg.go
+++ b/pkg/coredata/entity_type_reg.go
@@ -68,7 +68,7 @@ const (
SAMLConfigurationEntityType uint16 = 42
PersonalAPIKeyEntityType uint16 = 43
_ uint16 = 44 // PersonalAPIKeyMembershipEntityType - removed
- MeetingEntityType uint16 = 45
+ _ uint16 = 45 // MeetingEntityType - removed
DataProtectionImpactAssessmentEntityType uint16 = 46
TransferImpactAssessmentEntityType uint16 = 47
RightsRequestEntityType uint16 = 48
@@ -196,8 +196,6 @@ func NewEntityFromID(id gid.GID) (any, bool) {
return &SAMLConfiguration{ID: id}, true
case PersonalAPIKeyEntityType:
return &PersonalAPIKey{ID: id}, true
- case MeetingEntityType:
- return &Meeting{ID: id}, true
case DataProtectionImpactAssessmentEntityType:
return &DataProtectionImpactAssessment{ID: id}, true
case TransferImpactAssessmentEntityType:
diff --git a/pkg/coredata/meeting.go b/pkg/coredata/meeting.go
deleted file mode 100644
index a87c7ffba..000000000
--- a/pkg/coredata/meeting.go
+++ /dev/null
@@ -1,306 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-package coredata
-
-import (
- "context"
- "errors"
- "fmt"
- "maps"
- "time"
-
- "github.com/jackc/pgx/v5"
- "go.gearno.de/kit/pg"
- "go.probo.inc/probo/pkg/gid"
- "go.probo.inc/probo/pkg/page"
-)
-
-type (
- Meeting struct {
- ID gid.GID `db:"id"`
- OrganizationID gid.GID `db:"organization_id"`
- Name string `db:"name"`
- Date time.Time `db:"date"`
- Minutes *string `db:"minutes"`
- CreatedAt time.Time `db:"created_at"`
- UpdatedAt time.Time `db:"updated_at"`
- }
-
- Meetings []*Meeting
-)
-
-func (m Meeting) CursorKey(orderBy MeetingOrderField) page.CursorKey {
- switch orderBy {
- case MeetingOrderFieldCreatedAt:
- return page.NewCursorKey(m.ID, m.CreatedAt)
- case MeetingOrderFieldDate:
- return page.NewCursorKey(m.ID, m.Date)
- case MeetingOrderFieldName:
- return page.NewCursorKey(m.ID, m.Name)
- }
-
- panic(fmt.Sprintf("unsupported order by: %s", orderBy))
-}
-
-// AuthorizationAttributes returns the authorization attributes for policy evaluation.
-func (m *Meeting) AuthorizationAttributes(ctx context.Context, conn pg.Querier) (map[string]string, error) {
- q := `SELECT organization_id FROM meetings WHERE id = $1 LIMIT 1;`
-
- var organizationID gid.GID
- if err := conn.QueryRow(ctx, q, m.ID).Scan(&organizationID); err != nil {
- if errors.Is(err, pgx.ErrNoRows) {
- return nil, ErrResourceNotFound
- }
- return nil, fmt.Errorf("cannot query meeting authorization attributes: %w", err)
- }
-
- return map[string]string{"organization_id": organizationID.String()}, nil
-}
-
-func (m *Meeting) LoadByID(
- ctx context.Context,
- conn pg.Querier,
- scope Scoper,
- meetingID gid.GID,
-) error {
- q := `
-SELECT
- id,
- organization_id,
- name,
- date,
- minutes,
- created_at,
- updated_at
-FROM
- meetings
-WHERE
- %s
- AND id = @meeting_id
-LIMIT 1;
-`
-
- q = fmt.Sprintf(q, scope.SQLFragment())
-
- args := pgx.StrictNamedArgs{"meeting_id": meetingID}
- maps.Copy(args, scope.SQLArguments())
-
- rows, err := conn.Query(ctx, q, args)
- if err != nil {
- return fmt.Errorf("cannot query meetings: %w", err)
- }
-
- meeting, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Meeting])
- if err != nil {
- if errors.Is(err, pgx.ErrNoRows) {
- return ErrResourceNotFound
- }
-
- return fmt.Errorf("cannot collect meeting: %w", err)
- }
-
- *m = meeting
- return nil
-}
-
-func (m *Meetings) LoadByOrganizationID(
- ctx context.Context,
- conn pg.Querier,
- scope Scoper,
- organizationID gid.GID,
- cursor *page.Cursor[MeetingOrderField],
-) error {
- q := `
-SELECT
- id,
- organization_id,
- name,
- date,
- minutes,
- created_at,
- updated_at
-FROM
- meetings
-WHERE
- %s
- AND organization_id = @organization_id
- AND %s
-`
- q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
-
- args := pgx.NamedArgs{"organization_id": organizationID}
- maps.Copy(args, scope.SQLArguments())
- maps.Copy(args, cursor.SQLArguments())
-
- rows, err := conn.Query(ctx, q, args)
- if err != nil {
- return fmt.Errorf("cannot query meetings: %w", err)
- }
-
- meetings, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Meeting])
- if err != nil {
- return fmt.Errorf("cannot collect meetings: %w", err)
- }
-
- *m = meetings
- return nil
-}
-
-func (m *Meetings) CountByOrganizationID(
- ctx context.Context,
- conn pg.Querier,
- scope Scoper,
- organizationID gid.GID,
-) (int, error) {
- q := `
-SELECT
- COUNT(*)
-FROM
- meetings
-WHERE
- %s
- AND organization_id = @organization_id
-`
- q = fmt.Sprintf(q, scope.SQLFragment())
-
- args := pgx.StrictNamedArgs{
- "organization_id": organizationID,
- }
- maps.Copy(args, scope.SQLArguments())
-
- row := conn.QueryRow(ctx, q, args)
- var count int
- if err := row.Scan(&count); err != nil {
- return 0, fmt.Errorf("cannot count meetings: %w", err)
- }
-
- return count, nil
-}
-
-func (m *Meeting) Insert(
- ctx context.Context,
- conn pg.Tx,
- scope Scoper,
-) error {
- q := `
-INSERT INTO
- meetings (
- tenant_id,
- id,
- organization_id,
- name,
- date,
- minutes,
- created_at,
- updated_at
- )
-VALUES (
- @tenant_id,
- @meeting_id,
- @organization_id,
- @name,
- @date,
- @minutes,
- @created_at,
- @updated_at
-);
-`
-
- args := pgx.StrictNamedArgs{
- "tenant_id": scope.GetTenantID(),
- "meeting_id": m.ID,
- "organization_id": m.OrganizationID,
- "name": m.Name,
- "date": m.Date,
- "minutes": m.Minutes,
- "created_at": m.CreatedAt,
- "updated_at": m.UpdatedAt,
- }
- _, err := conn.Exec(ctx, q, args)
-
- if err != nil {
- return fmt.Errorf("cannot insert meeting: %w", err)
- }
-
- return nil
-}
-
-func (m *Meeting) Update(
- ctx context.Context,
- conn pg.Tx,
- scope Scoper,
-) error {
- q := `
-UPDATE meetings
-SET
- name = @name,
- date = @date,
- minutes = @minutes,
- updated_at = @updated_at
-WHERE %s
- AND id = @meeting_id
-`
-
- q = fmt.Sprintf(q, scope.SQLFragment())
-
- args := pgx.StrictNamedArgs{
- "meeting_id": m.ID,
- "name": m.Name,
- "date": m.Date,
- "minutes": m.Minutes,
- "updated_at": m.UpdatedAt,
- }
- maps.Copy(args, scope.SQLArguments())
-
- result, err := conn.Exec(ctx, q, args)
- if err != nil {
- return fmt.Errorf("cannot update meeting: %w", err)
- }
-
- if result.RowsAffected() == 0 {
- return ErrResourceNotFound
- }
-
- return nil
-}
-
-func (m *Meeting) Delete(
- ctx context.Context,
- conn pg.Tx,
- scope Scoper,
-) error {
- q := `
-DELETE FROM meetings
-WHERE %s
- AND id = @meeting_id
-`
- q = fmt.Sprintf(q, scope.SQLFragment())
-
- args := pgx.StrictNamedArgs{
- "meeting_id": m.ID,
- }
- maps.Copy(args, scope.SQLArguments())
-
- result, err := conn.Exec(ctx, q, args)
- if err != nil {
- return fmt.Errorf("cannot delete meeting: %w", err)
- }
-
- if result.RowsAffected() == 0 {
- return ErrResourceNotFound
- }
-
- return nil
-}
diff --git a/pkg/coredata/meeting_attendee.go b/pkg/coredata/meeting_attendee.go
deleted file mode 100644
index 667a6281d..000000000
--- a/pkg/coredata/meeting_attendee.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-package coredata
-
-import (
- "context"
- "fmt"
- "time"
-
- "github.com/jackc/pgx/v5"
- "go.gearno.de/kit/pg"
- "go.probo.inc/probo/pkg/gid"
-)
-
-type (
- MeetingAttendee struct {
- MeetingID gid.GID `db:"meeting_id"`
- OrganizationID gid.GID `db:"organization_id"`
- AttendeeID gid.GID `db:"attendee_profile_id"`
- CreatedAt time.Time `db:"created_at"`
- }
-
- MeetingAttendees []*MeetingAttendee
-)
-
-func (ma *MeetingAttendees) Merge(
- ctx context.Context,
- conn pg.Querier,
- scope Scoper,
- meetingID gid.GID,
- organizationID gid.GID,
- attendeeIDs []gid.GID,
-) error {
- q := `
-WITH attendee_ids AS (
- SELECT
- unnest(@attendee_ids::text[]) AS attendee_profile_id,
- @tenant_id AS tenant_id,
- @meeting_id AS meeting_id,
- @organization_id AS organization_id,
- @created_at::timestamptz AS created_at
-)
-MERGE INTO meeting_attendees AS tgt
-USING attendee_ids AS src
-ON tgt.tenant_id = src.tenant_id
- AND tgt.meeting_id = src.meeting_id
- AND tgt.attendee_profile_id = src.attendee_profile_id
-WHEN NOT MATCHED THEN
- INSERT (tenant_id, meeting_id, attendee_profile_id, organization_id, created_at)
- VALUES (src.tenant_id, src.meeting_id, src.attendee_profile_id, src.organization_id, src.created_at)
-WHEN NOT MATCHED BY SOURCE
- AND tgt.tenant_id = @tenant_id AND tgt.meeting_id = @meeting_id
- THEN DELETE
-`
-
- args := pgx.StrictNamedArgs{
- "tenant_id": scope.GetTenantID(),
- "meeting_id": meetingID,
- "organization_id": organizationID,
- "created_at": time.Now(),
- "attendee_ids": attendeeIDs,
- }
-
- _, err := conn.Exec(ctx, q, args)
- if err != nil {
- return fmt.Errorf("cannot merge meeting attendees: %w", err)
- }
-
- return nil
-}
diff --git a/pkg/coredata/meeting_order_field.go b/pkg/coredata/meeting_order_field.go
deleted file mode 100644
index 5510536d5..000000000
--- a/pkg/coredata/meeting_order_field.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-package coredata
-
-import (
- "fmt"
-)
-
-type (
- MeetingOrderField string
-)
-
-const (
- MeetingOrderFieldDate MeetingOrderField = "DATE"
- MeetingOrderFieldName MeetingOrderField = "NAME"
- MeetingOrderFieldCreatedAt MeetingOrderField = "CREATED_AT"
-)
-
-func (p MeetingOrderField) Column() string {
- return string(p)
-}
-
-func (p MeetingOrderField) String() string {
- return string(p)
-}
-
-func (p MeetingOrderField) IsValid() bool {
- switch p {
- case MeetingOrderFieldDate, MeetingOrderFieldName, MeetingOrderFieldCreatedAt:
- return true
- }
- return false
-}
-
-func (p MeetingOrderField) MarshalText() ([]byte, error) {
- return []byte(p.String()), nil
-}
-
-func (p *MeetingOrderField) UnmarshalText(text []byte) error {
- *p = MeetingOrderField(text)
- if !p.IsValid() {
- return fmt.Errorf("%s is not a valid MeetingOrderField", string(text))
- }
- return nil
-}
diff --git a/pkg/coredata/membership_profile.go b/pkg/coredata/membership_profile.go
index 0ca7c4264..972f49bf6 100644
--- a/pkg/coredata/membership_profile.go
+++ b/pkg/coredata/membership_profile.go
@@ -718,122 +718,6 @@ WHERE
return count, nil
}
-func (p *MembershipProfiles) LoadByMeetingID(
- ctx context.Context,
- conn pg.Querier,
- scope Scoper,
- meetingID gid.GID,
-) error {
- q := `
-WITH attendees AS (
- SELECT
- p.id,
- p.tenant_id,
- p.identity_id,
- p.organization_id,
- i.email_address,
- p.source,
- p.state,
- p.full_name,
- p.kind,
- p.additional_email_addresses,
- p.position,
- p.contract_start_date,
- p.contract_end_date,
- p.user_name,
- p.external_id,
- p.nickname,
- p.locale,
- p.timezone,
- p.profile_url,
- p.preferred_language,
- p.given_name,
- p.family_name,
- p.formatted_name,
- p.middle_name,
- p.honorific_prefix,
- p.honorific_suffix,
- p.employee_number,
- p.department,
- p.cost_center,
- p.enterprise_organization,
- p.division,
- p.manager_value,
- p.created_at,
- p.updated_at,
- ma.created_at AS attendee_created_at
- FROM
- iam_membership_profiles p
- INNER JOIN identities i
- ON i.id = p.identity_id
- INNER JOIN
- meeting_attendees ma ON p.id = ma.attendee_profile_id
- WHERE
- ma.meeting_id = @meeting_id
-)
-SELECT
- id,
- identity_id,
- organization_id,
- kind,
- email_address,
- source,
- state,
- full_name,
- additional_email_addresses,
- position,
- contract_start_date,
- contract_end_date,
- '' AS organization_name,
- user_name,
- external_id,
- nickname,
- locale,
- timezone,
- profile_url,
- preferred_language,
- given_name,
- family_name,
- formatted_name,
- middle_name,
- honorific_prefix,
- honorific_suffix,
- employee_number,
- department,
- cost_center,
- enterprise_organization,
- division,
- manager_value,
- created_at,
- updated_at
-FROM
- attendees
-WHERE
- %s
-ORDER BY
- attendee_created_at ASC
-`
-
- q = fmt.Sprintf(q, scope.SQLFragment())
-
- args := pgx.NamedArgs{"meeting_id": meetingID}
- maps.Copy(args, scope.SQLArguments())
-
- rows, err := conn.Query(ctx, q, args)
- if err != nil {
- return fmt.Errorf("cannot query profiles: %w", err)
- }
-
- profiles, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[MembershipProfile])
- if err != nil {
- return fmt.Errorf("cannot collect profiles: %w", err)
- }
-
- *p = profiles
-
- return nil
-}
-
func (p *MembershipProfiles) LoadAwaitingSigning(
ctx context.Context,
conn pg.Querier,
diff --git a/pkg/coredata/migrations/20260420T130000Z.sql b/pkg/coredata/migrations/20260420T130000Z.sql
new file mode 100644
index 000000000..a9bc04a3d
--- /dev/null
+++ b/pkg/coredata/migrations/20260420T130000Z.sql
@@ -0,0 +1,16 @@
+-- Copyright (c) 2026 Probo Inc .
+--
+-- Permission to use, copy, modify, and/or distribute this software for any
+-- purpose with or without fee is hereby granted, provided that the above
+-- copyright notice and this permission notice appear in all copies.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+-- PERFORMANCE OF THIS SOFTWARE.
+
+DROP TABLE meeting_attendees;
+DROP TABLE meetings;
diff --git a/pkg/coredata/webhook_event_type.go b/pkg/coredata/webhook_event_type.go
index 96ffcb859..320154116 100644
--- a/pkg/coredata/webhook_event_type.go
+++ b/pkg/coredata/webhook_event_type.go
@@ -23,9 +23,6 @@ import (
type WebhookEventType string
const (
- WebhookEventTypeMeetingCreated WebhookEventType = "meeting:created"
- WebhookEventTypeMeetingUpdated WebhookEventType = "meeting:updated"
- WebhookEventTypeMeetingDeleted WebhookEventType = "meeting:deleted"
WebhookEventTypeVendorCreated WebhookEventType = "vendor:created"
WebhookEventTypeVendorUpdated WebhookEventType = "vendor:updated"
WebhookEventTypeVendorDeleted WebhookEventType = "vendor:deleted"
@@ -43,8 +40,7 @@ func (w WebhookEventType) String() string {
func (w WebhookEventType) IsValid() bool {
switch w {
- case WebhookEventTypeMeetingCreated, WebhookEventTypeMeetingUpdated, WebhookEventTypeMeetingDeleted,
- WebhookEventTypeVendorCreated, WebhookEventTypeVendorUpdated, WebhookEventTypeVendorDeleted,
+ case WebhookEventTypeVendorCreated, WebhookEventTypeVendorUpdated, WebhookEventTypeVendorDeleted,
WebhookEventTypeUserCreated, WebhookEventTypeUserUpdated, WebhookEventTypeUserDeleted,
WebhookEventTypeObligationCreated, WebhookEventTypeObligationUpdated, WebhookEventTypeObligationDeleted:
return true
diff --git a/pkg/probo/actions.go b/pkg/probo/actions.go
index 8c2edd60c..e3c0e4d98 100644
--- a/pkg/probo/actions.go
+++ b/pkg/probo/actions.go
@@ -294,13 +294,6 @@ const (
ActionFileGet = "core:file:get"
ActionFileDownloadUrl = "core:file:download-url"
- // Meeting actions
- ActionMeetingList = "core:meeting:list"
- ActionMeetingGet = "core:meeting:get"
- ActionMeetingCreate = "core:meeting:create"
- ActionMeetingUpdate = "core:meeting:update"
- ActionMeetingDelete = "core:meeting:delete"
-
// Connector actions
ActionConnectorInitiate = "core:connector:initiate"
diff --git a/pkg/probo/meeting_service.go b/pkg/probo/meeting_service.go
deleted file mode 100644
index af83cc4a0..000000000
--- a/pkg/probo/meeting_service.go
+++ /dev/null
@@ -1,338 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-package probo
-
-import (
- "context"
- "fmt"
- "time"
-
- "go.gearno.de/kit/pg"
- "go.probo.inc/probo/pkg/coredata"
- "go.probo.inc/probo/pkg/gid"
- "go.probo.inc/probo/pkg/page"
- "go.probo.inc/probo/pkg/validator"
- "go.probo.inc/probo/pkg/webhook"
-
- webhooktypes "go.probo.inc/probo/pkg/webhook/types"
-)
-
-type MeetingService struct {
- svc *TenantService
-}
-
-type (
- CreateMeetingRequest struct {
- OrganizationID gid.GID
- Name string
- Date time.Time
- AttendeeIDs []gid.GID
- Minutes *string
- }
-
- UpdateMeetingRequest struct {
- MeetingID gid.GID
- Name *string
- Date *time.Time
- AttendeeIDs []gid.GID
- Minutes **string
- }
-)
-
-const (
- MinutesMaxLength = 50_000
-)
-
-func (cmr *CreateMeetingRequest) Validate() error {
- v := validator.New()
-
- v.Check(cmr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
- v.Check(cmr.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
- v.Check(cmr.Date, "date", validator.Required())
- v.CheckEach(cmr.AttendeeIDs, "attendee_ids", func(index int, item any) {
- v.Check(item, fmt.Sprintf("attendee_ids[%d]", index), validator.Required(), validator.GID(coredata.MembershipProfileEntityType))
- })
- v.Check(cmr.Minutes, "minutes", validator.SafeText(MinutesMaxLength))
-
- return v.Error()
-}
-
-func (umr *UpdateMeetingRequest) Validate() error {
- v := validator.New()
-
- v.Check(umr.MeetingID, "meeting_id", validator.Required(), validator.GID(coredata.MeetingEntityType))
- v.Check(umr.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
- v.CheckEach(umr.AttendeeIDs, "attendee_ids", func(index int, item any) {
- v.Check(item, fmt.Sprintf("attendee_ids[%d]", index), validator.Required(), validator.GID(coredata.MembershipProfileEntityType))
- })
- v.Check(umr.Minutes, "minutes", validator.SafeText(MinutesMaxLength))
-
- return v.Error()
-}
-
-func (s MeetingService) ListForOrganizationID(
- ctx context.Context,
- organizationID gid.GID,
- cursor *page.Cursor[coredata.MeetingOrderField],
-) (*page.Page[*coredata.Meeting, coredata.MeetingOrderField], error) {
- var meetings coredata.Meetings
- organization := &coredata.Organization{}
-
- err := s.svc.pg.WithConn(
- ctx,
- func(ctx context.Context, conn pg.Querier) error {
- if err := organization.LoadByID(ctx, conn, s.svc.scope, organizationID); err != nil {
- return fmt.Errorf("cannot load organization: %w", err)
- }
-
- err := meetings.LoadByOrganizationID(
- ctx,
- conn,
- s.svc.scope,
- organization.ID,
- cursor,
- )
- if err != nil {
- return fmt.Errorf("cannot load meetings: %w", err)
- }
-
- return nil
- },
- )
-
- if err != nil {
- return nil, err
- }
-
- return page.NewPage(meetings, cursor), nil
-}
-
-func (s MeetingService) CountForOrganizationID(
- ctx context.Context,
- organizationID gid.GID,
-) (int, error) {
- var count int
-
- err := s.svc.pg.WithConn(
- ctx,
- func(ctx context.Context, conn pg.Querier) (err error) {
- meetings := &coredata.Meetings{}
- count, err = meetings.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID)
- if err != nil {
- return fmt.Errorf("cannot count meetings: %w", err)
- }
-
- return nil
- },
- )
-
- if err != nil {
- return 0, err
- }
-
- return count, nil
-}
-
-func (s MeetingService) Get(
- ctx context.Context,
- meetingID gid.GID,
-) (*coredata.Meeting, error) {
- meeting := &coredata.Meeting{}
-
- err := s.svc.pg.WithConn(
- ctx,
- func(ctx context.Context, conn pg.Querier) error {
- return meeting.LoadByID(ctx, conn, s.svc.scope, meetingID)
- },
- )
-
- if err != nil {
- return nil, err
- }
-
- return meeting, nil
-}
-
-func (s MeetingService) Create(
- ctx context.Context,
- req CreateMeetingRequest,
-) (*coredata.Meeting, error) {
- if err := req.Validate(); err != nil {
- return nil, fmt.Errorf("invalid request: %w", err)
- }
-
- now := time.Now()
- var meeting *coredata.Meeting
- organization := &coredata.Organization{}
-
- err := s.svc.pg.WithTx(
- ctx,
- func(ctx context.Context, conn pg.Tx) error {
- if err := organization.LoadByID(ctx, conn, s.svc.scope, req.OrganizationID); err != nil {
- return fmt.Errorf("cannot load organization: %w", err)
- }
-
- meeting = &coredata.Meeting{
- ID: gid.New(organization.ID.TenantID(), coredata.MeetingEntityType),
- OrganizationID: organization.ID,
- Name: req.Name,
- Date: req.Date,
- Minutes: req.Minutes,
- CreatedAt: now,
- UpdatedAt: now,
- }
-
- if err := meeting.Insert(ctx, conn, s.svc.scope); err != nil {
- return fmt.Errorf("cannot insert meeting: %w", err)
- }
-
- if len(req.AttendeeIDs) > 0 {
- var attendeeProfiles coredata.MembershipProfiles
- if err := attendeeProfiles.LoadByIDs(ctx, conn, s.svc.scope, req.AttendeeIDs); err != nil {
- return fmt.Errorf("cannot load attendee profiles: %w", err)
- }
-
- var attendees coredata.MeetingAttendees
- if err := attendees.Merge(ctx, conn, s.svc.scope, meeting.ID, organization.ID, req.AttendeeIDs); err != nil {
- return fmt.Errorf("cannot merge meeting attendees: %w", err)
- }
- }
-
- if err := webhook.InsertData(ctx, conn, s.svc.scope, organization.ID, coredata.WebhookEventTypeMeetingCreated, webhooktypes.NewMeeting(meeting)); err != nil {
- return fmt.Errorf("cannot insert webhook event: %w", err)
- }
-
- return nil
- },
- )
-
- if err != nil {
- return nil, err
- }
-
- return meeting, nil
-}
-
-func (s MeetingService) GetAttendees(
- ctx context.Context,
- meetingID gid.GID,
-) (coredata.MembershipProfiles, error) {
- var attendees coredata.MembershipProfiles
-
- err := s.svc.pg.WithConn(
- ctx,
- func(ctx context.Context, conn pg.Querier) error {
- return attendees.LoadByMeetingID(ctx, conn, s.svc.scope, meetingID)
- },
- )
-
- if err != nil {
- return nil, err
- }
-
- return attendees, nil
-}
-
-func (s MeetingService) Update(
- ctx context.Context,
- req UpdateMeetingRequest,
-) (*coredata.Meeting, error) {
- if err := req.Validate(); err != nil {
- return nil, fmt.Errorf("invalid request: %w", err)
- }
-
- meeting := &coredata.Meeting{}
-
- err := s.svc.pg.WithTx(
- ctx,
- func(ctx context.Context, conn pg.Tx) error {
- if err := meeting.LoadByID(ctx, conn, s.svc.scope, req.MeetingID); err != nil {
- return fmt.Errorf("cannot load meeting: %w", err)
- }
-
- if req.Name != nil {
- meeting.Name = *req.Name
- }
- if req.Date != nil {
- meeting.Date = *req.Date
- }
- if req.Minutes != nil {
- meeting.Minutes = *req.Minutes
- }
-
- meeting.UpdatedAt = time.Now()
-
- if err := meeting.Update(ctx, conn, s.svc.scope); err != nil {
- return fmt.Errorf("cannot update meeting: %w", err)
- }
-
- if req.AttendeeIDs != nil {
- var attendeeProfiles coredata.MembershipProfiles
- if err := attendeeProfiles.LoadByIDs(ctx, conn, s.svc.scope, req.AttendeeIDs); err != nil {
- return fmt.Errorf("cannot load attendee profiles: %w", err)
- }
-
- var attendees coredata.MeetingAttendees
- if err := attendees.Merge(ctx, conn, s.svc.scope, meeting.ID, meeting.OrganizationID, req.AttendeeIDs); err != nil {
- return fmt.Errorf("cannot merge meeting attendees: %w", err)
- }
- }
-
- if err := webhook.InsertData(ctx, conn, s.svc.scope, meeting.OrganizationID, coredata.WebhookEventTypeMeetingUpdated, webhooktypes.NewMeeting(meeting)); err != nil {
- return fmt.Errorf("cannot insert webhook event: %w", err)
- }
-
- return nil
- },
- )
-
- if err != nil {
- return nil, err
- }
-
- return meeting, nil
-}
-
-func (s MeetingService) Delete(
- ctx context.Context,
- meetingID gid.GID,
-) error {
- meeting := &coredata.Meeting{ID: meetingID}
-
- err := s.svc.pg.WithTx(
- ctx,
- func(ctx context.Context, conn pg.Tx) error {
- if err := meeting.LoadByID(ctx, conn, s.svc.scope, meetingID); err != nil {
- return fmt.Errorf("cannot load meeting: %w", err)
- }
-
- if err := webhook.InsertData(ctx, conn, s.svc.scope, meeting.OrganizationID, coredata.WebhookEventTypeMeetingDeleted, webhooktypes.NewMeeting(meeting)); err != nil {
- return fmt.Errorf("cannot insert webhook event: %w", err)
- }
-
- if err := meeting.Delete(ctx, conn, s.svc.scope); err != nil {
- return fmt.Errorf("cannot delete meeting: %w", err)
- }
-
- return nil
- },
- )
-
- if err != nil {
- return err
- }
-
- return nil
-}
diff --git a/pkg/probo/policies.go b/pkg/probo/policies.go
index ff06642e9..1c7fa8ffb 100644
--- a/pkg/probo/policies.go
+++ b/pkg/probo/policies.go
@@ -75,7 +75,6 @@ var ViewerPolicy = policy.NewPolicy(
ActionDataProtectionImpactAssessmentGet, ActionDataProtectionImpactAssessmentList,
ActionTransferImpactAssessmentGet, ActionTransferImpactAssessmentList,
ActionSnapshotGet, ActionSnapshotList,
- ActionMeetingGet, ActionMeetingList,
ActionFileGet, ActionFileDownloadUrl,
ActionSlackConnectionList, ActionConnectorList,
ActionRightsRequestGet, ActionRightsRequestList,
diff --git a/pkg/probo/service.go b/pkg/probo/service.go
index cc9bba32d..3d6726615 100644
--- a/pkg/probo/service.go
+++ b/pkg/probo/service.go
@@ -101,7 +101,6 @@ type (
Assets *AssetService
Data *DatumService
Audits *AuditService
- Meetings *MeetingService
WebhookSubscriptions *WebhookSubscriptionService
Reports *ReportService
TrustCenters *TrustCenterService
@@ -246,7 +245,6 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tenantService.Assets = &AssetService{svc: tenantService}
tenantService.Data = &DatumService{svc: tenantService}
tenantService.Audits = &AuditService{svc: tenantService}
- tenantService.Meetings = &MeetingService{svc: tenantService}
tenantService.WebhookSubscriptions = &WebhookSubscriptionService{svc: tenantService}
tenantService.Reports = &ReportService{svc: tenantService}
tenantService.TrustCenters = &TrustCenterService{svc: tenantService}
diff --git a/pkg/server/api/console/v1/base_resolvers.go b/pkg/server/api/console/v1/base_resolvers.go
index 2147d6a8c..8b30bd235 100644
--- a/pkg/server/api/console/v1/base_resolvers.go
+++ b/pkg/server/api/console/v1/base_resolvers.go
@@ -275,15 +275,6 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
}
return types.NewTrustCenterAccess(trustCenterAccess), nil
}
- case coredata.MeetingEntityType:
- action = probo.ActionMeetingGet
- loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
- meeting, err := prb.Meetings.Get(ctx, id)
- if err != nil {
- return nil, err
- }
- return types.NewMeeting(meeting), nil
- }
case coredata.RightsRequestEntityType:
action = probo.ActionRightsRequestGet
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
diff --git a/pkg/server/api/console/v1/graphql/meeting.graphql b/pkg/server/api/console/v1/graphql/meeting.graphql
deleted file mode 100644
index dbcb15fc2..000000000
--- a/pkg/server/api/console/v1/graphql/meeting.graphql
+++ /dev/null
@@ -1,82 +0,0 @@
-enum MeetingOrderField
- @goModel(model: "go.probo.inc/probo/pkg/coredata.MeetingOrderField") {
- DATE @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeetingOrderFieldDate")
- NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeetingOrderFieldName")
- CREATED_AT
- @goEnum(
- value: "go.probo.inc/probo/pkg/coredata.MeetingOrderFieldCreatedAt"
- )
-}
-
-input MeetingOrder
- @goModel(
- model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MeetingOrderBy"
- ) {
- direction: OrderDirection!
- field: MeetingOrderField!
-}
-
-type Meeting implements Node {
- id: ID!
- name: String!
- date: Datetime!
- minutes: String
- attendees: [Profile!]! @goField(forceResolver: true)
- organization: Organization! @goField(forceResolver: true)
- createdAt: Datetime!
- updatedAt: Datetime!
-
- permission(action: String!): Boolean! @goField(forceResolver: true)
-}
-
-type MeetingConnection
- @goModel(
- model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MeetingConnection"
- ) {
- totalCount: Int! @goField(forceResolver: true)
- edges: [MeetingEdge!]!
- pageInfo: PageInfo!
-}
-
-type MeetingEdge {
- cursor: CursorKey!
- node: Meeting!
-}
-
-extend type Mutation {
- createMeeting(input: CreateMeetingInput!): CreateMeetingPayload!
- updateMeeting(input: UpdateMeetingInput!): UpdateMeetingPayload!
- deleteMeeting(input: DeleteMeetingInput!): DeleteMeetingPayload!
-}
-
-input CreateMeetingInput {
- organizationId: ID!
- name: String!
- date: Datetime!
- attendeeIds: [ID!]
- minutes: String
-}
-
-input UpdateMeetingInput {
- meetingId: ID!
- name: String
- date: Datetime
- attendeeIds: [ID!]
- minutes: String @goField(omittable: true)
-}
-
-input DeleteMeetingInput {
- meetingId: ID!
-}
-
-type CreateMeetingPayload {
- meetingEdge: MeetingEdge!
-}
-
-type UpdateMeetingPayload {
- meeting: Meeting!
-}
-
-type DeleteMeetingPayload {
- deletedMeetingId: ID!
-}
diff --git a/pkg/server/api/console/v1/graphql/organization.graphql b/pkg/server/api/console/v1/graphql/organization.graphql
index 1e112f20e..fe7c1aa23 100644
--- a/pkg/server/api/console/v1/graphql/organization.graphql
+++ b/pkg/server/api/console/v1/graphql/organization.graphql
@@ -245,14 +245,6 @@ type Organization implements Node {
filter: MeasureFilter
): MeasureConnection! @goField(forceResolver: true)
- meetings(
- first: Int
- after: CursorKey
- last: Int
- before: CursorKey
- orderBy: MeetingOrder
- ): MeetingConnection! @goField(forceResolver: true)
-
obligations(
first: Int
after: CursorKey
diff --git a/pkg/server/api/console/v1/graphql/webhook.graphql b/pkg/server/api/console/v1/graphql/webhook.graphql
index 0f988cd14..3af9d7bb0 100644
--- a/pkg/server/api/console/v1/graphql/webhook.graphql
+++ b/pkg/server/api/console/v1/graphql/webhook.graphql
@@ -1,11 +1,5 @@
enum WebhookEventType
@goModel(model: "go.probo.inc/probo/pkg/coredata.WebhookEventType") {
- MEETING_CREATED
- @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeMeetingCreated")
- MEETING_UPDATED
- @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeMeetingUpdated")
- MEETING_DELETED
- @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeMeetingDeleted")
VENDOR_CREATED
@goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorCreated")
VENDOR_UPDATED
diff --git a/pkg/server/api/console/v1/meeting_resolvers.go b/pkg/server/api/console/v1/meeting_resolvers.go
deleted file mode 100644
index 8a6d96d4a..000000000
--- a/pkg/server/api/console/v1/meeting_resolvers.go
+++ /dev/null
@@ -1,196 +0,0 @@
-package console_v1
-
-// This file will be automatically regenerated based on the schema, any resolver
-// implementations
-// will be copied through when generating and any unknown code will be moved to the end.
-// Code generated by github.com/99designs/gqlgen version v0.17.87
-
-import (
- "context"
- "errors"
-
- "github.com/vikstrous/dataloadgen"
- "go.gearno.de/kit/log"
- "go.probo.inc/probo/pkg/coredata"
- "go.probo.inc/probo/pkg/gid"
- "go.probo.inc/probo/pkg/iam"
- "go.probo.inc/probo/pkg/probo"
- "go.probo.inc/probo/pkg/server/api/console/v1/dataloader"
- "go.probo.inc/probo/pkg/server/api/console/v1/schema"
- "go.probo.inc/probo/pkg/server/api/console/v1/types"
- "go.probo.inc/probo/pkg/server/gqlutils"
- "go.probo.inc/probo/pkg/validator"
-)
-
-// Attendees is the resolver for the attendees field.
-func (r *meetingResolver) Attendees(ctx context.Context, obj *types.Meeting) ([]*types.Profile, error) {
- // TODO bug must be paginated
-
- if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil {
- return nil, err
- }
-
- prb := r.ProboService(ctx, obj.ID.TenantID())
-
- attendees, err := prb.Meetings.GetAttendees(ctx, obj.ID)
- if err != nil {
- r.logger.ErrorCtx(ctx, "cannot load meeting attendees", log.Error(err))
- return nil, gqlutils.Internal(ctx)
- }
-
- if len(attendees) == 0 {
- return []*types.Profile{}, nil
- }
-
- people := make([]*types.Profile, len(attendees))
- for i, attendee := range attendees {
- people[i] = types.NewProfile(attendee)
- }
-
- return people, nil
-}
-
-// Organization is the resolver for the organization field.
-func (r *meetingResolver) Organization(ctx context.Context, obj *types.Meeting) (*types.Organization, error) {
- if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil {
- return nil, err
- }
-
- loaders := dataloader.FromContext(ctx)
-
- organization, err := loaders.Organization.Load(ctx, obj.Organization.ID)
- if err != nil {
- if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
- return nil, gqlutils.NotFound(ctx, err)
- }
-
- r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err))
- return nil, gqlutils.Internal(ctx)
- }
-
- return types.NewOrganization(organization), nil
-}
-
-// Permission is the resolver for the permission field.
-func (r *meetingResolver) Permission(ctx context.Context, obj *types.Meeting, action string) (bool, error) {
- return r.Resolver.Permission(ctx, obj, action)
-}
-
-// TotalCount is the resolver for the totalCount field.
-func (r *meetingConnectionResolver) TotalCount(ctx context.Context, obj *types.MeetingConnection) (int, error) {
- if err := r.authorize(ctx, obj.ParentID, probo.ActionMeetingList); err != nil {
- return 0, err
- }
-
- prb := r.ProboService(ctx, obj.ParentID.TenantID())
-
- switch obj.Resolver.(type) {
- case *organizationResolver:
- count, err := prb.Meetings.CountForOrganizationID(ctx, obj.ParentID)
- if err != nil {
- r.logger.ErrorCtx(ctx, "cannot count meetings", log.Error(err))
- return 0, gqlutils.Internal(ctx)
- }
- return count, nil
- }
-
- r.logger.ErrorCtx(ctx, "unsupported resolver")
- return 0, gqlutils.Internal(ctx)
-}
-
-// CreateMeeting is the resolver for the createMeeting field.
-func (r *mutationResolver) CreateMeeting(ctx context.Context, input types.CreateMeetingInput) (*types.CreateMeetingPayload, error) {
- if err := r.authorize(ctx, input.OrganizationID, probo.ActionMeetingCreate); err != nil {
- return nil, err
- }
-
- prb := r.ProboService(ctx, input.OrganizationID.TenantID())
-
- meeting, err := prb.Meetings.Create(
- ctx,
- probo.CreateMeetingRequest{
- OrganizationID: input.OrganizationID,
- Name: input.Name,
- Date: input.Date,
- AttendeeIDs: input.AttendeeIds,
- Minutes: input.Minutes,
- },
- )
- if err != nil {
- if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
- return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
- }
- r.logger.ErrorCtx(ctx, "cannot create meeting", log.Error(err))
- return nil, gqlutils.Internal(ctx)
- }
-
- return &types.CreateMeetingPayload{
- MeetingEdge: types.NewMeetingEdge(meeting, coredata.MeetingOrderFieldCreatedAt),
- }, nil
-}
-
-// UpdateMeeting is the resolver for the updateMeeting field.
-func (r *mutationResolver) UpdateMeeting(ctx context.Context, input types.UpdateMeetingInput) (*types.UpdateMeetingPayload, error) {
- if err := r.authorize(ctx, input.MeetingID, probo.ActionMeetingUpdate); err != nil {
- return nil, err
- }
-
- prb := r.ProboService(ctx, input.MeetingID.TenantID())
-
- var attendeeIDs []gid.GID
- if input.AttendeeIds != nil {
- attendeeIDs = input.AttendeeIds
- }
-
- meeting, err := prb.Meetings.Update(
- ctx,
- probo.UpdateMeetingRequest{
- MeetingID: input.MeetingID,
- Name: input.Name,
- Date: input.Date,
- AttendeeIDs: attendeeIDs,
- Minutes: gqlutils.UnwrapOmittable(input.Minutes),
- },
- )
- if err != nil {
- if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
- return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
- }
- r.logger.ErrorCtx(ctx, "cannot update meeting", log.Error(err))
- return nil, gqlutils.Internal(ctx)
- }
-
- return &types.UpdateMeetingPayload{
- Meeting: types.NewMeeting(meeting),
- }, nil
-}
-
-// DeleteMeeting is the resolver for the deleteMeeting field.
-func (r *mutationResolver) DeleteMeeting(ctx context.Context, input types.DeleteMeetingInput) (*types.DeleteMeetingPayload, error) {
- if err := r.authorize(ctx, input.MeetingID, probo.ActionMeetingDelete); err != nil {
- return nil, err
- }
-
- prb := r.ProboService(ctx, input.MeetingID.TenantID())
-
- err := prb.Meetings.Delete(ctx, input.MeetingID)
- if err != nil {
- r.logger.ErrorCtx(ctx, "cannot delete meeting", log.Error(err))
- return nil, gqlutils.Internal(ctx)
- }
-
- return &types.DeleteMeetingPayload{
- DeletedMeetingID: input.MeetingID,
- }, nil
-}
-
-// Meeting returns schema.MeetingResolver implementation.
-func (r *Resolver) Meeting() schema.MeetingResolver { return &meetingResolver{r} }
-
-// MeetingConnection returns schema.MeetingConnectionResolver implementation.
-func (r *Resolver) MeetingConnection() schema.MeetingConnectionResolver {
- return &meetingConnectionResolver{r}
-}
-
-type meetingResolver struct{ *Resolver }
-type meetingConnectionResolver struct{ *Resolver }
diff --git a/pkg/server/api/console/v1/organization_resolvers.go b/pkg/server/api/console/v1/organization_resolvers.go
index 3eec8969c..5c60849cb 100644
--- a/pkg/server/api/console/v1/organization_resolvers.go
+++ b/pkg/server/api/console/v1/organization_resolvers.go
@@ -769,36 +769,6 @@ func (r *organizationResolver) Measures(ctx context.Context, obj *types.Organiza
return types.NewMeasureConnection(page, r, obj.ID, measureFilter), nil
}
-// Meetings is the resolver for the meetings field.
-func (r *organizationResolver) Meetings(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeetingOrderBy) (*types.MeetingConnection, error) {
- if err := r.authorize(ctx, obj.ID, probo.ActionMeetingList); err != nil {
- return nil, err
- }
-
- prb := r.ProboService(ctx, obj.ID.TenantID())
-
- pageOrderBy := page.OrderBy[coredata.MeetingOrderField]{
- Field: coredata.MeetingOrderFieldCreatedAt,
- Direction: page.OrderDirectionDesc,
- }
- if orderBy != nil {
- pageOrderBy = page.OrderBy[coredata.MeetingOrderField]{
- Field: orderBy.Field,
- Direction: orderBy.Direction,
- }
- }
-
- cursor := types.NewCursor(first, after, last, before, pageOrderBy)
-
- page, err := prb.Meetings.ListForOrganizationID(ctx, obj.ID, cursor)
- if err != nil {
- r.logger.ErrorCtx(ctx, "cannot list organization meetings", log.Error(err))
- return nil, gqlutils.Internal(ctx)
- }
-
- return types.NewMeetingConnection(page, r, obj.ID), nil
-}
-
// Obligations is the resolver for the obligations field.
func (r *organizationResolver) Obligations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ObligationOrderBy, filter *types.ObligationFilter) (*types.ObligationConnection, error) {
if err := r.authorize(ctx, obj.ID, probo.ActionObligationList); err != nil {
diff --git a/pkg/server/api/console/v1/types/meeting.go b/pkg/server/api/console/v1/types/meeting.go
deleted file mode 100644
index 453d0a956..000000000
--- a/pkg/server/api/console/v1/types/meeting.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) 2025-2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-package types
-
-import (
- "go.probo.inc/probo/pkg/coredata"
- "go.probo.inc/probo/pkg/gid"
- "go.probo.inc/probo/pkg/page"
-)
-
-type (
- MeetingOrderBy OrderBy[coredata.MeetingOrderField]
-
- MeetingConnection struct {
- TotalCount int
- Edges []*MeetingEdge
- PageInfo PageInfo
-
- Resolver any
- ParentID gid.GID
- }
-)
-
-func NewMeetingConnection(
- p *page.Page[*coredata.Meeting, coredata.MeetingOrderField],
- parentType any,
- parentID gid.GID,
-) *MeetingConnection {
- var edges = make([]*MeetingEdge, len(p.Data))
-
- for i := range edges {
- edges[i] = NewMeetingEdge(p.Data[i], p.Cursor.OrderBy.Field)
- }
-
- return &MeetingConnection{
- Edges: edges,
- PageInfo: *NewPageInfo(p),
-
- Resolver: parentType,
- ParentID: parentID,
- }
-}
-
-func NewMeetingEdge(meeting *coredata.Meeting, orderBy coredata.MeetingOrderField) *MeetingEdge {
- return &MeetingEdge{
- Cursor: meeting.CursorKey(orderBy),
- Node: NewMeeting(meeting),
- }
-}
-
-func NewMeeting(meeting *coredata.Meeting) *Meeting {
- return &Meeting{
- ID: meeting.ID,
- Name: meeting.Name,
- Organization: &Organization{
- ID: meeting.OrganizationID,
- },
- Date: meeting.Date,
- Minutes: meeting.Minutes,
- CreatedAt: meeting.CreatedAt,
- UpdatedAt: meeting.UpdatedAt,
- }
-}
diff --git a/pkg/server/api/mcp/v1/schema.resolvers.go b/pkg/server/api/mcp/v1/schema.resolvers.go
index 93dd16f02..223f81322 100644
--- a/pkg/server/api/mcp/v1/schema.resolvers.go
+++ b/pkg/server/api/mcp/v1/schema.resolvers.go
@@ -2300,110 +2300,6 @@ func (r *Resolver) CancelSignatureRequestTool(ctx context.Context, req *mcp.Call
}, nil
}
-func (r *Resolver) ListMeetingsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListMeetingsInput) (*mcp.CallToolResult, types.ListMeetingsOutput, error) {
- r.MustAuthorize(ctx, input.OrganizationID, probo.ActionMeetingList)
-
- prb := r.ProboService(ctx, input.OrganizationID)
-
- pageOrderBy := page.OrderBy[coredata.MeetingOrderField]{
- Field: coredata.MeetingOrderFieldCreatedAt,
- Direction: page.OrderDirectionDesc,
- }
- if input.OrderBy != nil {
- pageOrderBy = page.OrderBy[coredata.MeetingOrderField]{
- Field: input.OrderBy.Field,
- Direction: input.OrderBy.Direction,
- }
- }
-
- cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy)
-
- page, err := prb.Meetings.ListForOrganizationID(ctx, input.OrganizationID, cursor)
- if err != nil {
- panic(fmt.Errorf("cannot list organization meetings: %w", err))
- }
-
- return nil, types.NewListMeetingsOutput(page), nil
-}
-
-func (r *Resolver) GetMeetingTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetMeetingInput) (*mcp.CallToolResult, types.GetMeetingOutput, error) {
- r.MustAuthorize(ctx, input.ID, probo.ActionMeetingGet)
-
- prb := r.ProboService(ctx, input.ID)
-
- meeting, err := prb.Meetings.Get(ctx, input.ID)
- if err != nil {
- return nil, types.GetMeetingOutput{}, fmt.Errorf("failed to get meeting: %w", err)
- }
-
- return nil, types.GetMeetingOutput{
- Meeting: types.NewMeeting(meeting),
- }, nil
-}
-
-func (r *Resolver) AddMeetingTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddMeetingInput) (*mcp.CallToolResult, types.AddMeetingOutput, error) {
- r.MustAuthorize(ctx, input.OrganizationID, probo.ActionMeetingCreate)
-
- svc := r.ProboService(ctx, input.OrganizationID)
-
- meeting, err := svc.Meetings.Create(
- ctx,
- probo.CreateMeetingRequest{
- OrganizationID: input.OrganizationID,
- Name: input.Name,
- Date: input.Date,
- AttendeeIDs: input.AttendeeIds,
- Minutes: input.Minutes,
- },
- )
- if err != nil {
- return nil, types.AddMeetingOutput{}, fmt.Errorf("failed to create meeting: %w", err)
- }
-
- return nil, types.AddMeetingOutput{
- Meeting: types.NewMeeting(meeting),
- }, nil
-}
-
-func (r *Resolver) UpdateMeetingTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateMeetingInput) (*mcp.CallToolResult, types.UpdateMeetingOutput, error) {
- r.MustAuthorize(ctx, input.ID, probo.ActionMeetingUpdate)
-
- svc := r.ProboService(ctx, input.ID)
-
- meeting, err := svc.Meetings.Update(
- ctx,
- probo.UpdateMeetingRequest{
- MeetingID: input.ID,
- Name: input.Name,
- Date: input.Date,
- AttendeeIDs: input.AttendeeIds,
- Minutes: UnwrapOmittable(input.Minutes),
- },
- )
- if err != nil {
- return nil, types.UpdateMeetingOutput{}, fmt.Errorf("failed to update meeting: %w", err)
- }
-
- return nil, types.UpdateMeetingOutput{
- Meeting: types.NewMeeting(meeting),
- }, nil
-}
-
-func (r *Resolver) DeleteMeetingTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteMeetingInput) (*mcp.CallToolResult, types.DeleteMeetingOutput, error) {
- r.MustAuthorize(ctx, input.ID, probo.ActionMeetingDelete)
-
- svc := r.ProboService(ctx, input.ID)
-
- err := svc.Meetings.Delete(ctx, input.ID)
- if err != nil {
- return nil, types.DeleteMeetingOutput{}, fmt.Errorf("failed to delete meeting: %w", err)
- }
-
- return nil, types.DeleteMeetingOutput{
- DeletedMeetingID: input.ID,
- }, nil
-}
-
func (r *Resolver) DeleteRiskTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteRiskInput) (*mcp.CallToolResult, types.DeleteRiskOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionRiskDelete)
@@ -2419,26 +2315,6 @@ func (r *Resolver) DeleteRiskTool(ctx context.Context, req *mcp.CallToolRequest,
}, nil
}
-func (r *Resolver) ListMeetingAttendeesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListMeetingAttendeesInput) (*mcp.CallToolResult, types.ListMeetingAttendeesOutput, error) {
- r.MustAuthorize(ctx, input.MeetingID, probo.ActionMeetingGet)
-
- svc := r.ProboService(ctx, input.MeetingID)
-
- attendees, err := svc.Meetings.GetAttendees(ctx, input.MeetingID)
- if err != nil {
- return nil, types.ListMeetingAttendeesOutput{}, fmt.Errorf("failed to list meeting attendees: %w", err)
- }
-
- profiles := make([]*types.Profile, 0, len(attendees))
- for _, a := range attendees {
- profiles = append(profiles, types.NewProfile(a))
- }
-
- return nil, types.ListMeetingAttendeesOutput{
- Attendees: profiles,
- }, nil
-}
-
func (r *Resolver) DeleteMeasureTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteMeasureInput) (*mcp.CallToolResult, types.DeleteMeasureOutput, error) {
r.MustAuthorize(ctx, input.ID, probo.ActionMeasureDelete)
diff --git a/pkg/server/api/mcp/v1/specification.yaml b/pkg/server/api/mcp/v1/specification.yaml
index f947e9e81..f09386a2b 100644
--- a/pkg/server/api/mcp/v1/specification.yaml
+++ b/pkg/server/api/mcp/v1/specification.yaml
@@ -6113,68 +6113,9 @@ components:
type: boolean
description: Whether the notifications were sent successfully
- MeetingOrderField:
- type: string
- enum:
- - CREATED_AT
- - DATE
- - NAME
- go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.MeetingOrderField
-
- MeetingOrderBy:
- type: object
- required:
- - field
- - direction
- properties:
- field:
- $ref: "#/components/schemas/MeetingOrderField"
- description: Meeting order field
- direction:
- $ref: "#/components/schemas/OrderDirection"
- description: Meeting order direction
-
- Meeting:
- type: object
- required:
- - id
- - name
- - date
- - created_at
- - updated_at
- properties:
- id:
- $ref: "#/components/schemas/GID"
- description: Meeting ID
- name:
- type: string
- description: Meeting name
- date:
- type: string
- format: date-time
- description: Meeting date
- minutes:
- anyOf:
- - type: string
- description: Meeting minutes
- - type: "null"
- description: No minutes
- description: Meeting minutes
- created_at:
- type: string
- format: date-time
- description: Creation timestamp
- updated_at:
- type: string
- format: date-time
- description: Update timestamp
-
WebhookEventType:
type: string
enum:
- - "meeting:created"
- - "meeting:updated"
- - "meeting:deleted"
- "vendor:created"
- "vendor:updated"
- "vendor:deleted"
@@ -6447,166 +6388,6 @@ components:
- type: "null"
description: Next page cursor
- ListMeetingsInput:
- type: object
- required:
- - organization_id
- properties:
- organization_id:
- $ref: "#/components/schemas/GID"
- description: Organization ID
- order_by:
- $ref: "#/components/schemas/MeetingOrderBy"
- description: Meeting order by
- size:
- type: integer
- description: Page size
- cursor:
- $ref: "#/components/schemas/CursorKey"
- description: Page cursor
-
- ListMeetingsOutput:
- type: object
- required:
- - meetings
- properties:
- meetings:
- type: array
- items:
- $ref: "#/components/schemas/Meeting"
- description: List of meetings
- next_cursor:
- anyOf:
- - $ref: "#/components/schemas/CursorKey"
- - type: "null"
- description: Next page cursor
-
- GetMeetingInput:
- type: object
- required:
- - id
- properties:
- id:
- $ref: "#/components/schemas/GID"
- description: Meeting ID
-
- GetMeetingOutput:
- type: object
- required:
- - meeting
- properties:
- meeting:
- $ref: "#/components/schemas/Meeting"
-
- AddMeetingInput:
- type: object
- required:
- - organization_id
- - name
- - date
- properties:
- organization_id:
- $ref: "#/components/schemas/GID"
- description: Organization ID
- name:
- type: string
- description: Meeting name
- date:
- type: string
- format: date-time
- description: Meeting date
- attendee_ids:
- type: array
- items:
- $ref: "#/components/schemas/GID"
- description: List of attendee profile IDs
- minutes:
- anyOf:
- - type: string
- description: Meeting minutes
- - type: "null"
- description: No minutes
- description: Meeting minutes
-
- AddMeetingOutput:
- type: object
- required:
- - meeting
- properties:
- meeting:
- $ref: "#/components/schemas/Meeting"
-
- UpdateMeetingInput:
- type: object
- required:
- - id
- properties:
- id:
- $ref: "#/components/schemas/GID"
- description: Meeting ID
- name:
- type: string
- description: Meeting name
- date:
- type: string
- format: date-time
- description: Meeting date
- attendee_ids:
- type: array
- items:
- $ref: "#/components/schemas/GID"
- description: List of attendee profile IDs
- minutes:
- type: ["string", "null"]
- description: Meeting minutes
- go.probo.inc/mcpgen/omittable: true
-
- UpdateMeetingOutput:
- type: object
- required:
- - meeting
- properties:
- meeting:
- $ref: "#/components/schemas/Meeting"
-
- DeleteMeetingInput:
- type: object
- required:
- - id
- properties:
- id:
- $ref: "#/components/schemas/GID"
- description: Meeting ID
-
- DeleteMeetingOutput:
- type: object
- required:
- - deleted_meeting_id
- properties:
- deleted_meeting_id:
- $ref: "#/components/schemas/GID"
- description: Deleted meeting ID
-
- ListMeetingAttendeesInput:
- type: object
- required:
- - meeting_id
- properties:
- meeting_id:
- $ref: "#/components/schemas/GID"
- description: Meeting ID
-
- ListMeetingAttendeesOutput:
- type: object
- required:
- - attendees
- properties:
- attendees:
- type: array
- items:
- $ref: "#/components/schemas/Profile"
- description: List of attendee profiles
-
StatementOfApplicabilityOrderField:
type: string
enum:
@@ -9038,58 +8819,6 @@ tools:
$ref: "#/components/schemas/SendSigningNotificationsInput"
outputSchema:
$ref: "#/components/schemas/SendSigningNotificationsOutput"
- - name: listMeetings
- description: List all meetings for the organization
- hints:
- readonly: true
- idempotent: true
- inputSchema:
- $ref: "#/components/schemas/ListMeetingsInput"
- outputSchema:
- $ref: "#/components/schemas/ListMeetingsOutput"
- - name: getMeeting
- description: Get a meeting by ID
- hints:
- readonly: true
- idempotent: true
- inputSchema:
- $ref: "#/components/schemas/GetMeetingInput"
- outputSchema:
- $ref: "#/components/schemas/GetMeetingOutput"
- - name: addMeeting
- description: Add a new meeting to the organization
- hints:
- readonly: false
- inputSchema:
- $ref: "#/components/schemas/AddMeetingInput"
- outputSchema:
- $ref: "#/components/schemas/AddMeetingOutput"
- - name: updateMeeting
- description: Update an existing meeting
- hints:
- readonly: false
- inputSchema:
- $ref: "#/components/schemas/UpdateMeetingInput"
- outputSchema:
- $ref: "#/components/schemas/UpdateMeetingOutput"
- - name: deleteMeeting
- description: Delete a meeting
- hints:
- readonly: false
- destructive: true
- inputSchema:
- $ref: "#/components/schemas/DeleteMeetingInput"
- outputSchema:
- $ref: "#/components/schemas/DeleteMeetingOutput"
- - name: listMeetingAttendees
- description: List all attendees for a meeting
- hints:
- readonly: true
- idempotent: true
- inputSchema:
- $ref: "#/components/schemas/ListMeetingAttendeesInput"
- outputSchema:
- $ref: "#/components/schemas/ListMeetingAttendeesOutput"
- name: listStatementsOfApplicability
description: List all statements of applicability for the organization
hints:
diff --git a/pkg/server/api/mcp/v1/types/meeting.go b/pkg/server/api/mcp/v1/types/meeting.go
deleted file mode 100644
index 1a23ab10c..000000000
--- a/pkg/server/api/mcp/v1/types/meeting.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-package types
-
-import (
- "go.probo.inc/probo/pkg/coredata"
- "go.probo.inc/probo/pkg/page"
-)
-
-func NewMeeting(m *coredata.Meeting) *Meeting {
- return &Meeting{
- ID: m.ID,
- Name: m.Name,
- Date: m.Date,
- Minutes: m.Minutes,
- CreatedAt: m.CreatedAt,
- UpdatedAt: m.UpdatedAt,
- }
-}
-
-func NewListMeetingsOutput(meetingPage *page.Page[*coredata.Meeting, coredata.MeetingOrderField]) ListMeetingsOutput {
- meetings := make([]*Meeting, 0, len(meetingPage.Data))
- for _, v := range meetingPage.Data {
- meetings = append(meetings, NewMeeting(v))
- }
-
- var nextCursor *page.CursorKey
- if len(meetingPage.Data) > 0 {
- cursorKey := meetingPage.Data[len(meetingPage.Data)-1].CursorKey(meetingPage.Cursor.OrderBy.Field)
- nextCursor = &cursorKey
- }
-
- return ListMeetingsOutput{
- NextCursor: nextCursor,
- Meetings: meetings,
- }
-}
diff --git a/pkg/webhook/types/meeting.go b/pkg/webhook/types/meeting.go
deleted file mode 100644
index 7630622c0..000000000
--- a/pkg/webhook/types/meeting.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2026 Probo Inc .
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
-// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-package types
-
-import (
- "time"
-
- "go.probo.inc/probo/pkg/coredata"
- "go.probo.inc/probo/pkg/gid"
-)
-
-type Meeting struct {
- ID gid.GID `json:"id"`
- Name string `json:"name"`
- Date time.Time `json:"date"`
- Minutes *string `json:"minutes"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
-}
-
-func NewMeeting(m *coredata.Meeting) *Meeting {
- return &Meeting{
- ID: m.ID,
- Name: m.Name,
- Date: m.Date,
- Minutes: m.Minutes,
- CreatedAt: m.CreatedAt,
- UpdatedAt: m.UpdatedAt,
- }
-}