Smart Contract
FindThoughts
A.097bafa4e0b48eef.FindThoughts
1import FindViews from 0x097bafa4e0b48eef
2import FindMarket from 0x097bafa4e0b48eef
3import MetadataViews from 0x1d7e57aa55817448
4import ViewResolver from 0x1d7e57aa55817448
5import FINDNFTCatalog from 0x097bafa4e0b48eef
6import FIND from 0x097bafa4e0b48eef
7import Clock from 0x097bafa4e0b48eef
8
9access(all) contract FindThoughts {
10 // Entitlements
11 access(all) entitlement Owner
12
13 // Events
14 access(all) event Published(id: UInt64, creator: Address, creatorName: String?, header: String, message: String, medias: [String], nfts:[FindMarket.NFTInfo], tags: [String], quoteOwner: Address?, quoteId: UInt64?)
15 access(all) event Edited(id: UInt64, creator: Address, creatorName: String?, header: String, message: String, medias: [String], hide: Bool, tags: [String])
16 access(all) event Deleted(id: UInt64, creator: Address, creatorName: String?, header: String, message: String, medias: [String], tags: [String])
17 access(all) event Reacted(id: UInt64, by: Address, byName: String?, creator: Address, creatorName: String?, header: String, reaction: String?, totalCount: {String : Int})
18
19 // Paths
20 access(all) let CollectionStoragePath : StoragePath
21 access(all) let CollectionPublicPath : PublicPath
22
23 access(all) struct ThoughtPointer {
24 access(all) let cap: Capability<&FindThoughts.Collection>
25 access(all) let id: UInt64
26
27 init(creator: Address, id: UInt64) {
28 let cap = getAccount(creator).capabilities.get<&FindThoughts.Collection>(FindThoughts.CollectionPublicPath)
29 if cap !=nil && !cap!.check() {
30 panic("creator's find thought capability is not valid. Creator : ".concat(creator.toString()))
31 }
32 self.cap = cap!
33 self.id = id
34 }
35
36 access(all) fun borrowThoughtPublic() : &{ThoughtPublic}? {
37 if self.cap.check() {
38 let ref = self.cap.borrow()!
39 if ref.contains(self.id) {
40 return ref.borrowThoughtPublic(self.id)
41 }
42 }
43 return nil
44 }
45
46 access(all) fun valid() : Bool {
47 if self.borrowThoughtPublic() != nil {
48 return true
49 }
50 return false
51 }
52
53 access(all) fun owner() : Address {
54 return self.cap.address
55 }
56 }
57
58 access(all) resource interface ThoughtPublic {
59 access(all) let id: UInt64
60 access(all) let creator: Address
61 access(all) var header: String
62 access(all) var body: String
63 access(all) let created: UFix64
64 access(all) var lastUpdated: UFix64?
65 access(all) let medias: [MetadataViews.Media]
66 access(all) let nft: [FindViews.ViewReadPointer]
67 access(all) var tags: [String]
68 access(all) var reacted: {Address : String}
69 access(all) var reactions: {String : Int}
70
71 access(contract) fun internal_react(user: Address, reaction: String?)
72 access(all) fun getQuotedThought() : ThoughtPointer?
73 access(all) fun getHide() : Bool
74 access(all) fun getTags() : [String] {
75 return self.tags
76 }
77 access(all) fun getReactions() : {String:Int}{
78 return self.reactions
79 }
80
81 access(all) fun getNFTS() : [FindViews.ViewReadPointer] {
82 return self.nft
83 }
84 }
85
86 access(all) resource interface ThoughtPrivate {
87 access(Owner) fun hide(_ hide: Bool)
88 access(Owner) fun edit(header: String , body: String, tags: [String])
89 }
90
91 access(all) resource Thought : ThoughtPublic, ThoughtPrivate, ViewResolver.Resolver {
92 access(all) let id: UInt64
93 access(all) let creator: Address
94 access(all) var header: String
95 access(all) var body: String
96 access(all) let created: UFix64
97 access(all) var lastUpdated: UFix64?
98 access(all) var tags: [String]
99 // user : Reactions
100 access(all) var reacted: {Address : String}
101 // Reactions : Counts
102 access(all) var reactions: {String : Int}
103
104 // only one image is enabled at the moment
105 access(all) let medias: [MetadataViews.Media]
106
107 // These are here only for future extension
108 access(all) let nft: [FindViews.ViewReadPointer]
109 access(self) let stringTags: {String : String}
110 access(self) let scalars: {String : UFix64}
111 access(self) let extras: {String : AnyStruct}
112
113 init(creator: Address , header: String , body: String , created: UFix64, tags: [String], medias: [MetadataViews.Media], nft: [FindViews.ViewReadPointer], quote: ThoughtPointer?, stringTags: {String : String}, scalars : {String : UFix64}, extras: {String : AnyStruct} ) {
114 self.id = self.uuid
115 self.creator = creator
116 self.header = header
117 self.body = body
118 self.created = created
119 self.lastUpdated = nil
120 self.tags = tags
121 self.medias = medias
122
123 self.nft = nft
124 self.stringTags = stringTags
125 self.scalars = scalars
126 extras["quote"] = quote
127 extras["hidden"] = false
128 self.extras = extras
129
130 self.reacted = {}
131 self.reactions = {}
132 }
133
134 access(all) fun getQuotedThought() : ThoughtPointer? {
135 if let r = self.extras["quote"] {
136 return r as! ThoughtPointer
137 }
138 return nil
139 }
140
141 access(all) fun getHide() : Bool {
142 if let r = self.extras["hidden"] {
143 return r as! Bool
144 }
145 return false
146 }
147
148 access(Owner) fun hide(_ hide: Bool) {
149 self.extras["hidden"] = hide
150 let medias : [String] = []
151 for m in self.medias {
152 medias.append(m.file.uri())
153 }
154 emit Edited(id: self.id, creator: self.creator, creatorName: FIND.reverseLookup(self.creator), header: self.header, message: self.body, medias: medias, hide: hide, tags: self.tags)
155 }
156
157 access(Owner) fun edit(header: String , body: String, tags: [String]) {
158 self.header = header
159 self.body = body
160 self.tags = tags
161 let address = self.owner!.address
162 let medias : [String] = []
163 for m in self.medias {
164 medias.append(m.file.uri())
165 }
166 self.lastUpdated = Clock.time()
167 emit Edited(id: self.id, creator: address, creatorName: FIND.reverseLookup(address), header: self.header, message: self.body, medias: medias, hide: self.getHide(), tags: self.tags)
168 }
169
170 // To withdraw reaction, pass in nil
171 access(contract) fun internal_react(user: Address, reaction: String?) {
172 let owner = self.owner!.address
173 if let previousReaction = self.reacted[user] {
174 // reaction here cannot be nil, therefore we can !
175 self.reactions[previousReaction] = self.reactions[previousReaction]! - 1
176 if self.reactions[previousReaction]! == 0 {
177 self.reactions.remove(key: previousReaction)
178 }
179 }
180
181 self.reacted[user] = reaction
182
183 if reaction != nil {
184 var reacted = self.reactions[reaction!] ?? 0
185 reacted = reacted + 1
186 self.reactions[reaction!] = reacted
187 }
188
189 emit Reacted(id: self.id, by: user, byName: FIND.reverseLookup(user), creator: owner, creatorName: FIND.reverseLookup(owner), header: self.header, reaction: reaction, totalCount: self.reactions)
190 }
191
192 access(all) view fun getViews(): [Type] {
193 return [
194 Type<MetadataViews.Display>()
195 ]
196 }
197
198 access(all) view fun resolveView(_ type: Type) : AnyStruct? {
199 switch type {
200
201 case Type<MetadataViews.Display>():
202
203 let content = self.body.concat(" -- FIND Thought by ").concat(self.owner!.address.toString())
204
205 return MetadataViews.Display(
206 name: self.header,
207 description: content,
208 thumbnail: self.medias[0].file
209 )
210
211 }
212 return nil
213 }
214 }
215
216 access(all) resource interface CollectionPublic {
217 access(all) fun contains(_ id: UInt64) : Bool
218 access(all) view fun borrowThoughtPublic(_ id: UInt64) : &{FindThoughts.ThoughtPublic}
219 }
220
221 access(all) resource Collection : CollectionPublic, ViewResolver.ResolverCollection {
222 access(self) let ownedThoughts : @{UInt64 : FindThoughts.Thought}
223 access(self) let sequence : [UInt64]
224
225 init() {
226 self.ownedThoughts <- {}
227 self.sequence = []
228 }
229
230 access(all) fun contains(_ id: UInt64) : Bool {
231 return self.ownedThoughts.containsKey(id)
232 }
233
234 access(all) view fun getIDs() : [UInt64] {
235 return self.ownedThoughts.keys
236 }
237
238 access(Owner) fun borrow(_ id: UInt64) : auth(Owner) &{FindThoughts.ThoughtPrivate} {
239 pre{
240 self.ownedThoughts.containsKey(id) : "Cannot borrow Thought with ID : ".concat(id.toString())
241 }
242 return (&self.ownedThoughts[id])!
243 }
244
245 access(all) view fun borrowThoughtPublic(_ id: UInt64) : &{FindThoughts.ThoughtPublic} {
246 pre{
247 self.ownedThoughts.containsKey(id) : "Cannot borrow Thought with ID : ".concat(id.toString())
248 }
249 return (&self.ownedThoughts[id] as &FindThoughts.Thought?)!
250 }
251
252 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver} {
253 pre{
254 self.ownedThoughts.containsKey(id) : "Cannot borrow Thought with ID : ".concat(id.toString())
255 }
256 return (&self.ownedThoughts[id] as &FindThoughts.Thought?)!
257 }
258
259 access(Owner) fun publish(header: String , body: String , tags: [String], media: MetadataViews.Media?, nftPointer: FindViews.ViewReadPointer?, quote: FindThoughts.ThoughtPointer?) {
260 let medias : [MetadataViews.Media] = []
261 let m : [String] = []
262 if media != nil {
263 medias.append(media!)
264 m.append(media!.file.uri())
265 }
266 let address = self.owner!.address
267
268 let nfts : [FindMarket.NFTInfo] = []
269 let extra : {String : AnyStruct} = {}
270 let nftPointers : [FindViews.ViewReadPointer] = []
271 if nftPointer != nil {
272 let rv = nftPointer!.getViewResolver()
273 nfts.append(FindMarket.NFTInfo(rv, id: nftPointer!.id, detail: true))
274 nftPointers.append(nftPointer!)
275 }
276
277
278 let thought <- create Thought(creator: address, header: header , body: body , created: Clock.time(), tags: tags, medias: medias, nft: nftPointers, quote: quote, stringTags: {}, scalars : {}, extras: extra)
279
280 self.sequence.append(thought.uuid)
281
282 let creatorName = FIND.reverseLookup(address)
283
284 emit Published(id: thought.id ,creator: address, creatorName: creatorName , header: header, message: body, medias: m, nfts: nfts, tags: tags, quoteOwner: quote?.owner(), quoteId: quote?.id)
285
286
287 self.ownedThoughts[thought.uuid] <-! thought
288 }
289
290 access(Owner) fun delete(_ id: UInt64) {
291 pre{
292 self.ownedThoughts.containsKey(id) : "Does not contains Thought with ID : ".concat(id.toString())
293 }
294
295 let thought <- self.ownedThoughts.remove(key: id)!
296 self.sequence.remove(at: self.sequence.firstIndex(of: id)!)
297
298 let address = self.owner!.address
299
300 let medias : [String] = []
301 for m in thought.medias {
302 medias.append(m.file.uri())
303 }
304
305 var name : String? = nil
306 if address != nil {
307 name = FIND.reverseLookup(address)
308 }
309 emit Deleted(id: thought.id, creator: thought.creator, creatorName: FIND.reverseLookup(thought.creator), header: thought.header, message: thought.body, medias: medias, tags: thought.tags)
310
311 destroy thought
312 }
313
314 access(Owner) fun react(user: Address, id: UInt64, reaction: String?) {
315 let cap = FindThoughts.getFindThoughtsCapability(user)
316 let ref = cap.borrow() ?? panic("Cannot borrow reference to Find Thoughts Collection from user : ".concat(user.toString()))
317
318 let thought = ref.borrowThoughtPublic(id)
319 thought.internal_react(user: self.owner!.address, reaction: reaction)
320 }
321
322 access(Owner) fun hide(id: UInt64, hide: Bool) {
323 let thought = self.borrow(id)
324 thought.hide(hide)
325 }
326
327 }
328
329 access(all) fun createEmptyCollection() : @FindThoughts.Collection {
330 return <- create Collection()
331 }
332
333 access(all) fun getFindThoughtsCapability(_ user: Address) : Capability<&{FindThoughts.CollectionPublic}> {
334 return getAccount(user).capabilities.get<&{FindThoughts.CollectionPublic}>(FindThoughts.CollectionPublicPath)!
335 }
336
337 init(){
338 self.CollectionStoragePath = /storage/FindThoughts
339 self.CollectionPublicPath = /public/FindThoughts
340 }
341}
342