about summary refs log tree commit diff
path: root/src/state/models/content/list-membership.ts
blob: 135d34dd5cf4267f81e9f1ad57d01a227f8e431a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import {makeAutoObservable} from 'mobx'
import {AtUri, AppBskyGraphListitem} from '@atproto/api'
import {runInAction} from 'mobx'
import {RootStoreModel} from '../root-store'

const PAGE_SIZE = 100
interface Membership {
  uri: string
  value: AppBskyGraphListitem.Record
}

interface ListitemRecord {
  uri: string
  value: AppBskyGraphListitem.Record
}

interface ListitemListResponse {
  cursor?: string
  records: ListitemRecord[]
}

export class ListMembershipModel {
  // data
  memberships: Membership[] = []

  constructor(public rootStore: RootStoreModel, public subject: string) {
    makeAutoObservable(
      this,
      {
        rootStore: false,
      },
      {autoBind: true},
    )
  }

  // public api
  // =

  async fetch() {
    // NOTE
    // this approach to determining list membership is too inefficient to work at any scale
    // it needs to be replaced with server side list membership queries
    // -prf
    let cursor
    let records: ListitemRecord[] = []
    for (let i = 0; i < 100; i++) {
      const res: ListitemListResponse =
        await this.rootStore.agent.app.bsky.graph.listitem.list({
          repo: this.rootStore.me.did,
          cursor,
          limit: PAGE_SIZE,
        })
      records = records.concat(
        res.records.filter(record => record.value.subject === this.subject),
      )
      cursor = res.cursor
      if (!cursor) {
        break
      }
    }
    runInAction(() => {
      this.memberships = records
    })
  }

  getMembership(listUri: string) {
    return this.memberships.find(m => m.value.list === listUri)
  }

  isMember(listUri: string) {
    return !!this.getMembership(listUri)
  }

  async add(listUri: string) {
    if (this.isMember(listUri)) {
      return
    }
    const res = await this.rootStore.agent.app.bsky.graph.listitem.create(
      {
        repo: this.rootStore.me.did,
      },
      {
        subject: this.subject,
        list: listUri,
        createdAt: new Date().toISOString(),
      },
    )
    const {rkey} = new AtUri(res.uri)
    const record = await this.rootStore.agent.app.bsky.graph.listitem.get({
      repo: this.rootStore.me.did,
      rkey,
    })
    runInAction(() => {
      this.memberships = this.memberships.concat([record])
    })
  }

  async remove(listUri: string) {
    const membership = this.getMembership(listUri)
    if (!membership) {
      return
    }
    const {rkey} = new AtUri(membership.uri)
    await this.rootStore.agent.app.bsky.graph.listitem.delete({
      repo: this.rootStore.me.did,
      rkey,
    })
    runInAction(() => {
      this.memberships = this.memberships.filter(m => m.value.list !== listUri)
    })
  }

  async updateTo(
    uris: string[],
  ): Promise<{added: string[]; removed: string[]}> {
    const added = []
    const removed = []
    for (const uri of uris) {
      await this.add(uri)
      added.push(uri)
    }
    for (const membership of this.memberships) {
      if (!uris.includes(membership.value.list)) {
        await this.remove(membership.value.list)
        removed.push(membership.value.list)
      }
    }
    return {added, removed}
  }
}