diff --git a/README.md b/README.md index 3b98e6a..705c3e7 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,10 @@ Or use our favorite package manager. ## Usage -### Hive Blog Loader +In your Astro project, edit the `/src/content/config.ts` and define any collections using the loaders +that this package provides: -In your Astro project, edit the `/src/content/config.ts`: +### Hive Blog Loader ```ts import { defineCollection } from "astro:content"; @@ -30,11 +31,22 @@ export const collections = { blog: defineCollection({ type: "content_layer", loader: hiveBlogLoader("hive.coding") // Selected username + }), + accounts: defineCollection({ + type: "content_layer", + loader: hiveAccountsLoader("hive.coding") // or ["acc1", "acc2"] for multiple accounts }) }; ``` -For now only `hiveBlogLoader` is available, more coming soon! +## Learn more + +- [Astro](https://astro.build/) +- [Content Layer API](https://astro.build/blog/content-layer-deep-dive/) + +## Support + +If you have any questions or need help, please join our [Discord server](https://discord.gg/3u9v7b4w). ## Contributing diff --git a/bun.lockb b/bun.lockb index 8eb87c1..4aa310f 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index c2d874c..c606c64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@onhive.io/astro-loader", - "version": "0.1.2", + "version": "0.2.0", "author": "mietek.dev ", "repository": { "type": "git", @@ -22,21 +22,21 @@ "prepare": "bunx husky" }, "dependencies": { - "@hiveio/dhive": "^1.3.1-beta" + "@hiveio/dhive": "^1.3.2" }, "devDependencies": { - "@eslint/js": "^9.13.0", - "@faker-js/faker": "^9.1.0", + "@eslint/js": "^9.15.0", + "@faker-js/faker": "^9.2.0", "@types/bun": "latest", - "eslint": "^9.13.0", + "eslint": "^9.15.0", "eslint-config-prettier": "^9.1.0", - "globals": "^15.11.0", - "husky": "^9.1.6", + "globals": "^15.12.0", + "husky": "^9.1.7", "lint-staged": "^15.2.10", "prettier": "^3.3.3", - "tsup": "^8.3.0", - "typescript": "^5.6.3", - "typescript-eslint": "^8.11.0" + "tsup": "^8.3.5", + "typescript": "^5.7.2", + "typescript-eslint": "^8.15.0" }, "peerDependencies": { "astro": "^4.16.7" diff --git a/src/adapters/accounts.ts b/src/adapters/accounts.ts new file mode 100644 index 0000000..a3a207a --- /dev/null +++ b/src/adapters/accounts.ts @@ -0,0 +1,37 @@ +import type { DynamicGlobalProperties, ExtendedAccount } from "@hiveio/dhive"; +import { type Account } from "~/schema/accounts.ts"; +import { balanceToMoney, vestsToHive } from "~/schema/utils.ts"; + +const adaptAccount = ( + account: ExtendedAccount, + params: DynamicGlobalProperties +): Account => { + const jsonMetadata = JSON.parse(account.posting_json_metadata); + + return { + id: account.id.toString(), + name: account.name, + created: new Date(account.created), + wallet: { + hive: { + liquid: balanceToMoney(account.balance as string, 3)!, + frozen: vestsToHive(account.vesting_shares as string, params) + }, + hbd: { + liquid: balanceToMoney(account.hbd_balance as string, 3)!, + frozen: balanceToMoney(account.savings_hbd_balance as string, 3)! + } + }, + postCount: account.post_count, + profile: { + name: jsonMetadata.profile.name, + about: jsonMetadata.profile.about, + coverImage: jsonMetadata.profile.cover_image, + profileImage: jsonMetadata.profile.profile_image, + website: jsonMetadata.profile.website, + location: jsonMetadata.profile.location + } + }; +}; + +export { adaptAccount }; diff --git a/src/api/accounts.ts b/src/api/accounts.ts new file mode 100644 index 0000000..d5c3739 --- /dev/null +++ b/src/api/accounts.ts @@ -0,0 +1,9 @@ +import { hive } from "~/api/common"; +import type { Account } from "~/schema/accounts.ts"; +import { adaptAccount } from "~/adapters/accounts.ts"; + +export async function getAccounts(usernames: string[]): Promise { + const params = await hive.database.getDynamicGlobalProperties(); + const accounts = await hive.database.getAccounts(usernames); + return accounts.map((acc) => adaptAccount(acc, params)); +} diff --git a/src/index.ts b/src/index.ts index 61a98f2..bed2516 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ export { hiveBlogLoader } from "~/loaders/blogLoader"; export type { Post } from "~/schema/posts.ts"; -export type { Community } from "~/schema/community.ts"; +export type { Communities } from "~/schema/communities.ts"; diff --git a/src/loaders/accountsLoader.ts b/src/loaders/accountsLoader.ts new file mode 100644 index 0000000..082d4be --- /dev/null +++ b/src/loaders/accountsLoader.ts @@ -0,0 +1,24 @@ +import type { Loader } from "astro/loaders"; +import { typeToZ } from "~/schema/utils.ts"; +import type { Account } from "~/schema/accounts.ts"; +import { getAccounts } from "~/api/accounts.ts"; + +export function hiveAccountsLoader(username: string | string[]): Loader { + return { + name: "hive-accounts-loader", + load: async function (this: Loader, { store, logger }) { + logger.debug(`Fetching accounts [usernames: ${username}]`); + const data = await getAccounts( + Array.isArray(username) ? username : [username] + ); + store.clear(); + data.forEach((account) => { + store.set({ + id: account.id.toString(), + data: { ...account } + }); + }); + }, + schema: typeToZ + }; +} diff --git a/src/schema/accounts.ts b/src/schema/accounts.ts new file mode 100644 index 0000000..71d6070 --- /dev/null +++ b/src/schema/accounts.ts @@ -0,0 +1,52 @@ +export type Account = { + id: string; + name: string; + created: Date; + wallet: Wallet; + postCount: number; + profile: Profile; +}; + +export type Profile = { + name: string; + about: string; + coverImage: string; + profileImage: string; + website: string; + location: string; +}; + +export type Wallet = { + hive: { + liquid: Money; + frozen: Money; + }; + hbd: { + liquid: Money; + frozen: Money; + }; +}; + +export interface MoneyInterface { + currency: string; + amount: string; + precision: number; + + asFloat(): number; +} + +export class Money implements MoneyInterface { + currency: string; + amount: string; + precision: number; + + constructor(currency: string, amount: string, precision: number) { + this.currency = currency; + this.amount = parseFloat(amount).toFixed(precision).toString(); + this.precision = precision; + } + + asFloat(): number { + return parseFloat(this.amount); + } +} diff --git a/src/schema/community.ts b/src/schema/communities.ts similarity index 54% rename from src/schema/community.ts rename to src/schema/communities.ts index 14a1636..8437166 100644 --- a/src/schema/community.ts +++ b/src/schema/communities.ts @@ -1,4 +1,4 @@ -export type Community = { +export type Communities = { id: string; name: string; }; diff --git a/src/schema/posts.ts b/src/schema/posts.ts index 6834ceb..bc9cf46 100644 --- a/src/schema/posts.ts +++ b/src/schema/posts.ts @@ -1,4 +1,4 @@ -import type { Community } from "~/schema/community.ts"; +import type { Communities } from "~/schema/communities.ts"; export type Post = { id: string; @@ -7,7 +7,7 @@ export type Post = { description: string; created: Date; updated?: Date; - community?: Community; + community?: Communities; category: string; tags: string[]; image?: string; diff --git a/src/schema/utils.ts b/src/schema/utils.ts index 23257a8..0dded21 100644 --- a/src/schema/utils.ts +++ b/src/schema/utils.ts @@ -1,5 +1,30 @@ import { z } from "astro/zod"; +import { Money } from "~/schema/accounts.ts"; +import type { DynamicGlobalProperties } from "@hiveio/dhive"; export function typeToZ() { return z.custom(() => true) as z.ZodType; } + +export function balanceToMoney( + balance: string, + precision: number +): Money | null { + const [amount, currency] = balance.split(" "); + if (!amount || !currency) { + throw new Error(`Invalid balance: ${balance}`); + } + return new Money(currency, amount, precision); +} + +export const vestsToHive = ( + vests: string, + params: DynamicGlobalProperties +): Money => { + const [v] = vests.split(" "); + const [totalFund] = (params.total_vesting_fund_hive as string).split(" "); + const [totalShares] = (params.total_vesting_shares as string).split(" "); + const hive = + (parseFloat(v!) * parseFloat(totalFund!)) / parseFloat(totalShares!); + return new Money("HIVE", hive.toString(), 3); +}; diff --git a/src/tests/api/accounts.test.ts b/src/tests/api/accounts.test.ts new file mode 100644 index 0000000..1fadd7b --- /dev/null +++ b/src/tests/api/accounts.test.ts @@ -0,0 +1,16 @@ +import { describe, test, expect } from "bun:test"; +import { getAccounts } from "~/api/accounts.ts"; + +describe("getAccounts", () => { + test("returns accounts", async () => { + const accounts = await getAccounts(["hive.coding", "mciszczon"]); + expect(accounts).toBeArray(); + expect(accounts.length).toEqual(2); + const acc1 = accounts[0]!; + expect(acc1.name).toEqual("hive.coding"); + const acc2 = accounts[1]!; + expect(acc2.name).toEqual("mciszczon"); + expect(acc2.wallet.hive.liquid.asFloat()).toBeNumber(); + expect(acc2.wallet.hive.frozen.asFloat()).toBeNumber(); + }); +});