diff --git a/carbonmark-api/package.json b/carbonmark-api/package.json index ced57c9114..0f5ba8ed1d 100644 --- a/carbonmark-api/package.json +++ b/carbonmark-api/package.json @@ -1,6 +1,6 @@ { "name": "@klimadao/carbonmark-api", - "version": "3.0.0", + "version": "4.0.0", "description": "An API for exploring Carbonmark project data, prices and activity.", "main": "app.ts", "scripts": { diff --git a/carbonmark-api/src/models/Listing.model.ts b/carbonmark-api/src/models/Listing.model.ts index f8adf7eca0..7409301cc1 100644 --- a/carbonmark-api/src/models/Listing.model.ts +++ b/carbonmark-api/src/models/Listing.model.ts @@ -30,10 +30,10 @@ export const ListingModel = Type.Object( deleted: Type.Optional(Type.Union([Type.Boolean(), Type.Null()])), batches: Nullable(Type.Array(Type.String())), batchPrices: Nullable(Type.Array(Type.String())), - createdAt: Type.Optional(Type.Union([Type.String(), Type.Null()])), - updatedAt: Type.Optional(Type.Union([Type.String(), Type.Null()])), + createdAt: Type.Optional(Type.Union([Type.Number(), Type.Null()])), + updatedAt: Type.Optional(Type.Union([Type.Number(), Type.Null()])), seller: ListingSeller, - expiration: Type.String({ + expiration: Type.Number({ description: "Unix Timestamp (seconds) when the listing expires.", }), minFillAmount: Type.String({ diff --git a/carbonmark-api/src/models/User.model.ts b/carbonmark-api/src/models/User.model.ts index 672567858b..9bcabbeac5 100644 --- a/carbonmark-api/src/models/User.model.ts +++ b/carbonmark-api/src/models/User.model.ts @@ -14,9 +14,9 @@ export const UserModel = Type.Object({ updatedAt: Type.Number(), createdAt: Type.Number(), wallet: Type.String(), - listings: Type.Array(ListingModel), - activities: Type.Array(ActivityModel), - assets: Type.Array(AssetModel), + listings: Type.Optional(Type.Array(ListingModel)), + activities: Type.Optional(Type.Array(ActivityModel)), + assets: Type.Optional(Type.Array(AssetModel)), }); export type User = Static; diff --git a/carbonmark-api/src/routes/users/get.ts b/carbonmark-api/src/routes/users/get.ts index baada421c3..3aaf1294ff 100644 --- a/carbonmark-api/src/routes/users/get.ts +++ b/carbonmark-api/src/routes/users/get.ts @@ -1,7 +1,7 @@ import { utils } from "ethers"; import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; +import { User } from "src/models/User.model"; import { Activity } from "../../models/Activity.model"; -import { User } from "../../models/User.model"; import { getProfileByAddress, getProfileByHandle, diff --git a/carbonmark-api/src/utils/helpers/users.utils.ts b/carbonmark-api/src/utils/helpers/users.utils.ts index cbe182e182..2e54d814e4 100644 --- a/carbonmark-api/src/utils/helpers/users.utils.ts +++ b/carbonmark-api/src/utils/helpers/users.utils.ts @@ -26,7 +26,7 @@ export const getProfileByAddress = async (params: { if (!doc.exists) return null; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known type - return doc.data() as UserProfile; + return formatProfile(doc.data() as UserProfile); }; /** @@ -56,7 +56,7 @@ export const getUserProfilesByIds = async (params: { if (!d.exists) return; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known const profile = d.data() as UserProfile; - UserProfileMap.set(profile.address, profile); + UserProfileMap.set(profile.address, formatProfile(profile)); }); return UserProfileMap; }; @@ -78,5 +78,12 @@ export const getProfileByHandle = async (params: { if (snapshot.empty) return null; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- apply known type const profile = snapshot.docs.at(0)?.data() as UserProfile | undefined; - return profile || null; + return profile ? formatProfile(profile) : null; +}; +const formatProfile = (profile: UserProfile): UserProfile => { + return { + ...profile, + createdAt: Math.floor(profile.createdAt / 1000), + updatedAt: Math.floor(profile.updatedAt / 1000), + }; }; diff --git a/carbonmark-api/src/utils/helpers/utils.ts b/carbonmark-api/src/utils/helpers/utils.ts index e1870b04b3..501081702f 100644 --- a/carbonmark-api/src/utils/helpers/utils.ts +++ b/carbonmark-api/src/utils/helpers/utils.ts @@ -1,5 +1,5 @@ import { FastifyInstance, FastifyReply } from "fastify"; -import { compact, concat, isArray } from "lodash"; +import { compact, concat, isArray, omit } from "lodash"; import { filter, flatten, map, pipe, split, trim, uniq } from "lodash/fp"; import { ActivityType, @@ -230,6 +230,17 @@ export const isMatchingCmsProject = ( project: CarbonProject ) => project?.registryProjectId === projectId && project.registry === registry; +export function formatGraphTimestamps< + T extends { createdAt: string | null; updatedAt: string | null }, +>(data: T) { + const partialData = omit(data, ["createdAt", "updatedAt"]); + return { + ...partialData, + /** Note: Graph timestamps are in seconds **/ + createdAt: Number(data.createdAt), + updatedAt: Number(data.updatedAt), + }; +} /** * Converts a String into an ActivityType without breaking typescript checks */ diff --git a/carbonmark-api/src/utils/marketplace.utils.ts b/carbonmark-api/src/utils/marketplace.utils.ts index 33a005d168..27e687c572 100644 --- a/carbonmark-api/src/utils/marketplace.utils.ts +++ b/carbonmark-api/src/utils/marketplace.utils.ts @@ -5,6 +5,7 @@ import { } from "../.generated/types/marketplace.types"; import { Listing as ListingModel } from "../models/Listing.model"; import { notNil } from "./functional.utils"; +import { formatGraphTimestamps } from "./helpers/utils"; export const isListingActive = (listing: Partial) => notNil(listing.leftToSell) && @@ -33,11 +34,12 @@ export type GetProjectListing = NonNullable< /** Formats a gql.marketplace listing to match Listing.model, and formats integers */ export const formatListing = (listing: GetProjectListing): ListingModel => { return { - ...listing, + ...formatGraphTimestamps(listing), leftToSell: utils.formatUnits(listing.leftToSell, 18), singleUnitPrice: utils.formatUnits(listing.singleUnitPrice, 6), minFillAmount: utils.formatUnits(listing.minFillAmount, 18), totalAmountToSell: utils.formatUnits(listing.totalAmountToSell, 18), + expiration: Number(listing.expiration), project: { ...listing.project, category: listing.project.category?.id || "", diff --git a/carbonmark-api/test/fixtures/marketplace.ts b/carbonmark-api/test/fixtures/marketplace.ts index b8d678b66c..c117d3c80c 100644 --- a/carbonmark-api/test/fixtures/marketplace.ts +++ b/carbonmark-api/test/fixtures/marketplace.ts @@ -14,6 +14,7 @@ const listing = aListing({ totalAmountToSell: "100000000000000000000", leftToSell: "100000000000000000000", updatedAt: "1234", + createdAt: "1234", }); const projectWithListing = aProject({ diff --git a/carbonmark-api/test/routes/projects/get.test.ts b/carbonmark-api/test/routes/projects/get.test.ts index b5b32ae2d0..b6b94af9fa 100644 --- a/carbonmark-api/test/routes/projects/get.test.ts +++ b/carbonmark-api/test/routes/projects/get.test.ts @@ -201,16 +201,18 @@ describe("GET /projects", () => { price: "99", updatedAt: marketplace.projectWithListing.listings?.[0].updatedAt, listings: [ - pick(marketplace.projectWithListing.listings![0], [ - "active", - "batchPrices", - "batches", - "createdAt", - "deleted", - "updatedAt", - "id", - "tokenAddress", - ]), + { + ...pick(marketplace.projectWithListing.listings![0], [ + "active", + "batchPrices", + "batches", + "deleted", + "id", + "tokenAddress", + ]), + createdAt: 1234, + updatedAt: 1234, + }, ], location: { geometry: { diff --git a/carbonmark-api/test/routes/users/get.test.ts b/carbonmark-api/test/routes/users/get.test.ts index 1c216fb2c9..a81dcc8bce 100644 --- a/carbonmark-api/test/routes/users/get.test.ts +++ b/carbonmark-api/test/routes/users/get.test.ts @@ -1,5 +1,4 @@ import { FastifyInstance } from "fastify"; -import { omit } from "lodash"; import nock from "nock"; import { aListing, @@ -7,7 +6,12 @@ import { } from "../../../src/.generated/mocks/marketplace.mocks"; import { GRAPH_URLS } from "../../../src/app.constants"; import { build } from "../../helper"; -import { DEV_URL, MOCK_ADDRESS, MOCK_USER_PROFILE } from "../../test.constants"; +import { + DEV_URL, + EXPECTED_USER_RESPONSE, + MOCK_ADDRESS, + MOCK_USER_PROFILE, +} from "../../test.constants"; import { disableAuth, mockFirebase } from "../../test.utils"; describe("GET /users/[walletOrHandle]", () => { @@ -50,15 +54,8 @@ describe("GET /users/[walletOrHandle]", () => { }); const actual_response = await response.json(); - const expected_response = { - ...omit(MOCK_USER_PROFILE, "address"), - wallet: MOCK_USER_PROFILE.address, - listings: [], - activities: [], - assets: [], - }; expect(response.statusCode).toBe(200); - expect(expected_response).toEqual(actual_response); + expect(EXPECTED_USER_RESPONSE).toEqual(actual_response); }); test("by handle", async () => { @@ -66,16 +63,10 @@ describe("GET /users/[walletOrHandle]", () => { method: "GET", url: `${DEV_URL}/users/${MOCK_USER_PROFILE.handle}`, // use handle instead of wallet address }); - const expected_response = { - ...omit(MOCK_USER_PROFILE, "address"), - wallet: MOCK_USER_PROFILE.address, - listings: [], - activities: [], - assets: [], - }; + const actual_response = await response?.json(); expect(response?.statusCode).toBe(200); - expect(actual_response).toEqual(expected_response); // check if the returned handle is correct + expect(actual_response).toEqual(EXPECTED_USER_RESPONSE); // check if the returned handle is correct }); test("with invalid handle", async () => { diff --git a/carbonmark-api/test/test.constants.ts b/carbonmark-api/test/test.constants.ts index 357bdb6171..667742e0cf 100644 --- a/carbonmark-api/test/test.constants.ts +++ b/carbonmark-api/test/test.constants.ts @@ -1,3 +1,4 @@ +import { omit } from "lodash"; import { UserProfile } from "../src/utils/helpers/users.utils"; export const DEV_URL = "http://localhost:3003"; @@ -38,7 +39,17 @@ export const MOCK_USER_PROFILE: UserProfile = { createdAt: new Date("1970-01-01T00:00:00Z").getTime(), description: "Some description", handle: "SomeHandle", - updatedAt: Number(new Date()), + updatedAt: new Date("2023-11-11T15:05:08Z").getTime(), username: "someusername", profileImgUrl: null, }; + +export const EXPECTED_USER_RESPONSE = { + ...omit(MOCK_USER_PROFILE, ["address", "updatedAt", "createdAt"]), + updatedAt: 1699715108, + createdAt: 0, + wallet: MOCK_USER_PROFILE.address, + listings: [], + activities: [], + assets: [], +}; diff --git a/package-lock.json b/package-lock.json index 25f6e6c0ad..ab86246a09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -589,7 +589,7 @@ }, "carbonmark-api": { "name": "@klimadao/carbonmark-api", - "version": "3.0.0", + "version": "4.0.0", "license": "ISC", "dependencies": { "@fastify/autoload": "^5.0.0",