import invariant from "invariant"; import { filter, find, isUndefined, orderBy } from "es-toolkit/compat"; import { action, computed, observable } from "mobx"; import type { NavigationNode, PublicTeam } from "@shared/types"; import type Document from "~/models/Document"; import Share from "~/models/Share"; import type { PartialExcept } from "~/types"; import { client } from "~/utils/ApiClient"; import type RootStore from "./RootStore"; import Store, { RPCAction } from "./base/Store"; export default class SharesStore extends Store { actions = [ RPCAction.Info, RPCAction.List, RPCAction.Create, RPCAction.Update, ]; @observable sharedCache: Map< string, { sharedTree: NavigationNode | null; team: PublicTeam } | undefined > = new Map(); constructor(rootStore: RootStore) { super(rootStore, Share); } @computed get orderedData(): Share[] { return orderBy(Array.from(this.data.values()), "createdAt", "asc"); } @computed get published(): Share[] { return filter(this.orderedData, (share) => share.published); } @action revoke = async (share: Share) => { await client.post("/shares.revoke", { id: share.id, }); this.remove(share.id); }; @action async create( params: | (PartialExcept & { type: "collection" }) | (PartialExcept & { type: "document" }) ): Promise { const item = params.type === "collection" ? this.getByCollectionId(params.collectionId) : this.getByDocumentId(params.documentId); if (item) { return item; } return super.create(params); } @action async fetch(id: string) { const share = this.get(id); const cache = this.sharedCache.get(id); if (share && cache) { return share; } this.isFetching = true; try { const res = await client.post(`/${this.apiEndpoint}.info`, { id, }); invariant(res?.data, "Data should be available"); res.data.shares.map(this.add); if (res.data.collection) { this.rootStore.collections.add(res.data.collection); } if (res.data.document) { this.rootStore.documents.add(res.data.document); } this.sharedCache.set(id, { sharedTree: res.data.sharedTree, team: res.data.team, }); this.addPolicies(res.policies); return this.data.get(id)!; } finally { this.isFetching = false; } } @action async fetchOne(params: { documentId: string } | { collectionId: string }) { const share = "collectionId" in params ? this.getByCollectionId(params.collectionId) : this.getByDocumentId(params.documentId); if (share) { return share; } this.isFetching = true; try { const res = await client.post(`/${this.apiEndpoint}.info`, params); if (isUndefined(res)) { return; } invariant(res?.data, "Data should be available"); this.addPolicies(res.policies); return res.data.shares.map(this.add); } finally { this.isFetching = false; } } getByDocumentParents = (document: Document): Share | undefined => { const collectionShare = document.collectionId ? this.getByCollectionId(document.collectionId) : undefined; if (collectionShare?.published) { return collectionShare; } const collection = document.collectionId ? this.rootStore.collections.get(document.collectionId) : undefined; if (!collection) { return; } const parentIds = collection .pathToDocument(document.id) .slice(0, -1) .map((p) => p.id); for (const parentId of parentIds) { const share = this.getByDocumentId(parentId); if (share?.includeChildDocuments && share.published) { return share; } } return undefined; }; getByCollectionId = (collectionId: string): Share | null | undefined => find(this.orderedData, (share) => share.collectionId === collectionId); getByDocumentId = (documentId: string): Share | null | undefined => find(this.orderedData, (share) => share.documentId === documentId); get(id: string): Share | undefined { return id ? (this.data.get(id) ?? this.orderedData.find((share) => id.endsWith(share.urlId))) : undefined; } }