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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion lib/api/model/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ sealed class Event {
switch (json['op'] as String) {
case 'add': return UserGroupAddEvent.fromJson(json);
case 'update': return UserGroupUpdateEvent.fromJson(json);
// TODO(#1687): add_members, remove_members, add_subgroups, remove_subgroups
case 'add_members': return UserGroupAddMembersEvent.fromJson(json);
case 'remove_members': return UserGroupRemoveMembersEvent.fromJson(json);
case 'add_subgroups': return UserGroupAddSubgroupsEvent.fromJson(json);
case 'remove_subgroups': return UserGroupRemoveSubgroupsEvent.fromJson(json);
case 'remove': return UserGroupRemoveEvent.fromJson(json);
default: return UnexpectedEvent.fromJson(json);
}
Expand Down Expand Up @@ -280,6 +283,78 @@ class UserGroupUpdateData {
Map<String, dynamic> toJson() => _$UserGroupUpdateDataToJson(this);
}

/// A [UserGroupEvent] with op `add_members`: https://zulip.com/api/get-events#user_group-add_members
@JsonSerializable(fieldRename: FieldRename.snake)
class UserGroupAddMembersEvent extends UserGroupEvent {
@override
@JsonKey(includeToJson: true)
String get op => 'add_members';

final int groupId;
final List<int> userIds;

UserGroupAddMembersEvent({required super.id, required this.groupId, required this.userIds});

factory UserGroupAddMembersEvent.fromJson(Map<String, dynamic> json) => _$UserGroupAddMembersEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$UserGroupAddMembersEventToJson(this);
}

/// A [UserGroupEvent] with op `remove_members`: https://zulip.com/api/get-events#user_group-remove_members
@JsonSerializable(fieldRename: FieldRename.snake)
class UserGroupRemoveMembersEvent extends UserGroupEvent {
@override
@JsonKey(includeToJson: true)
String get op => 'remove_members';

final int groupId;
final List<int> userIds;

UserGroupRemoveMembersEvent({required super.id, required this.groupId, required this.userIds});

factory UserGroupRemoveMembersEvent.fromJson(Map<String, dynamic> json) => _$UserGroupRemoveMembersEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$UserGroupRemoveMembersEventToJson(this);
}

/// A [UserGroupEvent] with op `add_subgroups`: https://zulip.com/api/get-events#user_group-add_subgroups
@JsonSerializable(fieldRename: FieldRename.snake)
class UserGroupAddSubgroupsEvent extends UserGroupEvent {
@override
@JsonKey(includeToJson: true)
String get op => 'add_subgroups';

final int groupId;
final List<int> directSubgroupIds;

UserGroupAddSubgroupsEvent({required super.id, required this.groupId, required this.directSubgroupIds});

factory UserGroupAddSubgroupsEvent.fromJson(Map<String, dynamic> json) => _$UserGroupAddSubgroupsEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$UserGroupAddSubgroupsEventToJson(this);
}

/// A [UserGroupEvent] with op `remove_subgroups`: https://zulip.com/api/get-events#user_group-remove_subgroups
@JsonSerializable(fieldRename: FieldRename.snake)
class UserGroupRemoveSubgroupsEvent extends UserGroupEvent {
@override
@JsonKey(includeToJson: true)
String get op => 'remove_subgroups';

final int groupId;
final List<int> directSubgroupIds;

UserGroupRemoveSubgroupsEvent({required super.id, required this.groupId, required this.directSubgroupIds});

factory UserGroupRemoveSubgroupsEvent.fromJson(Map<String, dynamic> json) => _$UserGroupRemoveSubgroupsEventFromJson(json);

@override
Map<String, dynamic> toJson() => _$UserGroupRemoveSubgroupsEventToJson(this);
}

/// A [UserGroupEvent] with op `remove`: https://zulip.com/api/get-events#user_group-remove
@JsonSerializable(fieldRename: FieldRename.snake)
class UserGroupRemoveEvent extends UserGroupEvent {
Expand Down Expand Up @@ -599,6 +674,9 @@ class ChannelUpdateEvent extends ChannelEvent {
return value as int?;
case ChannelPropertyName.channelPostPolicy:
return ChannelPostPolicy.fromApiValue(value as int);
case ChannelPropertyName.canAddSubscribersGroup:
case ChannelPropertyName.canSubscribeGroup:
return GroupSettingValue.fromJson(value);
case ChannelPropertyName.streamWeeklyTraffic:
return value as int?;
case null:
Expand Down
82 changes: 82 additions & 0 deletions lib/api/model/events.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 114 additions & 0 deletions lib/api/model/initial_snapshot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ class InitialSnapshot {
@JsonKey(readValue: _readUsersIsActiveFallbackTrue)
final List<User> crossRealmBots;

// TODO(server): Get this API stabilized, to replace [SupportedPermissionSettings.fixture].
// final SupportedPermissionSettings? serverSupportedPermissionSettings;

// TODO etc., etc.
// If adding fields, keep them all in the order they appear in the API docs.

Expand Down Expand Up @@ -389,3 +392,114 @@ class UnreadHuddleSnapshot {

Map<String, dynamic> toJson() => _$UnreadHuddleSnapshotToJson(this);
}

/// Metadata about how to interpret the various group-based permission settings.
///
/// This is the type that [InitialSnapshot.serverSupportedPermissionSettings]
/// would have, according to the API as it exists as of 2025-08;
/// but that API is documented as unstable and subject to change.
///
/// For a useful value of this type, see [SupportedPermissionSettings.fixture].
///
/// For docs, search for "d_perm" in: https://zulip.com/api/register-queue
@JsonSerializable(fieldRename: FieldRename.snake)
class SupportedPermissionSettings {
final Map<String, PermissionSettingsItem> realm;
final Map<String, PermissionSettingsItem> stream;
final Map<String, PermissionSettingsItem> group;

/// Metadata about how to interpret certain group-based permission settings,
/// including all those that this client uses, based on "current" servers.
///
/// "Current" here means as of when this code was written, or last updated;
/// details in comments below. Naturally it'd be better to have an API to
/// get this information from the actual server.
///
/// Effectively we're counting on it being uncommon for the metadata for a
/// given permission to ever change from one server version to the next,
/// so that the values we take from one server version usually remain valid
/// for all past and future server versions that have the corresponding
/// permission at all.
///
/// TODO(server): Stabilize [InitialSnapshot.serverSupportedPermissionSettings]
/// or a similar API, and switch to using that. See thread:
/// https://chat.zulip.org/#narrow/channel/378-api-design/topic/server_supported_permission_settings/near/2247549
static SupportedPermissionSettings fixture = SupportedPermissionSettings(
realm: {}, // Please go ahead and fill this in when we come to need it.
group: {}, // Please go ahead and fill this in when we come to need it.
stream: {
// From the server's Stream.stream_permission_group_settings,
// in zerver/models/streams.py. Current as of f9dc13014, 2025-08.
"can_add_subscribers_group": PermissionSettingsItem(
// allow_nobody_group=True,
allowEveryoneGroup: false,
// default_group_name=SystemGroups.NOBODY,
),
"can_administer_channel_group": PermissionSettingsItem(
// allow_nobody_group=True,
allowEveryoneGroup: false,
// default_group_name="stream_creator_or_nobody",
),
"can_delete_any_message_group": PermissionSettingsItem(
// allow_nobody_group=True,
allowEveryoneGroup: true,
// default_group_name=SystemGroups.NOBODY,
),
"can_delete_own_message_group": PermissionSettingsItem(
// allow_nobody_group=True,
allowEveryoneGroup: true,
// default_group_name=SystemGroups.NOBODY,
),
"can_move_messages_out_of_channel_group": PermissionSettingsItem(
// allow_nobody_group=True,
allowEveryoneGroup: true,
// default_group_name=SystemGroups.NOBODY,
),
"can_move_messages_within_channel_group": PermissionSettingsItem(
// allow_nobody_group=True,
allowEveryoneGroup: true,
// default_group_name=SystemGroups.NOBODY,
),
"can_remove_subscribers_group": PermissionSettingsItem(
// allow_nobody_group=True,
allowEveryoneGroup: true,
// default_group_name=SystemGroups.ADMINISTRATORS,
),
"can_send_message_group": PermissionSettingsItem(
// allow_nobody_group=True,
allowEveryoneGroup: true,
// default_group_name=SystemGroups.EVERYONE,
),
"can_subscribe_group": PermissionSettingsItem(
// allow_nobody_group=True,
allowEveryoneGroup: false,
// default_group_name=SystemGroups.NOBODY,
),
"can_resolve_topics_group": PermissionSettingsItem(
// allow_nobody_group=True,
allowEveryoneGroup: true,
// default_group_name=SystemGroups.NOBODY,
),
},
);

SupportedPermissionSettings({required this.realm, required this.stream, required this.group});

factory SupportedPermissionSettings.fromJson(Map<String, dynamic> json) =>
_$SupportedPermissionSettingsFromJson(json);

Map<String, dynamic> toJson() => _$SupportedPermissionSettingsToJson(this);
}

@JsonSerializable(fieldRename: FieldRename.snake)
class PermissionSettingsItem {
final bool allowEveryoneGroup;
// also other fields not yet used

PermissionSettingsItem({required this.allowEveryoneGroup});

factory PermissionSettingsItem.fromJson(Map<String, dynamic> json) =>
_$PermissionSettingsItemFromJson(json);

Map<String, dynamic> toJson() => _$PermissionSettingsItemToJson(this);
}
35 changes: 35 additions & 0 deletions lib/api/model/initial_snapshot.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading