Skip to content

Commit ff9a9f2

Browse files
committed
WPB-21964: Add Wire Meetings endpoints
1 parent e395db7 commit ff9a9f2

43 files changed

Lines changed: 2649 additions & 34 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

changelog.d/2-features/WPB-21964

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add payingTeam feature flag to distinguish paying teams from trial teams. Meetings created by paying team members are marked as non-trial. Public endpoints: GET/PUT /teams/:tid/features/payingTeam. Internal endpoints: GET/PUT/PATCH /i/teams/:tid/features/payingTeam and lock status management.

charts/background-worker/values.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ config:
8181
# Total attempts, including the first try
8282
maxAttempts: 3
8383

84+
# Meetings cleanup configuration
85+
meetingsCleanup:
86+
# Delete meetings older than this many hours (48 hours = 2 days)
87+
cleanOlderThanHours: 48
88+
# Maximum number of meetings to delete per batch
89+
batchSize: 1000
90+
# Frequency in seconds to run the cleanup job (3600 = 1 hour)
91+
cleanFrequencySeconds: 3600
92+
8493
# Controls where conversation data is stored/accessed
8594
postgresMigration:
8695
conversation: postgresql

integration/integration.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ library
157157
Test.FeatureFlags.MlsE2EId
158158
Test.FeatureFlags.MlsMigration
159159
Test.FeatureFlags.OutlookCalIntegration
160+
Test.FeatureFlags.PayingTeam
160161
Test.FeatureFlags.SearchVisibilityAvailable
161162
Test.FeatureFlags.SearchVisibilityInbound
162163
Test.FeatureFlags.SelfDeletingMessages
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-- This file is part of the Wire Server implementation.
2+
--
3+
-- Copyright (C) 2025 Wire Swiss GmbH <opensource@wire.com>
4+
--
5+
-- This program is free software: you can redistribute it and/or modify it under
6+
-- the terms of the GNU Affero General Public License as published by the Free
7+
-- Software Foundation, either version 3 of the License, or (at your option) any
8+
-- later version.
9+
--
10+
-- This program is distributed in the hope that it will be useful, but WITHOUT
11+
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12+
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13+
-- details.
14+
--
15+
-- You should have received a copy of the GNU Affero General Public License along
16+
-- with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
module Test.FeatureFlags.PayingTeam where
19+
20+
import Test.FeatureFlags.Util
21+
import Testlib.Prelude
22+
23+
testPatchPayingTeam :: (HasCallStack) => App ()
24+
testPatchPayingTeam = checkPatch OwnDomain "payingTeam" disabled
25+
26+
testPayingTeam :: (HasCallStack) => APIAccess -> App ()
27+
testPayingTeam access =
28+
mkFeatureTests "payingTeam"
29+
& addUpdate disabled
30+
& addUpdate enabled
31+
& runFeatureTests OwnDomain access

integration/test/Test/FeatureFlags/Util.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ defAllFeatures =
177177
"collabora" .= object ["edition" .= "COOL"],
178178
"storage" .= object ["teamQuotaBytes" .= "1000000000000"]
179179
]
180-
]
180+
],
181+
"payingTeam" .= disabled
181182
]
182183

183184
hasExplicitLockStatus :: String -> Bool

libs/galley-types/src/Galley/Types/Teams.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,13 @@ newtype instance FeatureDefaults StealthUsersConfig
330330
deriving (FromJSON) via Defaults (LockableFeature StealthUsersConfig)
331331
deriving (ParseFeatureDefaults) via OptionalField StealthUsersConfig
332332

333+
newtype instance FeatureDefaults PayingTeamConfig
334+
= PayingTeamDefaults (LockableFeature PayingTeamConfig)
335+
deriving stock (Eq, Show)
336+
deriving newtype (Default, GetFeatureDefaults)
337+
deriving (FromJSON) via Defaults (LockableFeature PayingTeamConfig)
338+
deriving (ParseFeatureDefaults) via OptionalField PayingTeamConfig
339+
333340
featureKey :: forall cfg. (IsFeatureConfig cfg) => Key.Key
334341
featureKey = Key.fromText $ featureName @cfg
335342

libs/wire-api/src/Wire/API/Conversation.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,7 @@ instance PostgresMarshall ReceiptMode Int32 where
840840
--------------------------------------------------------------------------------
841841
-- create
842842

843-
data GroupConvType = GroupConversation | Channel
843+
data GroupConvType = GroupConversation | Channel | MeetingConversation
844844
deriving stock (Eq, Show, Generic, Enum)
845845
deriving (Arbitrary) via (GenericUniform GroupConvType)
846846
deriving (FromJSON, ToJSON, S.ToSchema) via Schema GroupConvType
@@ -850,7 +850,8 @@ instance ToSchema GroupConvType where
850850
enum @Text "GroupConvType" $
851851
mconcat
852852
[ element "group_conversation" GroupConversation,
853-
element "channel" Channel
853+
element "channel" Channel,
854+
element "meeting" MeetingConversation
854855
]
855856

856857
instance C.Cql GroupConvType where

libs/wire-api/src/Wire/API/Error/Galley.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ data GalleyError
177177
| NotAnMlsConversation
178178
| MLSReadReceiptsNotAllowed
179179
| MLSInvalidLeafNodeSignature
180+
| -- Meeting errors
181+
MeetingNotFound
180182
deriving (Show, Eq, Generic)
181183
deriving (FromJSON, ToJSON) via (CustomEncoded GalleyError)
182184

@@ -375,6 +377,11 @@ type instance MapError 'MLSReadReceiptsNotAllowed = 'StaticError 403 "mls-receip
375377

376378
type instance MapError 'MLSInvalidLeafNodeSignature = 'StaticError 400 "mls-invalid-leaf-node-signature" "Invalid leaf node signature"
377379

380+
--------------------------------------------------------------------------------
381+
-- Meeting errors
382+
383+
type instance MapError 'MeetingNotFound = 'StaticError 404 "meeting-not-found" "Meeting not found"
384+
378385
--------------------------------------------------------------------------------
379386
-- Team Member errors
380387

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
-- This file is part of the Wire Server implementation.
2+
--
3+
-- Copyright (C) 2025 Wire Swiss GmbH <opensource@wire.com>
4+
--
5+
-- This program is free software: you can redistribute it and/or modify it under
6+
-- the terms of the GNU Affero General Public License as published by the Free
7+
-- Software Foundation, either version 3 of the License, or (at your option) any
8+
-- later version.
9+
--
10+
-- This program is distributed in the hope that it will be useful, but WITHOUT
11+
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12+
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13+
-- details.
14+
--
15+
-- You should have received a copy of the GNU Affero General Public License along
16+
-- with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
module Wire.API.Meeting where
19+
20+
import Control.Lens ((?~))
21+
import Data.Aeson ()
22+
import Data.Id (ConvId, UserId, uuidSchema)
23+
import Data.Json.Util (utcTimeSchema)
24+
import Data.OpenApi qualified as S
25+
import Data.Qualified (Qualified, qualifiedSchema)
26+
import Data.Schema
27+
import Data.Time.Clock
28+
import Data.UUID (UUID)
29+
import Deriving.Aeson
30+
import Imports
31+
import Servant (FromHttpApiData, ToHttpApiData)
32+
import Wire.API.User.Identity (EmailAddress)
33+
import Wire.Arbitrary (Arbitrary, GenericUniform (..))
34+
35+
-- | Unique identifier for a meeting
36+
newtype MeetingId = MeetingId {unMeetingId :: UUID}
37+
deriving stock (Eq, Ord, Show, Generic)
38+
deriving newtype (ToJSON, FromJSON, FromHttpApiData, ToHttpApiData, S.ToSchema, S.ToParamSchema)
39+
deriving (Arbitrary) via (GenericUniform MeetingId)
40+
41+
instance ToSchema MeetingId where
42+
schema = MeetingId <$> unMeetingId .= uuidSchema
43+
44+
instance ToSchema (Qualified MeetingId) where
45+
schema = qualifiedSchema "MeetingId" "id" schema
46+
47+
-- | Core Meeting type
48+
data Meeting = Meeting
49+
{ id :: Qualified MeetingId,
50+
title :: Text,
51+
creator :: Qualified UserId,
52+
startDate :: UTCTime,
53+
endDate :: UTCTime,
54+
recurrence :: Maybe Recurrence,
55+
conversationId :: Qualified ConvId,
56+
invitedEmails :: [EmailAddress],
57+
trial :: Bool
58+
}
59+
deriving stock (Eq, Show, Generic)
60+
deriving (ToJSON, FromJSON, S.ToSchema) via (Schema Meeting)
61+
deriving (Arbitrary) via (GenericUniform Meeting)
62+
63+
instance ToSchema Meeting where
64+
schema =
65+
objectWithDocModifier "Meeting" (description ?~ "A scheduled meeting") $
66+
Meeting
67+
<$> (.id) .= field "qualified_id" schema
68+
<*> (.title) .= field "title" schema
69+
<*> (.creator) .= field "qualified_creator" schema
70+
<*> (.startDate) .= field "start_date" utcTimeSchema
71+
<*> (.endDate) .= field "end_date" utcTimeSchema
72+
<*> (.recurrence) .= maybe_ (optField "recurrence" schema)
73+
<*> (.conversationId) .= field "qualified_conversation" schema
74+
<*> (.invitedEmails) .= field "invited_emails" (array schema)
75+
<*> (.trial) .= field "trial" schema
76+
77+
-- | Request to create a new meeting
78+
data NewMeeting = NewMeeting
79+
{ startDate :: UTCTime,
80+
endDate :: UTCTime,
81+
recurrence :: Maybe Recurrence,
82+
title :: Text,
83+
invitedEmails :: [EmailAddress]
84+
}
85+
deriving stock (Eq, Show, Generic)
86+
deriving (ToJSON, FromJSON, S.ToSchema) via (Schema NewMeeting)
87+
deriving (Arbitrary) via (GenericUniform NewMeeting)
88+
89+
data Recurrence = Recurrence
90+
{ freq :: Frequency,
91+
interval :: Maybe Int,
92+
until :: Maybe UTCTime
93+
}
94+
deriving stock (Eq, Show, Generic)
95+
deriving (ToJSON, FromJSON, S.ToSchema) via (Schema Recurrence)
96+
deriving (Arbitrary) via (GenericUniform Recurrence)
97+
98+
data Frequency = Daily | Weekly | Monthly | Yearly
99+
deriving stock (Eq, Show, Generic)
100+
deriving (ToJSON, FromJSON, S.ToSchema) via (Schema Frequency)
101+
deriving (Arbitrary) via (GenericUniform Frequency)
102+
103+
instance ToSchema NewMeeting where
104+
schema =
105+
objectWithDocModifier "NewMeeting" (description ?~ "Request to create a new meeting") $
106+
NewMeeting
107+
<$> (.startDate) .= field "start_date" utcTimeSchema
108+
<*> (.endDate) .= field "end_date" utcTimeSchema
109+
<*> (.recurrence) .= maybe_ (optField "recurrence" schema)
110+
<*> (.title) .= field "title" schema
111+
<*> (.invitedEmails) .= (fromMaybe [] <$> optField "invited_emails" (array schema))
112+
113+
-- | Request to update an existing meeting
114+
data UpdateMeeting = UpdateMeeting
115+
{ startDate :: Maybe UTCTime,
116+
endDate :: Maybe UTCTime,
117+
title :: Maybe Text,
118+
recurrence :: Maybe Recurrence
119+
}
120+
deriving stock (Eq, Show, Generic)
121+
deriving (ToJSON, FromJSON, S.ToSchema) via (Schema UpdateMeeting)
122+
deriving (Arbitrary) via (GenericUniform UpdateMeeting)
123+
124+
instance ToSchema UpdateMeeting where
125+
schema =
126+
objectWithDocModifier "UpdateMeeting" (description ?~ "Request to update a meeting") $
127+
UpdateMeeting
128+
<$> (.startDate) .= maybe_ (optField "start_date" utcTimeSchema)
129+
<*> (.endDate) .= maybe_ (optField "end_date" utcTimeSchema)
130+
<*> (.title) .= maybe_ (optField "title" schema)
131+
<*> (.recurrence) .= maybe_ (optField "recurrence" schema)
132+
133+
instance ToSchema Frequency where
134+
schema =
135+
enum @Text "Frequency" $
136+
mconcat
137+
[ element "Daily" Daily,
138+
element "Weekly" Weekly,
139+
element "Monthly" Monthly,
140+
element "Yearly" Yearly
141+
]
142+
143+
instance ToSchema Recurrence where
144+
schema =
145+
objectWithDocModifier "Recurrence" (description ?~ "Recurrence pattern for meetings") $
146+
Recurrence
147+
<$> (.freq) .= field "frequency" schema
148+
<*> (.interval) .= maybe_ (optField "interval" schema)
149+
<*> (.until) .= maybe_ (optField "until" utcTimeSchema)
150+
151+
-- | Request to add/remove invited email
152+
newtype MeetingEmailsInvitation = MeetingEmailsInvitation
153+
{ emails :: [EmailAddress]
154+
}
155+
deriving stock (Eq, Show, Generic)
156+
deriving (ToJSON, FromJSON, S.ToSchema) via (Schema MeetingEmailsInvitation)
157+
deriving (Arbitrary) via (GenericUniform MeetingEmailsInvitation)
158+
159+
instance ToSchema MeetingEmailsInvitation where
160+
schema =
161+
objectWithDocModifier "MeetingEmailsInvitation" (description ?~ "Emails invitation") $
162+
MeetingEmailsInvitation
163+
<$> (.emails) .= field "emails" (array schema)

libs/wire-api/src/Wire/API/Routes/Internal/Galley.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ type IFeatureAPI =
9494
:<|> IFeatureStatusLockStatusPut AppsConfig
9595
:<|> IFeatureStatusLockStatusPut SimplifiedUserConnectionRequestQRCodeConfig
9696
:<|> IFeatureStatusLockStatusPut StealthUsersConfig
97+
:<|> IFeatureStatusLockStatusPut PayingTeamConfig
9798
-- all feature configs
9899
:<|> Named
99100
"feature-configs-internal"

0 commit comments

Comments
 (0)