Skip to content

Adding a new sig ui implemented + integration with Ethan's backend functionalities #139

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

Open
wants to merge 1 commit into
base: siglead-management
Choose a base branch
from
Open
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
114 changes: 114 additions & 0 deletions src/api/functions/siglead.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {
DynamoDBClient,
QueryCommand,
ScanCommand,
} from "@aws-sdk/client-dynamodb";
import { unmarshall } from "@aws-sdk/util-dynamodb";
import { OrganizationList } from "common/orgs.js";
import {
SigDetailRecord,
SigMemberCount,
SigMemberRecord,
} from "common/types/siglead.js";
import { transformSigLeadToURI } from "common/utils.js";
import { string } from "zod";

export async function fetchMemberRecords(
sigid: string,
tableName: string,
dynamoClient: DynamoDBClient,
) {
const fetchSigMemberRecords = new QueryCommand({
TableName: tableName,
KeyConditionExpression: "#sigid = :accessVal",
ExpressionAttributeNames: {
"#sigid": "sigGroupId",
},
ExpressionAttributeValues: {
":accessVal": { S: sigid },
},
ScanIndexForward: false,
});

const result = await dynamoClient.send(fetchSigMemberRecords);

// Process the results
return (result.Items || []).map((item) => {
const unmarshalledItem = unmarshall(item);
return unmarshalledItem as SigMemberRecord;
});
}

export async function fetchSigDetail(
sigid: string,
tableName: string,
dynamoClient: DynamoDBClient,
) {
const fetchSigDetail = new QueryCommand({
TableName: tableName,
KeyConditionExpression: "#sigid = :accessVal",
ExpressionAttributeNames: {
"#sigid": "sigid",
},
ExpressionAttributeValues: {
":accessVal": { S: sigid },
},
ScanIndexForward: false,
});

const result = await dynamoClient.send(fetchSigDetail);

// Process the results
return (result.Items || [{}]).map((item) => {
const unmarshalledItem = unmarshall(item);

// Strip '#' from access field
delete unmarshalledItem.leadGroupId;
delete unmarshalledItem.memberGroupId;

return unmarshalledItem as SigDetailRecord;
})[0];
}

// select count(sigid)
// from table
// groupby sigid
export async function fetchSigCounts(
sigMemberTableName: string,
dynamoClient: DynamoDBClient,
) {
const scan = new ScanCommand({
TableName: sigMemberTableName,
ProjectionExpression: "sigGroupId",
});

const result = await dynamoClient.send(scan);

const ids2Name: Record<string, string> = {};
OrganizationList.forEach((org) => {
const sigid = transformSigLeadToURI(org);
ids2Name[sigid] = org;
});

const counts: Record<string, number> = {};
(result.Items || []).forEach((item) => {
const sigGroupId = item.sigGroupId?.S;
if (sigGroupId) {
counts[sigGroupId] = (counts[sigGroupId] || 0) + 1;
}
});

const joined: Record<string, [string, number]> = {};
Object.keys(counts).forEach((sigid) => {
joined[sigid] = [ids2Name[sigid], counts[sigid]];
});

const countsArray: SigMemberCount[] = Object.entries(joined).map(
([sigid, [signame, count]]) => ({
sigid,
signame,
count,
}),
);
return countsArray;
}
2 changes: 2 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as dotenv from "dotenv";
import iamRoutes from "./routes/iam.js";
import ticketsPlugin from "./routes/tickets.js";
import linkryRoutes from "./routes/linkry.js";
import sigleadRoutes from "./routes/siglead.js";
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
import NodeCache from "node-cache";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
Expand Down Expand Up @@ -262,6 +263,7 @@ async function init(prettyPrint: boolean = false) {
api.register(iamRoutes, { prefix: "/iam" });
api.register(ticketsPlugin, { prefix: "/tickets" });
api.register(linkryRoutes, { prefix: "/linkry" });
api.register(sigleadRoutes, { prefix: "/siglead" });
api.register(mobileWalletRoute, { prefix: "/mobileWallet" });
api.register(stripeRoutes, { prefix: "/stripe" });
api.register(roomRequestRoutes, { prefix: "/roomRequests" });
Expand Down
171 changes: 171 additions & 0 deletions src/api/routes/siglead.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { FastifyPluginAsync } from "fastify";
import { z } from "zod";
import { AppRoles } from "../../common/roles.js";
import {
BaseError,
DatabaseDeleteError,
DatabaseFetchError,
DatabaseInsertError,
NotFoundError,
UnauthenticatedError,
UnauthorizedError,
ValidationError,
} from "../../common/errors/index.js";
import { NoDataRequest } from "../types.js";
import {
DynamoDBClient,
QueryCommand,
DeleteItemCommand,
ScanCommand,
TransactWriteItemsCommand,
AttributeValue,
TransactWriteItem,
GetItemCommand,
TransactionCanceledException,
} from "@aws-sdk/client-dynamodb";
import { CloudFrontKeyValueStoreClient } from "@aws-sdk/client-cloudfront-keyvaluestore";
import {
genericConfig,
EVENT_CACHED_DURATION,
LinkryGroupUUIDToGroupNameMap,
} from "../../common/config.js";
import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
import rateLimiter from "api/plugins/rateLimiter.js";
import {
deleteKey,
getLinkryKvArn,
setKey,
} from "api/functions/cloudfrontKvStore.js";
import { zodToJsonSchema } from "zod-to-json-schema";
import {
SigDetailRecord,
SigleadGetRequest,
SigMemberCount,
SigMemberRecord,
} from "common/types/siglead.js";
import {
fetchMemberRecords,
fetchSigCounts,
fetchSigDetail,
} from "api/functions/siglead.js";
import { intersection } from "api/plugins/auth.js";

const sigleadRoutes: FastifyPluginAsync = async (fastify, _options) => {
const limitedRoutes: FastifyPluginAsync = async (fastify) => {
/*fastify.register(rateLimiter, {
limit: 30,
duration: 60,
rateLimitIdentifier: "linkry",
});*/

fastify.get<SigleadGetRequest>(
"/sigmembers/:sigid",
{
onRequest: async (request, reply) => {
/*await fastify.authorize(request, reply, [
AppRoles.LINKS_MANAGER,
AppRoles.LINKS_ADMIN,
]);*/
},
},
async (request, reply) => {
const { sigid } = request.params;
const tableName = genericConfig.SigleadDynamoSigMemberTableName;

// First try-catch: Fetch owner records
let memberRecords: SigMemberRecord[];
try {
memberRecords = await fetchMemberRecords(
sigid,
tableName,
fastify.dynamoClient,
);
} catch (error) {
request.log.error(
`Failed to fetch member records: ${error instanceof Error ? error.toString() : "Unknown error"}`,
);
throw new DatabaseFetchError({
message: "Failed to fetch member records from Dynamo table.",
});
}

// Send the response
reply.code(200).send(memberRecords);
},
);

fastify.get<SigleadGetRequest>(
"/sigdetail/:sigid",
{
onRequest: async (request, reply) => {
/*await fastify.authorize(request, reply, [
AppRoles.LINKS_MANAGER,
AppRoles.LINKS_ADMIN,
]);*/
},
},
async (request, reply) => {
const { sigid } = request.params;
const tableName = genericConfig.SigleadDynamoSigDetailTableName;

// First try-catch: Fetch owner records
let sigDetail: SigDetailRecord;
try {
sigDetail = await fetchSigDetail(
sigid,
tableName,
fastify.dynamoClient,
);
} catch (error) {
request.log.error(
`Failed to fetch sig detail record: ${error instanceof Error ? error.toString() : "Unknown error"}`,
);
throw new DatabaseFetchError({
message: "Failed to fetch sig detail record from Dynamo table.",
});
}

// Send the response
reply.code(200).send(sigDetail);
},
);

// fetch sig count
fastify.get<SigleadGetRequest>(
"/sigcount",
{
onRequest: async (request, reply) => {
/*await fastify.authorize(request, reply, [
AppRoles.LINKS_MANAGER,
AppRoles.LINKS_ADMIN,
]);*/
},
},
async (request, reply) => {
// First try-catch: Fetch owner records
let sigMemCounts: SigMemberCount[];
try {
sigMemCounts = await fetchSigCounts(
genericConfig.SigleadDynamoSigMemberTableName,
fastify.dynamoClient,
);
} catch (error) {
request.log.error(
`Failed to fetch sig member counts record: ${error instanceof Error ? error.toString() : "Unknown error"}`,
);
throw new DatabaseFetchError({
message:
"Failed to fetch sig member counts record from Dynamo table.",
});
}

// Send the response
reply.code(200).send(sigMemCounts);
},
);
};

fastify.register(limitedRoutes);
};

export default sigleadRoutes;
4 changes: 4 additions & 0 deletions src/common/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export type GenericConfigType = {
EventsDynamoTableName: string;
CacheDynamoTableName: string;
LinkryDynamoTableName: string;
SigleadDynamoSigDetailTableName: string;
SigleadDynamoSigMemberTableName: string;
StripeLinksDynamoTableName: string;
ConfigSecretName: string;
EntraSecretName: string;
Expand Down Expand Up @@ -70,6 +72,8 @@ const genericConfig: GenericConfigType = {
StripeLinksDynamoTableName: "infra-core-api-stripe-links",
CacheDynamoTableName: "infra-core-api-cache",
LinkryDynamoTableName: "infra-core-api-linkry",
SigleadDynamoSigDetailTableName: "infra-core-api-sig-details",
SigleadDynamoSigMemberTableName: "infra-core-api-sig-member-details",
ConfigSecretName: "infra-core-api-config",
EntraSecretName: "infra-core-api-entra",
EntraReadOnlySecretName: "infra-core-api-ro-entra",
Expand Down
2 changes: 1 addition & 1 deletion src/common/orgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const SIGList = [
"GameBuilders",
"SIGAIDA",
"SIGGRAPH",
"ICPC",
"SIGICPC",
"SIGMobile",
"SIGMusic",
"GLUG",
Expand Down
24 changes: 24 additions & 0 deletions src/common/types/siglead.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export type SigDetailRecord = {
sigid: string;
signame: string;
description: string;
};

export type SigMemberRecord = {
sigGroupId: string;
email: string;
designation: string;
memberName: string;
};

export type SigleadGetRequest = {
Params: { sigid: string };
Querystring: undefined;
Body: undefined;
};

export type SigMemberCount = {
sigid: string;
signame: string;
count: number;
};
Loading