Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
bin/
cache/

.vim/

# dependencies
node_modules
.pnp
Expand Down
7 changes: 7 additions & 0 deletions packages/core-firebase/src/admin/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {
erc721MintGroupPath,
erc721MintPath,
projectWarpConfigPath,
teamApiKeyGroupPath,
teamApiKeyPath,
} from "../collections.js";
import {
NetworkDataEncoded,
Expand Down Expand Up @@ -58,6 +60,7 @@ import {
NetworkId,
ProjectWarpConfigData,
} from "../models/index.js";
import { TeamApiKeyData } from "../models/TeamApiKey.js";

//users
export const userCol = getColRef<UserData>(firestore, userPath);
Expand All @@ -71,6 +74,10 @@ export const teamNetworkColGroup = getColGroupRef<NetworkDataEncoded>(firestore,
export const teamNetworkCol = (collectionId: TeamId) =>
getColRef<NetworkDataEncoded>(firestore, teamNetworkPath(collectionId));

export const teamApiKeyColGroup = getColGroupRef<TeamApiKeyData>(firestore, teamApiKeyGroupPath);
export const teamApiKeyCol = (collectionId: TeamId) =>
getColRef<TeamApiKeyData>(firestore, teamApiKeyPath(collectionId));

//project
export const projectCol = getColRef<ProjectData>(firestore, projectPath);
export const projectApiKeyColGroup = getColGroupRef<ProjectApiKeyData>(firestore, projectApiKeyGroupPath);
Expand Down
10 changes: 9 additions & 1 deletion packages/core-firebase/src/admin/controllers/team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { isUUID } from "../../utils/uuid.js";
import { Team } from "../../models/index.js";
import { teamMemberResource, teamResource } from "../resources.js";
import { getTeamsFactory } from "../../controllers/index.js";
import { teamMemberGroupQuery } from "../groupQueries.js";
import { teamApiKeyGroupQuery, teamMemberGroupQuery } from "../groupQueries.js";

export const getTeams = getTeamsFactory(teamMemberGroupQuery, teamResource);

Expand Down Expand Up @@ -47,3 +47,11 @@ export async function createTeam(team: Omit<Team, "teamId">): Promise<string> {

return teamId;
}

export async function getTeamWithApiKey(apiKey: string): Promise<Team | null> {
const teamApiKey = await teamApiKeyGroupQuery.getWhereFirst({ apiKey });
if (!teamApiKey) return null;

const team = await teamResource.getOrNull({ teamId: teamApiKey.teamId });
return team;
}
23 changes: 23 additions & 0 deletions packages/core-firebase/src/admin/groupQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
projectUserWalletSafeColGroup,
projectWalletDfnsColGroup,
projectWalletSafeColGroup,
teamApiKeyColGroup,
teamMemberColGroup,
teamNetworkColGroup,
} from "./collection.js";
Expand Down Expand Up @@ -72,6 +73,13 @@ import {
encodeTeamMemberData,
encodeTeamMemberDataPartial,
} from "../models/index.js";
import {
decodeTeamApiKeyId,
encodeTeamApiKeyData,
encodeTeamApiKeyDataPartial,
TeamApiKeyData,
TeamApiKeyId,
} from "../models/TeamApiKey.js";

//users
//Search user team membership across teams
Expand Down Expand Up @@ -102,6 +110,21 @@ export const teamNetworkGroupQuery = getFirebaseQueryResource<
decodeParentDocId: decodeTeamId,
});

export const teamApiKeyGroupQuery = getFirebaseQueryResource<
TeamApiKeyData,
TeamApiKeyId,
TeamId,
TeamApiKeyData,
TeamApiKeyData,
Query<"admin", TeamApiKeyData>
>(teamApiKeyColGroup, {
decodeId: decodeTeamApiKeyId,
encodeDataPartial: encodeTeamApiKeyDataPartial,
decodeData: encodeTeamApiKeyData,
encodeParentDocId: encodeTeamId,
decodeParentDocId: decodeTeamId,
});

//project
//Search project api key across projects
export const projectApiKeyGroupQuery = getFirebaseQueryResource<
Expand Down
16 changes: 16 additions & 0 deletions packages/core-firebase/src/admin/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
projectWalletDfnsCol,
projectWalletSafeCol,
projectWarpConfigCol,
teamApiKeyCol,
teamCol,
teamMemberCol,
teamNetworkCol,
Expand Down Expand Up @@ -130,6 +131,12 @@ import {
encodeProjectWarpConfigData,
encodeProjectWarpConfigDataPartial,
encodeProjectWarpConfigId,
TeamApiKeyData,
TeamApiKeyId,
encodeTeamApiKeyId,
decodeTeamApiKeyId,
encodeTeamApiKeyDataPartial,
encodeTeamApiKeyData,
} from "../models/index.js";

//user & team
Expand Down Expand Up @@ -169,6 +176,15 @@ export const teamNetworkResource = getFirebaseResource<
encodeParentDocId: encodeTeamId,
decodeParentDocId: decodeTeamId,
});
export const teamApiKeyResource = getFirebaseResource<TeamApiKeyData, TeamApiKeyId, TeamId>(firestore, teamApiKeyCol, {
encodeId: encodeTeamApiKeyId,
decodeId: decodeTeamApiKeyId,
encodeDataPartial: encodeTeamApiKeyDataPartial,
encodeData: encodeTeamApiKeyData,
encodeParentDocId: encodeTeamId,
decodeParentDocId: decodeTeamId,
});

//project
export const projectResource = getFirebaseResource<ProjectData, ProjectId>(firestore, projectCol, {
encodeId: encodeProjectId,
Expand Down
6 changes: 6 additions & 0 deletions packages/core-firebase/src/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@ export const teamMemberGroupPath = "teamMember";
export const teamMemberPath = (collectionId: TeamId) => {
return join(teamPath, encodeTeamId(collectionId), teamMemberGroupPath);
};
//team
export const teamNetworkGroupPath = "teamNetwork";
export const teamNetworkPath = (collectionId: TeamId) => {
return join(teamPath, encodeTeamId(collectionId), teamNetworkGroupPath);
};
export const teamApiKeyGroupPath = "teamApiKey";
export const teamApiKeyPath = (collectionId: TeamId) => {
return join(teamPath, encodeTeamId(collectionId), teamApiKeyGroupPath);
};

//project
export const projectPath = "project";
export const projectApiKeyGroupPath = "projectApiKey";
Expand Down
63 changes: 63 additions & 0 deletions packages/core-firebase/src/models/TeamApiKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { TypeOf, expectType } from "ts-expect";
import { z } from "zod";
import {
FirestoreSDK,
FirebaseQueryResource,
Query,
FirebaseResource,
FieldOverridesSchema,
} from "@owlprotocol/crud-firebase";
import { TeamId } from "./Team.js";

export interface TeamApiKeyId {
readonly apiKey: string;
}
export const teamApiKeyIdZod = z
.union([z.string(), z.object({ apiKey: z.string() })])
.transform((arg) => (typeof arg === "string" ? arg : arg.apiKey));
export const encodeTeamApiKeyId: (id: string | TeamApiKeyId) => string = teamApiKeyIdZod.parse;
export const decodeTeamApiKeyId: (id: string) => Required<TeamApiKeyId> = (id) => {
return { apiKey: id };
};

export interface TeamApiKeyData {
readonly apiKey: string;
readonly createdAt?: number;
readonly expiresAt?: number;
}

export const teamApiKeyDataZod = z
.object({
apiKey: z.string(),
createdAt: z.number().int().positive().describe("timestamp of team api key creation").optional(),
expiresAt: z.number().int().positive().describe("expiry").optional(),
})
.describe("team api key");
export const encodeTeamApiKeyData: (data: TeamApiKeyData) => TeamApiKeyData = teamApiKeyDataZod.parse;
export const encodeTeamApiKeyDataPartial: (data: Partial<TeamApiKeyData>) => Partial<TeamApiKeyData> =
teamApiKeyDataZod.partial().parse;

export type TeamApiKey = Required<TeamApiKeyId> & TeamApiKeyData;
//Generic interfaces for resource, useful for writing logic that works both in firebase admin/web
export type TeamApiKeyResource = FirebaseResource<FirestoreSDK, TeamApiKeyData, TeamApiKeyId>;
//Generic interfaces for read resource, and group read resource (for subcollections)
export type TeamApiKeyQueryResource = FirebaseQueryResource<FirestoreSDK, TeamApiKeyData, TeamApiKeyId>;
export type TeamApiKeyGroupQueryResource = FirebaseQueryResource<
FirestoreSDK,
TeamApiKeyData,
TeamApiKeyId,
TeamId,
TeamApiKeyData,
TeamApiKeyData,
Query<FirestoreSDK, TeamApiKeyData>
>;

//Check zod validator matches interface
expectType<TypeOf<TeamApiKeyData, z.input<typeof teamApiKeyDataZod>>>(true);
expectType<TypeOf<TeamApiKeyData, z.output<typeof teamApiKeyDataZod>>>(true);

export const TeamApiKeyFieldOverrides: FieldOverridesSchema<keyof TeamApiKeyData> = {
apiKey: "COLLECTION_GROUP",
expiresAt: "IGNORE",
createdAt: "IGNORE",
};
1 change: 1 addition & 0 deletions packages/core-firebase/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from "./projects/index.js";

export * from "./common.js";
export * from "./Team.js";
export * from "./TeamApiKey.js";
export * from "./TeamMember.js";
export * from "./TeamNetwork.js";
export * from "./ERC721Mint.js";
Expand Down
11 changes: 11 additions & 0 deletions packages/core-firebase/src/query/queryOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,18 @@ import {
projectWalletDfnsGroupPath,
projectWalletSafeGroupPath,
projectWarpConfigGroupPath,
teamApiKeyGroupPath,
} from "../collections.js";
import {
projectApiKeyGroupQuery,
projectContractGroupQuery,
projectUserGroupQuery,
projectUserWalletDfnsGroupQuery,
projectUserWalletSafeGroupQuery,
teamApiKeyGroupQuery,
teamMemberGroupQuery,
} from "../web/groupQueries.js";
import { TeamApiKeyData, TeamApiKeyId } from "../models/TeamApiKey.js";

/*** Collection Queries ***/
//TODO: REQUIRED Replace prefixPath/collectionGroup with computed => need getWhere to support getColPath()
Expand Down Expand Up @@ -215,6 +218,14 @@ export const teamMemberGroupQueryOptions = getFirebaseQueryReactQueryOptions<
TeamMemberData,
Query<"web", TeamMemberData>
>(teamMemberGroupQuery, { prefixPath: [], collectionGroup: teamMemberGroupPath });
export const teamApiKeyGroupQueryOptions = getFirebaseQueryReactQueryOptions<
TeamApiKeyData,
TeamApiKeyId,
TeamId,
TeamApiKeyData,
TeamApiKeyData,
Query<"web", TeamApiKeyData>
>(teamApiKeyGroupQuery, { prefixPath: [], collectionGroup: teamApiKeyGroupPath });

//project
export const projectApiKeyGroupQueryOptions = getFirebaseQueryReactQueryOptions<
Expand Down
18 changes: 18 additions & 0 deletions packages/core-firebase/src/scripts/createTeamApiKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { v4 as uuidv4 } from "uuid";
import { teamApiKeyResource } from "../admin/resources.js";

export async function createTeamApiKey() {
if (process.argv.length != 3) throw new Error("Usage: node createTeamApiKey.js <teamId>");
const teamId = process.argv[2];

const apiKey = uuidv4();

const numTeamApiKeys = await teamApiKeyResource.getWhereCount({ teamId });

if (numTeamApiKeys > 0) {
throw new Error("Team already has an API key");
}
await teamApiKeyResource.set({ teamId, apiKey, createdAt: Date.now() });
}

createTeamApiKey().then(() => console.log("Done"));
7 changes: 7 additions & 0 deletions packages/core-firebase/src/web/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
erc721MintGroupPath,
erc721MintPath,
projectWarpConfigPath,
teamApiKeyGroupPath,
teamApiKeyPath,
} from "../collections.js";
import {
ERC721MintData,
Expand All @@ -55,6 +57,7 @@ import {
TeamMemberData,
UserData,
} from "../models/index.js";
import { TeamApiKeyData } from "../models/TeamApiKey.js";

//users
export const userCol = getColRef<UserData>(firestore, userPath);
Expand All @@ -68,6 +71,10 @@ export const teamNetworkColGroup = getColGroupRef<NetworkDataEncoded>(firestore,
export const teamNetworkCol = (collectionId: TeamId) =>
getColRef<NetworkDataEncoded>(firestore, teamNetworkPath(collectionId));

export const teamApiKeyColGroup = getColGroupRef<TeamApiKeyData>(firestore, teamApiKeyGroupPath);
export const teamApiKeyCol = (collectionId: TeamId) =>
getColRef<TeamApiKeyData>(firestore, teamApiKeyPath(collectionId));

//project
export const projectCol = getColRef<ProjectData>(firestore, projectPath);
export const projectApiKeyColGroup = getColGroupRef<ProjectApiKeyData>(firestore, projectApiKeyGroupPath);
Expand Down
23 changes: 23 additions & 0 deletions packages/core-firebase/src/web/groupQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
projectUserWalletSafeColGroup,
projectWalletDfnsColGroup,
projectWalletSafeColGroup,
teamApiKeyColGroup,
teamMemberColGroup,
teamNetworkColGroup,
} from "./collection.js";
Expand Down Expand Up @@ -71,6 +72,13 @@ import {
encodeTeamMemberData,
encodeTeamMemberDataPartial,
} from "../models/index.js";
import {
decodeTeamApiKeyId,
encodeTeamApiKeyData,
encodeTeamApiKeyDataPartial,
TeamApiKeyData,
TeamApiKeyId,
} from "../models/TeamApiKey.js";

//users
//Search user team membership across teams
Expand Down Expand Up @@ -102,6 +110,21 @@ export const teamNetworkGroupQuery = getFirebaseQueryResource<
decodeParentDocId: decodeTeamId,
});

export const teamApiKeyGroupQuery = getFirebaseQueryResource<
TeamApiKeyData,
TeamApiKeyId,
TeamId,
TeamApiKeyData,
TeamApiKeyData,
Query<"web", TeamApiKeyData>
>(teamApiKeyColGroup, {
decodeId: decodeTeamApiKeyId,
encodeDataPartial: encodeTeamApiKeyDataPartial,
decodeData: encodeTeamApiKeyData,
encodeParentDocId: encodeTeamId,
decodeParentDocId: decodeTeamId,
});

//project
//Search project api key across projects
export const projectApiKeyGroupQuery = getFirebaseQueryResource<
Expand Down
18 changes: 18 additions & 0 deletions packages/core-firebase/src/web/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
projectWalletDfnsCol,
projectWalletSafeCol,
projectWarpConfigCol,
teamApiKeyCol,
teamCol,
teamMemberCol,
teamNetworkCol,
Expand Down Expand Up @@ -124,6 +125,14 @@ import {
encodeProjectWarpConfigDataPartial,
encodeProjectWarpConfigId,
} from "../models/index.js";
import {
decodeTeamApiKeyId,
encodeTeamApiKeyData,
encodeTeamApiKeyDataPartial,
encodeTeamApiKeyId,
TeamApiKeyData,
TeamApiKeyId,
} from "../models/TeamApiKey.js";

//user & team
export const userResource = getFirebaseResource<UserData, UserId>(firestore, userCol, {
Expand Down Expand Up @@ -162,6 +171,15 @@ export const teamNetworkResource = getFirebaseResource<
encodeParentDocId: encodeTeamId,
decodeParentDocId: decodeTeamId,
});
export const teamApiKeyResource = getFirebaseResource<TeamApiKeyData, TeamApiKeyId, TeamId>(firestore, teamApiKeyCol, {
encodeId: encodeTeamApiKeyId,
decodeId: decodeTeamApiKeyId,
encodeDataPartial: encodeTeamApiKeyDataPartial,
encodeData: encodeTeamApiKeyData,
encodeParentDocId: encodeTeamId,
decodeParentDocId: decodeTeamId,
});

//project
export const projectResource = getFirebaseResource<ProjectData, ProjectId>(firestore, projectCol, {
encodeId: encodeProjectId,
Expand Down
Loading