This commit is contained in:
j3d1 2026-08-26 20:40:23 +02:00
parent 35be834799
commit 8de6ae8d8b
11 changed files with 168 additions and 150 deletions

View file

@ -8,6 +8,11 @@ import {serializeWorkflowPayload, deserializeWorkflowPayload} from "@/workflows.
//import sharedStatePlugin from "@/../extras/shared-state-plugin";
//import persistentStatePlugin from "@/../extras/persistent-state-plugin";
function splitGroupHandle(handle) {
const [name, domain] = handle.slice(1).split('@')
return {name, domain}
}
const defaultPreferenceDefinitions = [
{
key: 'ui.compact_mode',
@ -398,14 +403,22 @@ export default createStore({
state.last_load.files = 0
return reply
},
// A group-owned item lives on its owning group's own backend, not necessarily the
// caller's home one (see the group actions below) - item.owner_group ("#name@domain")
// already carries that domain, so update/delete resolve it the same way rather than
// needing the caller to track it separately.
async updateInventoryItem({state, dispatch, getters}, item) {
const servers = await dispatch('getHomeServers')
const servers = item.owner_group
? await dispatch('getFriendServers', {username: 'x@' + splitGroupHandle(item.owner_group).domain})
: await dispatch('getHomeServers')
const data = {availability_policy: 'friends', ...item}
data.files = data.files.map(file => file.id)
return await servers.patch(getters.signAuth, '/api/inventory_items/' + item.id + '/', data)
},
async deleteInventoryItem({state, dispatch, getters}, item) {
const servers = await dispatch('getHomeServers')
const servers = item.owner_group
? await dispatch('getFriendServers', {username: 'x@' + splitGroupHandle(item.owner_group).domain})
: await dispatch('getHomeServers')
const ret = await servers.delete(getters.signAuth, '/api/inventory_items/' + item.id + '/')
dispatch('fetchInventoryItems')
return ret
@ -452,19 +465,21 @@ export default createStore({
return null;
}
},
// Group handles have no owner-handle GET route yet, so they resolve differently than
// personal handles here. See docs/implementation.md#fetch-item-by-handle-group-vs-personal-handles.
// A group handle resolves via fetchGroup (itself domain-routed, see below), so a group
// hosted elsewhere works exactly like one hosted on the caller's own backend - no idmap
// detour needed here. ServerSet.get() never checks response.ok, so a 404 (nonexistent or
// non-member group) surfaces as a JSON-parse rejection rather than resolving falsy - same
// try/catch shape as fetchForeignItem.
async fetchItemByHandle({dispatch, getters}, {handle, id}) {
if (handle.startsWith('#')) {
// idmap isn't guaranteed loaded on a direct/refreshed visit here; fetchIdMap is
// cheap and already called unconditionally by every other caller too.
await dispatch('fetchIdMap')
const groupId = getters.groupIdByHandle[handle]
if (groupId === undefined) {
return null
try {
const group = await dispatch('fetchGroup', {handle})
const items = await dispatch('fetchGroupInventoryItems', {groupId: group.id, domain: group.domain})
return items.find(item => item.id === parseInt(id)) || null
} catch (error) {
console.error(`Failed to fetch group item ${id} for ${handle}:`, error);
return null;
}
const items = await dispatch('fetchGroupInventoryItems', groupId)
return items.find(item => item.id === parseInt(id)) || null
}
return dispatch('fetchForeignItem', {owner: handle, id})
},
@ -515,9 +530,14 @@ export default createStore({
const servers = await dispatch('getHomeServers')
return await servers.delete(getters.signAuth, '/api/friends/' + id + '/')
},
// Groups are only ever hosted on the current user's own home backend for now (see
// docs/design-in-progress/groups-mvp.md), so every group action below uses getHomeServers
// rather than resolving a per-group domain.
// Listing/creating a group is inherently home-scoped - it's this backend's own view of
// "groups I host you in" - but every action below that acts on an *existing* group
// (found via its handle, e.g. from a GroupMembership pointer to a group hosted
// elsewhere - see GroupMembership/docs/design-in-progress/groups-mvp.md) resolves that
// group's own domain and talks to it directly, the same way requestFriend/
// fetchForeignItem resolve a friend's domain, rather than assuming home. A group hosted
// on the caller's own domain goes through exactly the same code path - there's nothing
// to special-case.
async fetchGroups({commit, dispatch, getters}) {
const servers = await dispatch('getHomeServers')
const data = await servers.get(getters.signAuth, '/api/groups/')
@ -530,16 +550,18 @@ export default createStore({
commit('setIdMap', idmap)
return idmap
},
async fetchGroup({dispatch, getters}, {id}) {
const servers = await dispatch('getHomeServers')
return await servers.get(getters.signAuth, '/api/groups/' + id + '/')
// handle is "#name@domain".
async fetchGroup({dispatch, getters}, {handle}) {
const {name, domain} = splitGroupHandle(handle)
const servers = await dispatch('getFriendServers', {username: 'x@' + domain})
return await servers.get(getters.signAuth, `/api/groups/handle/${name}/${domain}/`)
},
async createGroup({dispatch, getters}, {name}) {
const servers = await dispatch('getHomeServers')
return await servers.post(getters.signAuth, '/api/groups/', {name})
},
async removeGroupMember({dispatch, getters}, {groupId, identityId}) {
const servers = await dispatch('getHomeServers')
async removeGroupMember({dispatch, getters}, {groupId, domain, identityId}) {
const servers = await dispatch('getFriendServers', {username: 'x@' + domain})
return await servers.delete(getters.signAuth, '/api/groups/' + groupId + '/members/' + identityId + '/')
},
async fetchGroupInvites({commit, dispatch, getters}) {
@ -554,11 +576,11 @@ export default createStore({
commit('setGroupMemberships', data)
return data
},
async inviteToGroup({state, dispatch, getters}, {groupId, groupHandle, invitee}) {
const home_servers = await dispatch('getHomeServers')
const home_reply = await home_servers.post(
async inviteToGroup({state, dispatch, getters}, {groupId, domain, groupHandle, invitee}) {
const group_servers = await dispatch('getFriendServers', {username: 'x@' + domain})
const group_reply = await group_servers.post(
getters.signAuth, '/api/groups/' + groupId + '/invites/', {invitee})
if (!home_reply.secret) {
if (!group_reply.secret) {
return false
}
const invitee_servers = await dispatch('getFriendServers', {username: invitee})
@ -567,7 +589,7 @@ export default createStore({
inviter: state.user,
inviter_key: nacl.to_hex(state.keypair.signPk),
invitee,
secret: home_reply.secret
secret: group_reply.secret
})
return true
},
@ -588,10 +610,13 @@ export default createStore({
const servers = await dispatch('getHomeServers')
return await servers.delete(getters.signAuth, '/api/groupinvites/' + invite.id + '/')
},
async fetchGroupInventoryItems({commit, dispatch, getters}, groupId) {
const servers = await dispatch('getHomeServers')
async fetchGroupInventoryItems({commit, dispatch, getters}, {groupId, domain}) {
const servers = await dispatch('getFriendServers', {username: 'x@' + domain})
const items = await servers.get(getters.signAuth, '/api/inventory_items/?group=' + groupId)
commit('setInventoryItems', {url: '/group/' + groupId, items})
// Namespaced by domain, not just groupId: that pk is only unique within its own
// backend's database, so two different domains could otherwise collide on the same
// item_map key.
commit('setInventoryItems', {url: '/group/' + domain + '/' + groupId, items})
return items
},
async fetchFiles({state, commit, dispatch, getters}) {
@ -823,7 +848,7 @@ export default createStore({
inventory_items(state) {
return state.item_map['/'] || []
},
groupInventoryItems: (state) => (groupId) => state.item_map['/group/' + groupId] || [],
groupInventoryItems: (state) => (groupId, domain) => state.item_map['/group/' + domain + '/' + groupId] || [],
identityIdByHandle(state) {
return Object.fromEntries(state.idmap.identities.map(i => [i.username, i.id]))
},