Skip to content

Commit 22bf9dd

Browse files
authored
Eeen17/siglead mainscreen api (#152)
merging to deploy to qa for testing azure api calls (p2)
1 parent c7873c8 commit 22bf9dd

File tree

16 files changed

+471
-123
lines changed

16 files changed

+471
-123
lines changed

cloudformation/iam.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ Resources:
9999
Resource:
100100
- Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-cache
101101

102+
- Sid: DynamoDBRateLimitTableAccess
103+
Effect: Allow
104+
Action:
105+
- dynamodb:DescribeTable
106+
- dynamodb:UpdateItem
107+
Resource:
108+
- Fn::Sub: arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/infra-core-api-rate-limiter
109+
102110
- Sid: DynamoDBAuditLogTableAccess
103111
Effect: Allow
104112
Action:

cloudformation/main.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,30 @@ Resources:
408408
- AttributeName: userEmail
409409
KeyType: HASH
410410

411+
RateLimiterTable:
412+
Type: "AWS::DynamoDB::Table"
413+
DeletionPolicy: "Delete"
414+
UpdateReplacePolicy: "Delete"
415+
Properties:
416+
BillingMode: "PAY_PER_REQUEST"
417+
TableName: infra-core-api-rate-limiter
418+
DeletionProtectionEnabled: true
419+
PointInTimeRecoverySpecification:
420+
PointInTimeRecoveryEnabled: false
421+
AttributeDefinitions:
422+
- AttributeName: PK
423+
AttributeType: S
424+
- AttributeName: SK
425+
AttributeType: S
426+
KeySchema:
427+
- AttributeName: PK
428+
KeyType: HASH
429+
- AttributeName: SK
430+
KeyType: RANGE
431+
TimeToLiveSpecification:
432+
AttributeName: ttl
433+
Enabled: true
434+
411435
EventRecordsTable:
412436
Type: "AWS::DynamoDB::Table"
413437
DeletionPolicy: "Retain"

src/api/functions/entraId.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
2929
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
3030
import { checkPaidMembershipFromTable } from "./membership.js";
3131

32-
function validateGroupId(groupId: string): boolean {
32+
export function validateGroupId(groupId: string): boolean {
3333
const groupIdPattern = /^[a-zA-Z0-9-]+$/; // Adjust the pattern as needed
3434
return groupIdPattern.test(groupId);
3535
}
@@ -368,7 +368,7 @@ export async function listGroupMembers(
368368
* @throws {EntraUserError} If fetching the user profile fails.
369369
* @returns {Promise<UserProfileData>} The user's profile information.
370370
*/
371-
export async function getUserProfile(
371+
export async function getUserProflile(
372372
token: string,
373373
email: string,
374374
): Promise<UserProfileData> {

src/api/functions/siglead.ts

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import {
2+
AttributeValue,
23
DynamoDBClient,
4+
GetItemCommand,
5+
PutItemCommand,
6+
PutItemCommandInput,
37
QueryCommand,
48
ScanCommand,
59
} from "@aws-sdk/client-dynamodb";
610
import { unmarshall } from "@aws-sdk/util-dynamodb";
7-
import { OrganizationList } from "common/orgs.js";
11+
import { DatabaseInsertError } from "common/errors/index.js";
12+
import { OrganizationList, orgIds2Name } from "common/orgs.js";
813
import {
914
SigDetailRecord,
1015
SigMemberCount,
1116
SigMemberRecord,
17+
SigMemberUpdateRecord,
1218
} from "common/types/siglead.js";
1319
import { transformSigLeadToURI } from "common/utils.js";
20+
import { KeyObject } from "crypto";
1421
import { string } from "zod";
1522

1623
export async function fetchMemberRecords(
@@ -84,31 +91,82 @@ export async function fetchSigCounts(
8491

8592
const result = await dynamoClient.send(scan);
8693

87-
const ids2Name: Record<string, string> = {};
88-
OrganizationList.forEach((org) => {
89-
const sigid = transformSigLeadToURI(org);
90-
ids2Name[sigid] = org;
91-
});
92-
9394
const counts: Record<string, number> = {};
95+
// Object.entries(orgIds2Name).forEach(([id, _]) => {
96+
// counts[id] = 0;
97+
// });
98+
9499
(result.Items || []).forEach((item) => {
95100
const sigGroupId = item.sigGroupId?.S;
96101
if (sigGroupId) {
97102
counts[sigGroupId] = (counts[sigGroupId] || 0) + 1;
98103
}
99104
});
100105

101-
const joined: Record<string, [string, number]> = {};
102-
Object.keys(counts).forEach((sigid) => {
103-
joined[sigid] = [ids2Name[sigid], counts[sigid]];
104-
});
105-
106-
const countsArray: SigMemberCount[] = Object.entries(joined).map(
107-
([sigid, [signame, count]]) => ({
108-
sigid,
109-
signame,
106+
const countsArray: SigMemberCount[] = Object.entries(counts).map(
107+
([id, count]) => ({
108+
sigid: id,
109+
signame: orgIds2Name[id],
110110
count,
111111
}),
112112
);
113+
console.log(countsArray);
113114
return countsArray;
114115
}
116+
117+
export async function addMemberToSigDynamo(
118+
sigMemberTableName: string,
119+
sigMemberUpdateRequest: SigMemberUpdateRecord,
120+
dynamoClient: DynamoDBClient,
121+
) {
122+
const item: Record<string, AttributeValue> = {};
123+
Object.entries(sigMemberUpdateRequest).forEach(([k, v]) => {
124+
item[k] = { S: v };
125+
});
126+
127+
// put into table
128+
const put = new PutItemCommand({
129+
Item: item,
130+
ReturnConsumedCapacity: "TOTAL",
131+
TableName: sigMemberTableName,
132+
});
133+
try {
134+
const response = await dynamoClient.send(put);
135+
console.log(response);
136+
} catch (e) {
137+
console.error("Put to dynamo db went wrong.");
138+
throw e;
139+
}
140+
141+
// fetch from db and check if fetched item update time = input item update time
142+
const validatePutQuery = new GetItemCommand({
143+
TableName: sigMemberTableName,
144+
Key: {
145+
sigGroupId: { S: sigMemberUpdateRequest.sigGroupId },
146+
email: { S: sigMemberUpdateRequest.email },
147+
},
148+
ProjectionExpression: "updatedAt",
149+
});
150+
151+
try {
152+
const response = await dynamoClient.send(validatePutQuery);
153+
const item = response.Item;
154+
155+
if (!item || !item.updatedAt?.S) {
156+
throw new Error("Item not found or missing 'updatedAt'");
157+
}
158+
159+
if (item.updatedAt.S !== sigMemberUpdateRequest.updatedAt) {
160+
throw new DatabaseInsertError({
161+
message: "The member exists, but was updated by someone else!",
162+
});
163+
}
164+
} catch (e) {
165+
console.error("Validate DynamoDB get went wrong.", e);
166+
throw e;
167+
}
168+
}
169+
170+
export async function addMemberToSigEntra() {
171+
// uuid validation not implemented yet
172+
}

src/api/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,9 @@ async function init(prettyPrint: boolean = false) {
288288
api.register(iamRoutes, { prefix: "/iam" });
289289
api.register(ticketsPlugin, { prefix: "/tickets" });
290290
api.register(linkryRoutes, { prefix: "/linkry" });
291-
api.register(sigleadRoutes, { prefix: "/siglead" });
292291
api.register(mobileWalletRoute, { prefix: "/mobileWallet" });
293292
api.register(stripeRoutes, { prefix: "/stripe" });
293+
api.register(sigleadRoutes, { prefix: "/siglead" });
294294
api.register(roomRequestRoutes, { prefix: "/roomRequests" });
295295
api.register(logsPlugin, { prefix: "/logs" });
296296
api.register(apiKeyRoute, { prefix: "/apiKey" });

src/api/routes/siglead.ts

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import { FastifyPluginAsync } from "fastify";
22
import { DatabaseFetchError } from "../../common/errors/index.js";
3+
34
import { genericConfig } from "../../common/config.js";
5+
46
import {
57
SigDetailRecord,
68
SigleadGetRequest,
79
SigMemberCount,
810
SigMemberRecord,
11+
SigMemberUpdateRecord,
912
} from "common/types/siglead.js";
1013
import {
14+
addMemberToSigDynamo,
1115
fetchMemberRecords,
1216
fetchSigCounts,
1317
fetchSigDetail,
1418
} from "api/functions/siglead.js";
19+
import { intersection } from "api/plugins/auth.js";
20+
import { request } from "http";
1521

1622
const sigleadRoutes: FastifyPluginAsync = async (fastify, _options) => {
1723
const limitedRoutes: FastifyPluginAsync = async (fastify) => {
@@ -94,36 +100,47 @@ const sigleadRoutes: FastifyPluginAsync = async (fastify, _options) => {
94100
);
95101

96102
// fetch sig count
97-
fastify.get<SigleadGetRequest>(
98-
"/sigcount",
99-
{
100-
onRequest: async (request, reply) => {
101-
/*await fastify.authorize(request, reply, [
102-
AppRoles.LINKS_MANAGER,
103-
AppRoles.LINKS_ADMIN,
104-
]);*/
105-
},
106-
},
103+
fastify.get<SigleadGetRequest>("/sigcount", async (request, reply) => {
104+
// First try-catch: Fetch owner records
105+
let sigMemCounts: SigMemberCount[];
106+
try {
107+
sigMemCounts = await fetchSigCounts(
108+
genericConfig.SigleadDynamoSigMemberTableName,
109+
fastify.dynamoClient,
110+
);
111+
} catch (error) {
112+
request.log.error(
113+
`Failed to fetch sig member counts record: ${error instanceof Error ? error.toString() : "Unknown error"}`,
114+
);
115+
throw new DatabaseFetchError({
116+
message:
117+
"Failed to fetch sig member counts record from Dynamo table.",
118+
});
119+
}
120+
121+
// Send the response
122+
reply.code(200).send(sigMemCounts);
123+
});
124+
125+
// add member
126+
fastify.post<{ Body: SigMemberUpdateRecord }>(
127+
"/addMember",
107128
async (request, reply) => {
108-
// First try-catch: Fetch owner records
109-
let sigMemCounts: SigMemberCount[];
110129
try {
111-
sigMemCounts = await fetchSigCounts(
130+
await addMemberToSigDynamo(
112131
genericConfig.SigleadDynamoSigMemberTableName,
132+
request.body,
113133
fastify.dynamoClient,
114134
);
115135
} catch (error) {
116136
request.log.error(
117-
`Failed to fetch sig member counts record: ${error instanceof Error ? error.toString() : "Unknown error"}`,
137+
`Failed to add member: ${error instanceof Error ? error.toString() : "Unknown error"}`,
118138
);
119139
throw new DatabaseFetchError({
120-
message:
121-
"Failed to fetch sig member counts record from Dynamo table.",
140+
message: "Failed to add sig member record to Dynamo table.",
122141
});
123142
}
124-
125-
// Send the response
126-
reply.code(200).send(sigMemCounts);
143+
reply.code(200);
127144
},
128145
);
129146
};

src/common/config.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ export type GenericConfigType = {
2929
EventsDynamoTableName: string;
3030
CacheDynamoTableName: string;
3131
LinkryDynamoTableName: string;
32-
SigleadDynamoSigDetailTableName: string;
33-
SigleadDynamoSigMemberTableName: string;
3432
StripeLinksDynamoTableName: string;
3533
ConfigSecretName: string;
3634
EntraSecretName: string;
@@ -50,6 +48,10 @@ export type GenericConfigType = {
5048
EntraReadOnlySecretName: string;
5149
AuditLogTable: string;
5250
ApiKeyTable: string;
51+
52+
RateLimiterDynamoTableName: string;
53+
SigleadDynamoSigDetailTableName: string;
54+
SigleadDynamoSigMemberTableName: string;
5355
};
5456

5557
type EnvironmentConfigType = {
@@ -65,13 +67,13 @@ export const commChairsTestingGroupId = "d714adb7-07bb-4d4d-a40a-b035bc2a35a3";
6567
export const commChairsGroupId = "105e7d32-7289-435e-a67a-552c7f215507";
6668
export const miscTestingGroupId = "ff25ec56-6a33-420d-bdb0-51d8a3920e46";
6769

70+
export const orgsGroupId = "0b3be7c2-748e-46ce-97e7-cf86f9ca7337";
71+
6872
const genericConfig: GenericConfigType = {
6973
EventsDynamoTableName: "infra-core-api-events",
7074
StripeLinksDynamoTableName: "infra-core-api-stripe-links",
7175
CacheDynamoTableName: "infra-core-api-cache",
7276
LinkryDynamoTableName: "infra-core-api-linkry",
73-
SigleadDynamoSigDetailTableName: "infra-core-api-sig-details",
74-
SigleadDynamoSigMemberTableName: "infra-core-api-sig-member-details",
7577
ConfigSecretName: "infra-core-api-config",
7678
EntraSecretName: "infra-core-api-entra",
7779
EntraReadOnlySecretName: "infra-core-api-ro-entra",
@@ -90,6 +92,10 @@ const genericConfig: GenericConfigType = {
9092
RoomRequestsStatusTableName: "infra-core-api-room-requests-status",
9193
AuditLogTable: "infra-core-api-audit-log",
9294
ApiKeyTable: "infra-core-api-keys",
95+
96+
RateLimiterDynamoTableName: "infra-core-api-rate-limiter",
97+
SigleadDynamoSigDetailTableName: "infra-core-api-sig-details",
98+
SigleadDynamoSigMemberTableName: "infra-core-api-sig-member-details",
9399
} as const;
94100

95101
const environmentConfig: EnvironmentConfigType = {

src/common/orgs.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { transformSigLeadToURI } from "./utils.js";
2+
13
export const SIGList = [
24
"SIGPwny",
35
"SIGCHI",
@@ -28,3 +30,10 @@ export const CommitteeList = [
2830
"Marketing Committee",
2931
] as [string, ...string[]];
3032
export const OrganizationList = ["ACM", ...SIGList, ...CommitteeList] as [string, ...string[]];
33+
34+
const orgIds2Name: Record<string, string> = {};
35+
OrganizationList.forEach((org) => {
36+
const sigid = transformSigLeadToURI(org);
37+
orgIds2Name[sigid] = org;
38+
});
39+
export { orgIds2Name };

src/common/roles.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@ export enum AppRoles {
1818
MANAGE_ORG_API_KEYS = "manage:orgApiKey"
1919
}
2020
export const allAppRoles = Object.values(AppRoles).filter(
21-
(value) => typeof value === "string",
21+
(value) => typeof value === "string",
2222
);
23+
24+
25+

0 commit comments

Comments
 (0)