mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 00:16:59 +00:00
feat(concord): add mobile foundation enums (membership + view mode)
First slice of the Android integration, mirroring NIP-29's commons model: - ConcordMembership (OWNER/ADMIN/MEMBER/BANNED/NONE) derived from the folded owner-rooted AuthorityResolver + banlist — membership is key possession, roles layer moderation power, the banlist removes standing; with isMember/canModerate - ConcordViewMode (INLINE/GROUPED) for how joined channels surface in Messages Test classifies owner/admin/member/banned/stranger from a folded control plane. Green on :commons:jvmTest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CzJ2Cwo8tg4oZq43oRa3ig
This commit is contained in:
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.model.concord
|
||||
|
||||
import com.vitorpamplona.quartz.concord.cord04Roles.AuthorityResolver
|
||||
import com.vitorpamplona.quartz.concord.cord04Roles.ConcordPermissions
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
/**
|
||||
* A user's standing in a Concord community, derived from the locally-folded
|
||||
* Control Plane (the owner-rooted [AuthorityResolver] + banlist).
|
||||
*
|
||||
* Unlike NIP-29 (where the relay's signed roster is the truth), Concord
|
||||
* membership is **key possession**: anyone holding the community key is at least a
|
||||
* [MEMBER]. Roles layer moderation power on top ([ADMIN]/[OWNER]); the banlist
|
||||
* removes standing ([BANNED]). [NONE] is only for a user we can't place at all.
|
||||
*/
|
||||
enum class ConcordMembership {
|
||||
/** The community founder — supreme, unremovable. */
|
||||
OWNER,
|
||||
|
||||
/** Holds at least one moderation permission (manage roles/channels, kick, ban…). */
|
||||
ADMIN,
|
||||
|
||||
/** Holds the community key, no elevated role. */
|
||||
MEMBER,
|
||||
|
||||
/** On the community banlist — dropped everywhere. */
|
||||
BANNED,
|
||||
|
||||
/** Not placeable in this community. */
|
||||
NONE,
|
||||
;
|
||||
|
||||
/** True when the user is an active participant (holds the key and isn't banned). */
|
||||
fun isMember(): Boolean = this == OWNER || this == ADMIN || this == MEMBER
|
||||
|
||||
/** True when the user may take moderation actions. */
|
||||
fun canModerate(): Boolean = this == OWNER || this == ADMIN
|
||||
|
||||
companion object {
|
||||
private val MOD_BITS =
|
||||
intArrayOf(
|
||||
ConcordPermissions.MANAGE_ROLES,
|
||||
ConcordPermissions.MANAGE_CHANNELS,
|
||||
ConcordPermissions.MANAGE_METADATA,
|
||||
ConcordPermissions.KICK,
|
||||
ConcordPermissions.BAN,
|
||||
ConcordPermissions.MANAGE_MESSAGES,
|
||||
)
|
||||
|
||||
/**
|
||||
* Classifies [pubKey] against a folded [authority]. [holdsKey] tells us the
|
||||
* user is a member of this community locally (we joined it / hold its key),
|
||||
* which distinguishes a plain [MEMBER] from [NONE].
|
||||
*/
|
||||
fun of(
|
||||
authority: AuthorityResolver,
|
||||
pubKey: HexKey,
|
||||
holdsKey: Boolean = true,
|
||||
): ConcordMembership {
|
||||
if (authority.isBanned(pubKey)) return BANNED
|
||||
if (authority.isOwner(pubKey)) return OWNER
|
||||
val perms = authority.effectivePermissions(pubKey)
|
||||
if (MOD_BITS.any { perms.has(it) }) return ADMIN
|
||||
return if (holdsKey) MEMBER else NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.model.concord
|
||||
|
||||
/**
|
||||
* How joined Concord Channels surface in the Messages inbox (mirrors
|
||||
* `RelayGroupViewMode`).
|
||||
*/
|
||||
enum class ConcordViewMode {
|
||||
/** One inbox row per channel across all joined communities. */
|
||||
INLINE,
|
||||
|
||||
/** One inbox row per community, collapsing its channels behind a drill-down. */
|
||||
GROUPED,
|
||||
;
|
||||
|
||||
companion object {
|
||||
val DEFAULT = INLINE
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.model.concord
|
||||
|
||||
import com.vitorpamplona.quartz.concord.cord04Roles.AuthorityResolver
|
||||
import com.vitorpamplona.quartz.concord.cord04Roles.ControlEdition
|
||||
import com.vitorpamplona.quartz.concord.cord04Roles.ControlEntityKind
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ConcordMembershipTest {
|
||||
private val owner = "0f".repeat(32)
|
||||
private val admin = "a1".repeat(32)
|
||||
private val member = "b2".repeat(32)
|
||||
private val banned = "c3".repeat(32)
|
||||
private val stranger = "d4".repeat(32)
|
||||
private val adminRole = "11".repeat(32)
|
||||
|
||||
private fun ed(
|
||||
kind: ControlEntityKind,
|
||||
eid: String,
|
||||
content: String,
|
||||
author: String = owner,
|
||||
) = ControlEdition(kind, eid.hexToByteArray(), 0, null, null, content, author, "r-$eid", 0)
|
||||
|
||||
private val authority =
|
||||
AuthorityResolver.resolve(
|
||||
listOf(
|
||||
ed(ControlEntityKind.ROLE, adminRole, """{"name":"Admin","position":1,"permissions":"25"}"""), // KICK|BAN|MANAGE_ROLES
|
||||
ed(ControlEntityKind.GRANT, "ab".repeat(32), """{"member":"$admin","role_ids":["$adminRole"]}"""),
|
||||
ed(ControlEntityKind.BANLIST, "44".repeat(32), """["$banned"]"""),
|
||||
),
|
||||
owner,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun classifiesStandingFromAuthority() {
|
||||
assertEquals(ConcordMembership.OWNER, ConcordMembership.of(authority, owner))
|
||||
assertEquals(ConcordMembership.ADMIN, ConcordMembership.of(authority, admin))
|
||||
assertEquals(ConcordMembership.MEMBER, ConcordMembership.of(authority, member))
|
||||
assertEquals(ConcordMembership.BANNED, ConcordMembership.of(authority, banned))
|
||||
// A user we don't hold the key for reads as NONE.
|
||||
assertEquals(ConcordMembership.NONE, ConcordMembership.of(authority, stranger, holdsKey = false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun capabilityHelpers() {
|
||||
assertEquals(true, ConcordMembership.OWNER.canModerate())
|
||||
assertEquals(true, ConcordMembership.ADMIN.canModerate())
|
||||
assertEquals(false, ConcordMembership.MEMBER.canModerate())
|
||||
assertEquals(true, ConcordMembership.MEMBER.isMember())
|
||||
assertEquals(false, ConcordMembership.BANNED.isMember())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user