Skip to content

Commit

Permalink
feat: add v1 profiles (#7)
Browse files Browse the repository at this point in the history
* feat: from & to as Avatar types

* feat: remove unused file

* feat: fetch v1 garden profiles

* feat: refactor load v1 garden profiles

* feat: cache.db with v1 profiles

* docs: update with from to updated

* feat: refactor to use string instead of avatar
  • Loading branch information
vibern0 authored Dec 3, 2024
1 parent df17476 commit 8f1f7ca
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 68 deletions.
Binary file added .cache/cache.db
Binary file not shown.
17 changes: 10 additions & 7 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ A unified view of all transfer transactions within the system, consolidating bot
- `transactionHash`
- `version`
- `operator` (v2 only)
- `from`, `to`
- `from`
- `to`
- `id`
- `value`
- `type` (transaction type; e.g., `Erc20WrapperTransfer`, `TransferSingle`, etc.)
Expand All @@ -26,16 +27,16 @@ It's possible to get demurrages that happened during transfers or mints.
query getUserTransfers($address: String) {
Transfer(
where:{
to:{_eq:$address},
transferType:{_neq:"Demurrage"}
to: {_eq:$address},
transferType: {_neq:"Demurrage"}
}
order_by:{timestamp: desc}
order_by: {timestamp: desc}
) {
id
from
from { id }
to { id }
transferType
timestamp
to
value
demurrageFrom {
id
Expand Down Expand Up @@ -215,8 +216,10 @@ Defines the type of avatars.
- `Invite`: v2 user that is not yet on circles and has at least one invite.
- `RegisterGroup`: v2 group.
- `RegisterOrganization`: v2 organization.
- `Unknown`: Placeholder during processing; unlikely to occur in steady state.
- `Migrating`: v1 user that is migrating to v2.
- `Unknown`: Pending state.

The `Unknown` pending state happens when a user created a v2 profile, but did not yet receive an invite or joined circles. Once the user receives de first invite, then the avatar type is `Invite`.

Suppose you want to get the list of invited users by a given user.

Expand Down
39 changes: 5 additions & 34 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { Profile as Metadata } from "./types";
const db = new sqlite3.Database(".cache/cache.db");

export class ProfileCache {
static async init() {
const cache = new ProfileCache("cache_v2");
static async init(version: number) {
const cache = new ProfileCache("cache_cv" + version);
await cache.createTableIfNotExists();
return cache;
}
Expand Down Expand Up @@ -37,7 +37,7 @@ export class ProfileCache {
});
}

public read(id: string): Promise<{ cidV0: string; data: Metadata } | null> {
public read(id: string): Promise<{ cidV0: string | null; data: Metadata } | null> {
return new Promise((resolve, reject) => {
const query = `SELECT data, cidV0 FROM ${this.key} WHERE id = ?`;
db.get(query, [id], (err, row: any) => {
Expand All @@ -53,7 +53,7 @@ export class ProfileCache {
});
}

public async add(id: string, cidV0: string, metadata: Metadata) {
public async add(id: string, cidV0: string | null, metadata: Metadata) {
const query = `INSERT INTO ${this.key} (id, cidV0, data) VALUES (?, ?, ?)`;
const data = JSON.stringify(metadata);

Expand Down
6 changes: 0 additions & 6 deletions src/constants.ts

This file was deleted.

21 changes: 21 additions & 0 deletions src/event_handlers/hubV1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { maxUint256 } from "viem";
import { incrementStats } from "../incrementStats";
import { handleTransfer } from "../common/handleTransfer";
import { defaultAvatarProps, makeAvatarBalanceEntityId } from "../utils";
import { getProfileMetadataFromGardenApi } from "../gardenApi";

// ###############
// #### TOKEN ####
Expand Down Expand Up @@ -43,6 +44,22 @@ Hub.Signup.handlerWithLoader({
handler: async ({ event, context, loaderReturn }) => {
const { avatarBalance } = loaderReturn;

const profileFromGarden = await getProfileMetadataFromGardenApi(
event.params.user
);

if (profileFromGarden && profileFromGarden.data) {
const { data } = profileFromGarden;
context.Profile.set({
id: event.params.user,
description: undefined,
previewImageUrl: data?.previewImageUrl,
imageUrl: undefined,
name: data?.name,
symbol: undefined,
});
}

context.Avatar.set({
...defaultAvatarProps(event),
version: 1,
Expand Down Expand Up @@ -152,6 +169,10 @@ Hub.Trust.handlerWithLoader({
handler: async ({ event, context, loaderReturn }) => {
const { trustId, trustRelation, oppositeTrustRelation } = loaderReturn;

if (event.params.user === event.params.canSendTo) {
return;
}

if (event.params.limit === 0n) {
// this is untrust
if (trustRelation && trustRelation.version === 1) {
Expand Down
10 changes: 4 additions & 6 deletions src/event_handlers/hubV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,12 @@ HubV2.PersonalMint.handlerWithLoader({
});

NameRegistry.UpdateMetadataDigest.handler(async ({ event, context }) => {
let profileMetadata: { cidV0: string; data: Profile | null } | null = null;
let profileMetadata: { cidV0: string | null; data: Profile | null } | null = null;
try {
profileMetadata = await getProfileMetadataFromIpfs(
event.params.metadataDigest
);
} catch (_) {
console.log("Error in nameReg fetching Ipfs", _);
}
} catch (_) {}

const avatar = await context.Avatar.get(event.params.avatar);

Expand All @@ -228,7 +226,7 @@ NameRegistry.UpdateMetadataDigest.handler(async ({ event, context }) => {
id: event.params.avatar,
avatarType: "Unknown",
tokenId: bytesToBigInt(toBytes(event.params.avatar)).toString(),
cidV0: profileMetadata?.cidV0,
cidV0: profileMetadata?.cidV0 ?? undefined,
profile_id: event.params.avatar,
});
} else {
Expand All @@ -239,7 +237,7 @@ NameRegistry.UpdateMetadataDigest.handler(async ({ event, context }) => {
context.Avatar.set({
...avatar,
avatarType: transitiveType,
cidV0: profileMetadata?.cidV0,
cidV0: profileMetadata?.cidV0 ?? undefined,
});
}

Expand Down
76 changes: 76 additions & 0 deletions src/gardenApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Profile } from "./types";
import { ProfileCache } from "./cache";

type GardenApiResponse = {
status: string;
data: {
id: number;
username: string;
avatarUrl: string | undefined;
}[];
};
type GardenProfile = {
name: string;
previewImageUrl: string | undefined;
};
/**
* This functions calls https://api.circles.garden/api/users?address[]=${address}
* which returns a GardenApiResponse object.
* GardenApiResponse contains an avatarUrl, which is also fetched and it's result is converted into a base 64 image.
* @param {string} address - The address of the user.
* @returns {Promise<{ data: GardenProfile | undefined; timeTaken: number }>} - The GardenProfile object and the time taken to fetch it.
*/
async function fetchGardenProfile(
address: string
): Promise<{ data: GardenProfile | undefined; timeTaken: number }> {
const startTime = Date.now();

try {
const response = await fetch(
`https://api.circles.garden/api/users?address[]=${address}`
);
const json = (await response.json()) as GardenApiResponse;

if (json.status !== "ok" || json.data.length === 0) {
return { data: undefined, timeTaken: Date.now() - startTime };
}

const user = json.data[0];

return {
data: {
name: user.username,
previewImageUrl: user.avatarUrl,
},
timeTaken: Date.now() - startTime,
};
} catch (error) {
console.error("Error fetching garden profile:", error);
return { data: undefined, timeTaken: Date.now() - startTime };
}
}

export async function getProfileMetadataFromGardenApi(
address: string
): Promise<{ data: Profile | null } | null> {
if (!address) {
return null;
}

const cache = await ProfileCache.init(1);
const cacheResult = await cache.read(address);

if (cacheResult) {
return { data: cacheResult.data };
}

const { data } = await fetchGardenProfile(address);
if (!data) {
return null;
}

// v1 did not had cidV0
await cache.add(address, null, data);

return { data };
}
16 changes: 6 additions & 10 deletions src/ipfs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import multihash from "multihashes";
import { Profile } from "./types";
/* import { ProfileCache } from "./cache";
*/ import { Avatar, eventLog } from "generated";
import { ProfileCache } from "./cache";
import { uint8ArrayToCidV0 } from "./utils";

// Simple config with only needed values
Expand Down Expand Up @@ -74,30 +72,28 @@ async function fetchFromEndpoint(

export async function getProfileMetadataFromIpfs(
metadataDigest: string
): Promise<{ cidV0: string; data: Profile | null } | null> {
): Promise<{ cidV0: string | null; data: Profile | null } | null> {
if (!metadataDigest) {
return null;
}
const slicedDigest = metadataDigest.slice(2, metadataDigest.length);

/* const cache = await ProfileCache.init();
const cache = await ProfileCache.init(2);
const cacheResult = await cache.read(slicedDigest);

if (cacheResult) {
return cacheResult;
} */
}

const cidV0 = uint8ArrayToCidV0(
Uint8Array.from(Buffer.from(slicedDigest, "hex"))
);

// Try each endpoint until we get a successful response
for (const endpoint of IPFS_ENDPOINTS) {
const { data, timeTaken } = await fetchFromEndpoint(endpoint, cidV0);
console.log(`IPFS fetch from ${endpoint} took ${timeTaken.toFixed(2)}ms`);
const { data } = await fetchFromEndpoint(endpoint, cidV0);
if (data) {
console.log("Adding IPFS data to cache", data);
//await cache.add(slicedDigest, cidV0, data);
await cache.add(slicedDigest, cidV0, data);
return { cidV0, data };
}
}
Expand Down
1 change: 0 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import multihash from "multihashes";
import { Profile } from "./types";
import { Avatar, eventLog } from "generated";

/**
Expand Down

0 comments on commit 8f1f7ca

Please sign in to comment.