Skip to content

Commit b93db84

Browse files
committed
Merged mainscreen-api with siglead-management
1 parent 4702afe commit b93db84

File tree

12 files changed

+587
-105
lines changed

12 files changed

+587
-105
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/siglead.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {
2+
DynamoDBClient,
3+
QueryCommand,
4+
ScanCommand,
5+
} from "@aws-sdk/client-dynamodb";
6+
import { unmarshall } from "@aws-sdk/util-dynamodb";
7+
import { OrganizationList } from "common/orgs.js";
8+
import {
9+
SigDetailRecord,
10+
SigMemberCount,
11+
SigMemberRecord,
12+
} from "common/types/siglead.js";
13+
import { transformSigLeadToURI } from "common/utils.js";
14+
import { string } from "zod";
15+
16+
export async function fetchMemberRecords(
17+
sigid: string,
18+
tableName: string,
19+
dynamoClient: DynamoDBClient,
20+
) {
21+
const fetchSigMemberRecords = new QueryCommand({
22+
TableName: tableName,
23+
KeyConditionExpression: "#sigid = :accessVal",
24+
ExpressionAttributeNames: {
25+
"#sigid": "sigGroupId",
26+
},
27+
ExpressionAttributeValues: {
28+
":accessVal": { S: sigid },
29+
},
30+
ScanIndexForward: false,
31+
});
32+
33+
const result = await dynamoClient.send(fetchSigMemberRecords);
34+
35+
// Process the results
36+
return (result.Items || []).map((item) => {
37+
const unmarshalledItem = unmarshall(item);
38+
return unmarshalledItem as SigMemberRecord;
39+
});
40+
}
41+
42+
export async function fetchSigDetail(
43+
sigid: string,
44+
tableName: string,
45+
dynamoClient: DynamoDBClient,
46+
) {
47+
const fetchSigDetail = new QueryCommand({
48+
TableName: tableName,
49+
KeyConditionExpression: "#sigid = :accessVal",
50+
ExpressionAttributeNames: {
51+
"#sigid": "sigid",
52+
},
53+
ExpressionAttributeValues: {
54+
":accessVal": { S: sigid },
55+
},
56+
ScanIndexForward: false,
57+
});
58+
59+
const result = await dynamoClient.send(fetchSigDetail);
60+
61+
// Process the results
62+
return (result.Items || [{}]).map((item) => {
63+
const unmarshalledItem = unmarshall(item);
64+
65+
// Strip '#' from access field
66+
delete unmarshalledItem.leadGroupId;
67+
delete unmarshalledItem.memberGroupId;
68+
69+
return unmarshalledItem as SigDetailRecord;
70+
})[0];
71+
}
72+
73+
// select count(sigid)
74+
// from table
75+
// groupby sigid
76+
export async function fetchSigCounts(
77+
sigMemberTableName: string,
78+
dynamoClient: DynamoDBClient,
79+
) {
80+
const scan = new ScanCommand({
81+
TableName: sigMemberTableName,
82+
ProjectionExpression: "sigGroupId",
83+
});
84+
85+
const result = await dynamoClient.send(scan);
86+
87+
const ids2Name: Record<string, string> = {};
88+
OrganizationList.forEach((org) => {
89+
const sigid = transformSigLeadToURI(org);
90+
ids2Name[sigid] = org;
91+
});
92+
93+
const counts: Record<string, number> = {};
94+
(result.Items || []).forEach((item) => {
95+
const sigGroupId = item.sigGroupId?.S;
96+
if (sigGroupId) {
97+
counts[sigGroupId] = (counts[sigGroupId] || 0) + 1;
98+
}
99+
});
100+
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,
110+
count,
111+
}),
112+
);
113+
console.log(countsArray);
114+
return countsArray;
115+
}

src/api/routes/siglead.ts

Lines changed: 122 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,137 @@
1-
import { FastifyInstance, FastifyPluginAsync } from "fastify";
2-
import { allAppRoles, AppRoles } from "../../common/roles.js";
3-
import {
4-
addToTenant,
5-
getEntraIdToken,
6-
listGroupMembers,
7-
modifyGroup,
8-
patchUserProfile,
9-
} from "../functions/entraId.js";
10-
import {
11-
BaseError,
12-
DatabaseFetchError,
13-
DatabaseInsertError,
14-
EntraGroupError,
15-
EntraInvitationError,
16-
InternalServerError,
17-
NotFoundError,
18-
UnauthorizedError,
19-
} from "../../common/errors/index.js";
20-
import { PutItemCommand } from "@aws-sdk/client-dynamodb";
1+
import { FastifyPluginAsync } from "fastify";
2+
import { DatabaseFetchError } from "../../common/errors/index.js";
3+
214
import { genericConfig } from "../../common/config.js";
22-
import { marshall } from "@aws-sdk/util-dynamodb";
5+
236
import {
24-
InviteUserPostRequest,
25-
invitePostRequestSchema,
26-
GroupMappingCreatePostRequest,
27-
groupMappingCreatePostSchema,
28-
entraActionResponseSchema,
29-
groupModificationPatchSchema,
30-
GroupModificationPatchRequest,
31-
EntraGroupActions,
32-
entraGroupMembershipListResponse,
33-
ProfilePatchRequest,
34-
entraProfilePatchRequest,
35-
} from "../../common/types/iam.js";
7+
SigDetailRecord,
8+
SigleadGetRequest,
9+
SigMemberCount,
10+
SigMemberRecord,
11+
} from "common/types/siglead.js";
3612
import {
37-
AUTH_DECISION_CACHE_SECONDS,
38-
getGroupRoles,
39-
} from "../functions/authorization.js";
40-
import { OrganizationList } from "common/orgs.js";
41-
import { z } from "zod";
13+
fetchMemberRecords,
14+
fetchSigCounts,
15+
fetchSigDetail,
16+
} from "api/functions/siglead.js";
17+
import { intersection } from "api/plugins/auth.js";
18+
19+
const sigleadRoutes: FastifyPluginAsync = async (fastify, _options) => {
20+
const limitedRoutes: FastifyPluginAsync = async (fastify) => {
21+
/*fastify.register(rateLimiter, {
22+
limit: 30,
23+
duration: 60,
24+
rateLimitIdentifier: "linkry",
25+
});*/
4226

43-
const OrganizationListEnum = z.enum(OrganizationList as [string, ...string[]]);
44-
export type Org = z.infer<typeof OrganizationListEnum>;
27+
fastify.get<SigleadGetRequest>(
28+
"/sigmembers/:sigid",
29+
{
30+
onRequest: async (request, reply) => {
31+
/*await fastify.authorize(request, reply, [
32+
AppRoles.LINKS_MANAGER,
33+
AppRoles.LINKS_ADMIN,
34+
]);*/
35+
},
36+
},
37+
async (request, reply) => {
38+
const { sigid } = request.params;
39+
const tableName = genericConfig.SigleadDynamoSigMemberTableName;
4540

46-
type Member = { name: string; email: string };
47-
type OrgMembersResponse = { org: Org; members: Member[] };
41+
// First try-catch: Fetch owner records
42+
let memberRecords: SigMemberRecord[];
43+
try {
44+
memberRecords = await fetchMemberRecords(
45+
sigid,
46+
tableName,
47+
fastify.dynamoClient,
48+
);
49+
} catch (error) {
50+
request.log.error(
51+
`Failed to fetch member records: ${error instanceof Error ? error.toString() : "Unknown error"}`,
52+
);
53+
throw new DatabaseFetchError({
54+
message: "Failed to fetch member records from Dynamo table.",
55+
});
56+
}
4857

49-
const sigleadRoutes: FastifyPluginAsync = async (fastify, _options) => {
50-
fastify.get<{
51-
Reply: OrgMembersResponse[];
52-
}>("/groups", async (request, reply) => {
53-
const entraIdToken = await getEntraIdToken(
58+
// Send the response
59+
reply.code(200).send(memberRecords);
60+
},
61+
);
62+
63+
fastify.get<SigleadGetRequest>(
64+
"/sigdetail/:sigid",
5465
{
55-
smClient: fastify.secretsManagerClient,
56-
dynamoClient: fastify.dynamoClient,
66+
onRequest: async (request, reply) => {
67+
/*await fastify.authorize(request, reply, [
68+
AppRoles.LINKS_MANAGER,
69+
AppRoles.LINKS_ADMIN,
70+
]);*/
71+
},
72+
},
73+
async (request, reply) => {
74+
const { sigid } = request.params;
75+
const tableName = genericConfig.SigleadDynamoSigDetailTableName;
76+
77+
// First try-catch: Fetch owner records
78+
let sigDetail: SigDetailRecord;
79+
try {
80+
sigDetail = await fetchSigDetail(
81+
sigid,
82+
tableName,
83+
fastify.dynamoClient,
84+
);
85+
} catch (error) {
86+
request.log.error(
87+
`Failed to fetch sig detail record: ${error instanceof Error ? error.toString() : "Unknown error"}`,
88+
);
89+
throw new DatabaseFetchError({
90+
message: "Failed to fetch sig detail record from Dynamo table.",
91+
});
92+
}
93+
94+
// Send the response
95+
reply.code(200).send(sigDetail);
5796
},
58-
fastify.environmentConfig.AadValidClientId,
5997
);
6098

61-
const data = await Promise.all(
62-
OrganizationList.map(async (org) => {
63-
const members: Member[] = await listGroupMembers(entraIdToken, org);
64-
return { org, members } as OrgMembersResponse;
65-
}),
99+
// fetch sig count
100+
fastify.get<SigleadGetRequest>(
101+
"/sigcount",
102+
{
103+
onRequest: async (request, reply) => {
104+
/*await fastify.authorize(request, reply, [
105+
AppRoles.LINKS_MANAGER,
106+
AppRoles.LINKS_ADMIN,
107+
]);*/
108+
},
109+
},
110+
async (request, reply) => {
111+
// First try-catch: Fetch owner records
112+
let sigMemCounts: SigMemberCount[];
113+
try {
114+
sigMemCounts = await fetchSigCounts(
115+
genericConfig.SigleadDynamoSigMemberTableName,
116+
fastify.dynamoClient,
117+
);
118+
} catch (error) {
119+
request.log.error(
120+
`Failed to fetch sig member counts record: ${error instanceof Error ? error.toString() : "Unknown error"}`,
121+
);
122+
throw new DatabaseFetchError({
123+
message:
124+
"Failed to fetch sig member counts record from Dynamo table.",
125+
});
126+
}
127+
128+
// Send the response
129+
reply.code(200).send(sigMemCounts);
130+
},
66131
);
132+
};
67133

68-
reply.status(200).send(data);
69-
});
134+
fastify.register(limitedRoutes);
70135
};
71136

72137
export default sigleadRoutes;

src/common/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ export type GenericConfigType = {
4848
EntraReadOnlySecretName: string;
4949
AuditLogTable: string;
5050
ApiKeyTable: string;
51+
52+
RateLimiterDynamoTableName: string;
53+
SigleadDynamoSigDetailTableName: string;
54+
SigleadDynamoSigMemberTableName: string;
5155
};
5256

5357
type EnvironmentConfigType = {
@@ -63,6 +67,8 @@ export const commChairsTestingGroupId = "d714adb7-07bb-4d4d-a40a-b035bc2a35a3";
6367
export const commChairsGroupId = "105e7d32-7289-435e-a67a-552c7f215507";
6468
export const miscTestingGroupId = "ff25ec56-6a33-420d-bdb0-51d8a3920e46";
6569

70+
export const orgsGroupId = "0b3be7c2-748e-46ce-97e7-cf86f9ca7337";
71+
6672
const genericConfig: GenericConfigType = {
6773
EventsDynamoTableName: "infra-core-api-events",
6874
StripeLinksDynamoTableName: "infra-core-api-stripe-links",
@@ -86,6 +92,10 @@ const genericConfig: GenericConfigType = {
8692
RoomRequestsStatusTableName: "infra-core-api-room-requests-status",
8793
AuditLogTable: "infra-core-api-audit-log",
8894
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",
8999
} as const;
90100

91101
const environmentConfig: EnvironmentConfigType = {

src/common/orgs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export const SIGList = [
44
"GameBuilders",
55
"SIGAIDA",
66
"SIGGRAPH",
7-
"ICPC",
7+
"SIGICPC",
88
"SIGMobile",
99
"SIGMusic",
1010
"GLUG",

0 commit comments

Comments
 (0)