408 lines
9.4 KiB
TypeScript
408 lines
9.4 KiB
TypeScript
import { SiteNavigationItem, Profile } from "~/lib/types"
|
|
import { nanoid } from "nanoid"
|
|
import unidata from "~/queries/unidata.server"
|
|
import { renderPageContent } from "~/markdown"
|
|
import { toGateway } from "~/lib/ipfs-parser"
|
|
import type Unidata from "unidata.js"
|
|
import { createClient } from "@urql/core"
|
|
import axios from "axios"
|
|
import { indexer } from "~/queries/crossbell"
|
|
import type { LinkEntity, NoteEntity } from "crossbell.js"
|
|
|
|
export const checkSubdomain = async ({
|
|
subdomain,
|
|
updatingSiteId,
|
|
}: {
|
|
subdomain: string
|
|
updatingSiteId?: string
|
|
}) => {}
|
|
|
|
const expandSite = (site: Profile) => {
|
|
site.navigation = JSON.parse(
|
|
site.metadata?.raw?.attributes?.find(
|
|
(a: any) => a.trait_type === "xlog_navigation",
|
|
)?.value || "null",
|
|
) ||
|
|
site.metadata?.raw?.["_xlog_navigation"] ||
|
|
site.metadata?.raw?.["_crosslog_navigation"] || [
|
|
{ id: nanoid(), label: "Archives", url: "/archives" },
|
|
]
|
|
site.css =
|
|
site.metadata?.raw?.attributes?.find(
|
|
(a: any) => a.trait_type === "xlog_css",
|
|
)?.value ||
|
|
site.metadata?.raw?.["_xlog_css"] ||
|
|
site.metadata?.raw?.["_crosslog_css"] ||
|
|
""
|
|
site.ga =
|
|
site.metadata?.raw?.attributes?.find((a: any) => a.trait_type === "xlog_ga")
|
|
?.value || ""
|
|
site.custom_domain =
|
|
site.metadata?.raw?.attributes?.find(
|
|
(a: any) => a.trait_type === "xlog_custom_domain",
|
|
)?.value || ""
|
|
site.name = site.name || site.username
|
|
site.description = renderPageContent(site.bio || "", true).contentHTML
|
|
|
|
if (site.avatars) {
|
|
site.avatars = site.avatars.map((avatar) => toGateway(avatar))
|
|
}
|
|
if (site.banners) {
|
|
site.banners.map((banner) => {
|
|
banner.address = toGateway(banner.address)
|
|
return banner
|
|
})
|
|
}
|
|
|
|
return site
|
|
}
|
|
|
|
export const getUserSites = async (
|
|
address?: string,
|
|
customUnidata?: Unidata,
|
|
) => {
|
|
if (!address) {
|
|
return null
|
|
}
|
|
const profiles = await (customUnidata || unidata).profiles.get({
|
|
source: "Crossbell Profile",
|
|
identity: address,
|
|
platform: "Ethereum",
|
|
filter: {
|
|
primary: true,
|
|
},
|
|
})
|
|
|
|
const sites: Profile[] = profiles.list?.map((profile) => {
|
|
expandSite(profile)
|
|
return profile
|
|
})
|
|
|
|
if (!sites || !sites.length) return null
|
|
|
|
return sites
|
|
}
|
|
|
|
export const getSite = async (input: string, customUnidata?: Unidata) => {
|
|
const profiles = await (customUnidata || unidata).profiles.get({
|
|
source: "Crossbell Profile",
|
|
identity: input,
|
|
platform: "Crossbell",
|
|
})
|
|
|
|
const site: Profile = profiles.list[0]
|
|
if (site) {
|
|
expandSite(site)
|
|
}
|
|
|
|
return site
|
|
}
|
|
|
|
export const getSites = async (input: string[]) => {
|
|
const client = createClient({
|
|
url: "https://indexer.crossbell.io/v1/graphql",
|
|
})
|
|
const result = await client
|
|
.query(
|
|
`
|
|
query getCharacters($identities: [String!], $limit: Int) {
|
|
characters( where: { handle: { in: $identities } }, orderBy: [{ updatedAt: desc }], take: $limit ) {
|
|
handle
|
|
updatedAt
|
|
notes {
|
|
updatedAt
|
|
}
|
|
metadata {
|
|
uri
|
|
content
|
|
}
|
|
}
|
|
}`,
|
|
{
|
|
identities: input,
|
|
},
|
|
)
|
|
.toPromise()
|
|
|
|
await Promise.all(
|
|
result.data?.characters?.map(async (site: any) => {
|
|
if (!site?.metadata?.content && site?.metadata?.uri) {
|
|
site.metadata.content = (
|
|
await axios.get(
|
|
toGateway(site?.metadata?.uri, {
|
|
needRequestAtServerSide: true,
|
|
}),
|
|
{
|
|
...(typeof window === "undefined" && {
|
|
headers: {
|
|
"User-Agent":
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36",
|
|
},
|
|
}),
|
|
},
|
|
)
|
|
).data
|
|
}
|
|
site.metadata.content.name = site.metadata.content.name || site.handle
|
|
|
|
site.custom_domain =
|
|
site.metadata?.content?.attributes?.find(
|
|
(a: any) => a.trait_type === "xlog_custom_domain",
|
|
)?.value || ""
|
|
|
|
site.updatedAt = [...site.notes, site]
|
|
.map((i) => i.updatedAt)
|
|
.sort()
|
|
.pop()
|
|
}),
|
|
)
|
|
|
|
result.data?.characters?.sort((a: any, b: any) => {
|
|
return b.updatedAt > a.updatedAt ? 1 : -1
|
|
})
|
|
|
|
return result.data?.characters
|
|
}
|
|
|
|
export const getSubscription = async (
|
|
data: {
|
|
userId: string
|
|
siteId: string
|
|
},
|
|
customUnidata?: Unidata,
|
|
) => {
|
|
const links = await (customUnidata || unidata).links.get({
|
|
source: "Crossbell Link",
|
|
identity: data.userId,
|
|
platform: "Ethereum",
|
|
filter: {
|
|
to: data.siteId,
|
|
},
|
|
})
|
|
return !!links?.list?.length
|
|
}
|
|
|
|
export const getSiteSubscriptions = async (
|
|
data: {
|
|
siteId: string
|
|
cursor?: string
|
|
},
|
|
customUnidata?: Unidata,
|
|
) => {
|
|
const links = await (customUnidata || unidata).links.get({
|
|
source: "Crossbell Link",
|
|
identity: data.siteId,
|
|
platform: "Crossbell",
|
|
reversed: true,
|
|
cursor: data.cursor,
|
|
})
|
|
|
|
links?.list.map(async (item: any) => {
|
|
item.character = item.metadata.from_raw
|
|
}) || []
|
|
|
|
return links
|
|
}
|
|
|
|
export const getSiteToSubscriptions = async (
|
|
data: {
|
|
siteId: string
|
|
cursor?: string
|
|
},
|
|
customUnidata?: Unidata,
|
|
) => {
|
|
const links = await (customUnidata || unidata).links.get({
|
|
source: "Crossbell Link",
|
|
identity: data.siteId,
|
|
platform: "Crossbell",
|
|
cursor: data.cursor,
|
|
})
|
|
|
|
links?.list.map(async (item: any) => {
|
|
item.character = item.metadata.to_raw
|
|
}) || []
|
|
|
|
return links
|
|
}
|
|
|
|
export async function updateSite(
|
|
payload: {
|
|
site: string
|
|
name?: string
|
|
description?: string
|
|
icon?: string | null
|
|
subdomain?: string
|
|
navigation?: SiteNavigationItem[]
|
|
css?: string
|
|
ga?: string
|
|
custom_domain?: string
|
|
},
|
|
customUnidata?: Unidata,
|
|
) {
|
|
return await (customUnidata || unidata).profiles.set(
|
|
{
|
|
source: "Crossbell Profile",
|
|
identity: payload.site,
|
|
platform: "Crossbell",
|
|
action: "update",
|
|
},
|
|
{
|
|
...(payload.name && { name: payload.name }),
|
|
...(payload.description && { bio: payload.description }),
|
|
...(payload.icon && { avatars: [payload.icon] }),
|
|
...(payload.subdomain && { username: payload.subdomain }),
|
|
...((payload.navigation !== undefined ||
|
|
payload.css !== undefined ||
|
|
payload.ga !== undefined ||
|
|
payload.custom_domain !== undefined) && {
|
|
attributes: [
|
|
...(payload.navigation !== undefined
|
|
? [
|
|
{
|
|
trait_type: "xlog_navigation",
|
|
value: JSON.stringify(payload.navigation),
|
|
},
|
|
]
|
|
: []),
|
|
...(payload.css !== undefined
|
|
? [
|
|
{
|
|
trait_type: "xlog_css",
|
|
value: payload.css,
|
|
},
|
|
]
|
|
: []),
|
|
...(payload.ga !== undefined
|
|
? [
|
|
{
|
|
trait_type: "xlog_ga",
|
|
value: payload.ga,
|
|
},
|
|
]
|
|
: []),
|
|
...(payload.custom_domain !== undefined
|
|
? [
|
|
{
|
|
trait_type: "xlog_custom_domain",
|
|
value: payload.custom_domain,
|
|
},
|
|
]
|
|
: []),
|
|
],
|
|
}),
|
|
},
|
|
)
|
|
}
|
|
|
|
export async function createSite(
|
|
address: string,
|
|
payload: { name: string; subdomain: string },
|
|
customUnidata?: Unidata,
|
|
) {
|
|
return await (customUnidata || unidata).profiles.set(
|
|
{
|
|
source: "Crossbell Profile",
|
|
identity: address,
|
|
platform: "Ethereum",
|
|
action: "add",
|
|
},
|
|
{
|
|
username: payload.subdomain,
|
|
name: payload.name,
|
|
tags: [
|
|
"navigation:" +
|
|
JSON.stringify([
|
|
{
|
|
id: nanoid(),
|
|
label: "Archives",
|
|
url: "/archives",
|
|
},
|
|
]),
|
|
],
|
|
},
|
|
)
|
|
}
|
|
|
|
export async function subscribeToSite(
|
|
input: {
|
|
userId: string
|
|
siteId: string
|
|
},
|
|
customUnidata?: Unidata,
|
|
) {
|
|
return (customUnidata || unidata).links.set(
|
|
{
|
|
source: "Crossbell Link",
|
|
identity: input.userId,
|
|
platform: "Ethereum",
|
|
action: "add",
|
|
},
|
|
{
|
|
to: input.siteId,
|
|
type: "follow",
|
|
},
|
|
)
|
|
}
|
|
|
|
export async function unsubscribeFromSite(
|
|
input: {
|
|
userId: string
|
|
siteId: string
|
|
},
|
|
customUnidata?: Unidata,
|
|
) {
|
|
return (customUnidata || unidata).links.set(
|
|
{
|
|
source: "Crossbell Link",
|
|
identity: input.userId,
|
|
platform: "Ethereum",
|
|
action: "remove",
|
|
},
|
|
{
|
|
to: input.siteId,
|
|
type: "follow",
|
|
},
|
|
)
|
|
}
|
|
|
|
export async function getNotifications(input: { siteCId: string }) {
|
|
const [subscriptions, notes] = await Promise.all([
|
|
indexer.getBacklinksOfCharacter(input.siteCId, {
|
|
limit: 100,
|
|
}),
|
|
indexer.getNotes({
|
|
toCharacterId: input.siteCId,
|
|
limit: 100,
|
|
includeCharacter: true,
|
|
}),
|
|
])
|
|
|
|
return [
|
|
...(subscriptions.list.map(
|
|
(
|
|
item: LinkEntity & {
|
|
type?: "backlinks"
|
|
},
|
|
) => {
|
|
item.type = "backlinks"
|
|
return item
|
|
},
|
|
) as any),
|
|
]
|
|
.concat(
|
|
notes.list.map(
|
|
(
|
|
item: NoteEntity & {
|
|
type?: "notes"
|
|
},
|
|
) => {
|
|
item.type = "notes"
|
|
return item
|
|
},
|
|
),
|
|
)
|
|
.sort((a, b) => {
|
|
return b.createdAt > a.createdAt ? 1 : -1
|
|
})
|
|
}
|