123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- const pull = require('../server/node_modules/pull-stream')
- const moment = require('../server/node_modules/moment')
- module.exports = ({ cooler }) => {
- let ssb
- const openSsb = async () => { if (!ssb) ssb = await cooler.open(); return ssb }
- return {
- type: 'bookmark',
- async createBookmark(url, tagsRaw, description, category, lastVisit) {
- const ssbClient = await openSsb()
- const userId = ssbClient.id
- let tags = Array.isArray(tagsRaw) ? tagsRaw.filter(t => t) : tagsRaw.split(',').map(t => t.trim()).filter(Boolean)
- const isInternal = url.includes('127.0.0.1') || url.includes('localhost')
- if (!tags.includes(isInternal ? 'internal' : 'external')) {
- tags.push(isInternal ? 'internal' : 'external')
- }
- const formattedLastVisit = lastVisit
- ? moment(lastVisit, moment.ISO_8601, true).toISOString()
- : moment().toISOString()
- const content = {
- type: 'bookmark',
- author: userId,
- url,
- tags,
- description,
- category,
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
- lastVisit: formattedLastVisit,
- opinions: {},
- opinions_inhabitants: []
- }
- return new Promise((resolve, reject) => {
- ssbClient.publish(content, (err, res) => err ? reject(new Error("Error creating bookmark: " + err.message)) : resolve(res))
- })
- },
- async listAll(author = null, filter = 'all') {
- const ssbClient = await openSsb()
- const userId = ssbClient.id
- const results = await new Promise((res, rej) => {
- pull(
- ssbClient.createLogStream(),
- pull.collect((err, msgs) => err ? rej(err) : res(msgs))
- )
- })
- const tombstoned = new Set(
- results
- .filter(m => m.value.content?.type === 'tombstone')
- .map(m => m.value.content.target)
- )
- const replaces = new Map()
- const latest = new Map()
- for (const m of results) {
- const k = m.key
- const c = m.value.content
- if (!c || c.type !== 'bookmark') continue
- if (tombstoned.has(k)) continue
- if (c.replaces) replaces.set(c.replaces, k)
- latest.set(k, {
- id: k,
- url: c.url,
- description: c.description,
- category: c.category,
- createdAt: c.createdAt,
- lastVisit: c.lastVisit,
- tags: c.tags || [],
- opinions: c.opinions || {},
- opinions_inhabitants: c.opinions_inhabitants || [],
- author: c.author
- })
- }
- for (const oldId of replaces.keys()) {
- latest.delete(oldId)
- }
- let bookmarks = Array.from(latest.values())
- if (filter === 'mine' && author === userId) {
- bookmarks = bookmarks.filter(b => b.author === author)
- } else if (filter === 'external') {
- bookmarks = bookmarks.filter(b => b.tags.includes('external'))
- } else if (filter === 'internal') {
- bookmarks = bookmarks.filter(b => b.tags.includes('internal'))
- }
- return bookmarks
- },
- async updateBookmarkById(bookmarkId, updatedData) {
- const ssbClient = await openSsb()
- const userId = ssbClient.id
- const old = await new Promise((res, rej) =>
- ssbClient.get(bookmarkId, (err, msg) =>
- err || !msg?.content ? rej(err || new Error("Error retrieving old bookmark.")) : res(msg)
- )
- )
- if (Object.keys(old.content.opinions || {}).length > 0) {
- throw new Error('Cannot edit bookmark after it has received opinions.')
- }
- const tags = updatedData.tags
- ? updatedData.tags.split(',').map(t => t.trim()).filter(Boolean)
- : []
- const isInternal = updatedData.url.includes('127.0.0.1') || updatedData.url.includes('localhost')
- if (!tags.includes(isInternal ? 'internal' : 'external')) {
- tags.push(isInternal ? 'internal' : 'external')
- }
- const formattedLastVisit = updatedData.lastVisit
- ? moment(updatedData.lastVisit, moment.ISO_8601, true).toISOString()
- : moment().toISOString()
- const updated = {
- type: 'bookmark',
- replaces: bookmarkId,
- author: old.content.author,
- url: updatedData.url,
- tags,
- description: updatedData.description,
- category: updatedData.category,
- createdAt: old.content.createdAt,
- updatedAt: new Date().toISOString(),
- lastVisit: formattedLastVisit,
- opinions: old.content.opinions,
- opinions_inhabitants: old.content.opinions_inhabitants
- }
- return new Promise((resolve, reject) => {
- ssbClient.publish(updated, (err2, res) => err2 ? reject(new Error("Error creating updated bookmark.")) : resolve(res))
- })
- },
- async deleteBookmarkById(bookmarkId) {
- const ssbClient = await openSsb()
- const userId = ssbClient.id
- const msg = await new Promise((res, rej) =>
- ssbClient.get(bookmarkId, (err, m) => err ? rej(new Error("Error retrieving bookmark.")) : res(m))
- )
- if (msg.content.author !== userId) throw new Error("Error: You are not the author of this bookmark.")
- const tombstone = {
- type: 'tombstone',
- target: bookmarkId,
- deletedAt: new Date().toISOString(),
- author: userId
- }
- return new Promise((resolve, reject) => {
- ssbClient.publish(tombstone, (err2, res) => {
- if (err2) return reject(new Error("Error creating tombstone."))
- resolve(res)
- })
- })
- },
- async getBookmarkById(bookmarkId) {
- const ssbClient = await openSsb()
- return new Promise((resolve, reject) => {
- ssbClient.get(bookmarkId, (err, msg) => {
- if (err || !msg || !msg.content) return reject(new Error("Error retrieving bookmark"))
- const c = msg.content
- resolve({
- id: bookmarkId,
- url: c.url || "Unknown",
- description: c.description || "No description",
- category: c.category || "No category",
- createdAt: c.createdAt || "Unknown",
- updatedAt: c.updatedAt || "Unknown",
- lastVisit: c.lastVisit || "Unknown",
- tags: c.tags || [],
- opinions: c.opinions || {},
- opinions_inhabitants: c.opinions_inhabitants || [],
- author: c.author || "Unknown"
- })
- })
- })
- },
- async createOpinion(bookmarkId, category) {
- const ssbClient = await openSsb()
- const userId = ssbClient.id
- return new Promise((resolve, reject) => {
- ssbClient.get(bookmarkId, (err, msg) => {
- if (err || !msg || msg.content?.type !== 'bookmark') return reject(new Error('Bookmark not found'))
- if (msg.content.opinions_inhabitants?.includes(userId)) return reject(new Error('Already voted'))
- const updated = {
- ...msg.content,
- replaces: bookmarkId,
- opinions: {
- ...msg.content.opinions,
- [category]: (msg.content.opinions?.[category] || 0) + 1
- },
- opinions_inhabitants: [...(msg.content.opinions_inhabitants || []), userId],
- updatedAt: new Date().toISOString()
- }
- ssbClient.publish(updated, (err3, result) => err3 ? reject(err3) : resolve(result))
- })
- })
- }
- }
- }
|