Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solves inconsistencies in API return values #1929

Merged
merged 12 commits into from
Dec 13, 2023
2 changes: 1 addition & 1 deletion carbonmark-api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@klimadao/carbonmark-api",
"version": "2.0.1",
"version": "3.0.0",
"description": "An API for exploring Carbonmark project data, prices and activity.",
"main": "app.ts",
"scripts": {
Expand Down
6 changes: 3 additions & 3 deletions carbonmark-api/src/models/Listing.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
6 changes: 3 additions & 3 deletions carbonmark-api/src/models/User.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,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<typeof UserModel>;
Expand Down
2 changes: 1 addition & 1 deletion carbonmark-api/src/routes/users/get.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
13 changes: 10 additions & 3 deletions carbonmark-api/src/utils/helpers/users.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

/**
Expand Down Expand Up @@ -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;
};
Expand All @@ -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),
};
};
13 changes: 12 additions & 1 deletion carbonmark-api/src/utils/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FastifyInstance } 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 {
Category,
Expand Down Expand Up @@ -219,3 +219,14 @@ export const isMatchingCmsProject = (
{ registry, projectId }: IsMatchingCmsProjectArgs,
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,
createdAt: Number(data.createdAt),
biwano marked this conversation as resolved.
Show resolved Hide resolved
updatedAt: Number(data.updatedAt),
};
}
4 changes: 3 additions & 1 deletion carbonmark-api/src/utils/marketplace.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Listing>) =>
notNil(listing.leftToSell) &&
Expand Down Expand Up @@ -33,11 +34,12 @@ 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 || "",
Expand Down
1 change: 1 addition & 0 deletions carbonmark-api/test/fixtures/marketplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const listing = aListing({
totalAmountToSell: "100000000000000000000",
leftToSell: "100000000000000000000",
updatedAt: "1234",
createdAt: "1234",
});

const projectWithListing = aProject({
Expand Down
22 changes: 12 additions & 10 deletions carbonmark-api/test/routes/projects/get.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,16 +187,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,
biwano marked this conversation as resolved.
Show resolved Hide resolved
},
],
location: {
geometry: {
Expand Down
27 changes: 9 additions & 18 deletions carbonmark-api/test/routes/users/get.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { FastifyInstance } from "fastify";
import { omit } from "lodash";
import nock from "nock";
import {
aListing,
aUser,
} 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]", () => {
Expand Down Expand Up @@ -50,32 +54,19 @@ 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 () => {
const response = await app.inject({
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 () => {
Expand Down
13 changes: 12 additions & 1 deletion carbonmark-api/test/test.constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { omit } from "lodash";
import { UserProfile } from "../src/utils/helpers/users.utils";

export const DEV_URL = "http://localhost:3003";
Expand Down Expand Up @@ -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: [],
};
2 changes: 1 addition & 1 deletion package-lock.json

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