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

chore: ensure graphql lambda function has the correct IAM permissions #371

Merged
merged 27 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b75bc0f
feat: FE and graphql pieces for manual treasury report generation
as1729 Jul 25, 2024
ab3235e
Merge branch 'main' into 348-treasury-report-unblock-testing-of-file-…
as1729 Jul 25, 2024
2f080e5
fix: linting
as1729 Jul 25, 2024
ca10c8d
feat: add signedUrl support
as1729 Jul 25, 2024
5b57ea3
fix: stale or unused comments
as1729 Jul 25, 2024
515841a
fix: ensure file URL is being parsed from the response correctly
as1729 Jul 25, 2024
a00f688
chore: ensure graphql lambda function has the correct IAM permissions
as1729 Jul 25, 2024
a2ef725
feat: add stepfunctions
as1729 Jul 25, 2024
d28dfae
chore: add step function as an allowed trigger
as1729 Jul 25, 2024
1dd6fa5
fix: addresses removal of unnecessary console.log statements and refa…
as1729 Jul 26, 2024
7c4958a
fix: add unit tests for download function and ensure hard return when…
as1729 Jul 26, 2024
e9a38d6
feat: add tests for treasury generation execution
as1729 Jul 26, 2024
d4d0a71
fix: untrack generated type files
as1729 Jul 26, 2024
017a630
merge main
as1729 Jul 26, 2024
5640345
chore: re-org terraform and format
as1729 Jul 26, 2024
b694886
fix: use jsonencode format
as1729 Jul 26, 2024
8ca60ed
fix: ensure graphql lambda has start-execution permissions
as1729 Jul 26, 2024
bc72681
fix: incorrect arn reference
as1729 Jul 26, 2024
a95bc6b
chore: add environment variables for treasury report generation
as1729 Jul 29, 2024
d1b1451
chore: add some initial documentaiton to setup the env-var
as1729 Jul 29, 2024
760c682
Merge branch '348-treasury-report-unblock-testing-of-file-generation-…
as1729 Jul 29, 2024
4d344c0
fix: ensure there is one lambda function to handle project generation
as1729 Jul 31, 2024
5ba22fe
fix: ensure project code is derived from step function event
as1729 Jul 31, 2024
697423a
Merge branch 'main' into 348/add-terraform
as1729 Jul 31, 2024
b839ecf
fix: terraform linting
as1729 Jul 31, 2024
2364d79
fix: unused import
as1729 Jul 31, 2024
1d0095e
Merge branch 'main' into 348/add-terraform
as1729 Jul 31, 2024
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
23 changes: 23 additions & 0 deletions api/src/graphql/organizations.sdl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ export const schema = gql`
name: String
}

input NewTreasuryGenerationInput {
organizationId: Int
payload: JSON!
}

type TreasuryReportGenerationPayload {
response: JSON
}

input DownloadTreasuryFileInput {
fileType: String!
}

type TreasuryFilePayload {
fileLink: String
}

type Mutation {
createOrganization(input: CreateOrganizationInput!): Organization!
@requireAuth
Expand All @@ -34,6 +51,12 @@ export const schema = gql`
createOrganizationAgencyAdmin(
input: CreateOrgAgencyAdminInput!
): CreateOrgAgencyAdminPayload @requireAuth
kickOffTreasuryReportGeneration(
input: NewTreasuryGenerationInput!
): TreasuryReportGenerationPayload @requireAuth
downloadTreasuryFile(
input: DownloadTreasuryFileInput!
): TreasuryFilePayload @requireAuth
}

input CreateOrgAgencyAdminInput {
Expand Down
26 changes: 25 additions & 1 deletion api/src/lib/aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ async function sendHeadObjectToS3Bucket(bucketName: string, key: string) {
* If the upload is new, such as a CreateUploadInput object, the id will be null as it is not from the database.
* In that case, you can use the optional uploadId field to create the key.
*/
export function getS3UploadFileKey(organizationId: number, upload: Upload | CreateUploadInput, uploadId?: number) {
export function getS3UploadFileKey(
organizationId: number,
upload: Upload | CreateUploadInput,
uploadId?: number
) {
if ('id' in upload) {
uploadId = upload.id
}
Expand Down Expand Up @@ -154,6 +158,25 @@ async function getSignedUrl(upload: Upload): Promise<string> {
expiresIn: 60,
})
}
const OUTPUT_TEMPLATE = {
'1A': 'CPF1ABroadbandInfrastructureTemplate',
'1B': 'CPF1BDigitalConnectivityTechTemplate',
'1C': 'CPF1CMultiPurposeCommunityTemplate',
Subrecipient: 'CPFSubrecipientTemplate',
}
async function getTreasurySignedUrl(
fileType: string,
organization_id: number,
current_reporting_period_id: string
): Promise<string> {
const key = `treasuryreports/${organization_id}/${current_reporting_period_id}/${OUTPUT_TEMPLATE[fileType]}.csv`

const s3 = getS3Client()
const baseParams = { Bucket: REPORTING_DATA_BUCKET_NAME, Key: key }
return awsGetSignedUrl(s3, new GetObjectCommand(baseParams), {
expiresIn: 60,
})
}

function getSQSClient() {
let sqs: SQSClient
Expand Down Expand Up @@ -245,4 +268,5 @@ export default {
getS3Client,
startStepFunctionExecution,
s3DeleteObject,
getTreasurySignedUrl,
}
52 changes: 52 additions & 0 deletions api/src/services/organizations/organizations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import type {
QueryResolvers,
MutationResolvers,
OrganizationRelationResolvers,
Agency,
} from 'types/graphql'

import aws from 'src/lib/aws'
import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'

Expand Down Expand Up @@ -74,6 +76,56 @@ export const getOrCreateOrganization = async (orgName, reportingPeriodName) => {
}
}

export const downloadTreasuryFile: MutationResolvers['downloadTreasuryFile'] =
async ({ input }) => {
const { fileType } = input
const { organizationId } = context.currentUser.agency

// get the organization
const organization = await db.organization.findUnique({
where: { id: organizationId },
})

// Retrieve the signed URL for the treasury file
logger.info(`Downloading treasury file for ${fileType}`)
const url = await aws.getTreasurySignedUrl(
fileType,
organization.id,
organization.preferences['current_reporting_period_id']
)

// Return the file link
return { fileLink: url }
}

export const kickOffTreasuryReportGeneration: MutationResolvers['kickOffTreasuryReportGeneration'] =
async ({ input }) => {
let { organizationId } = input
const { payload } = input

if (!organizationId) {
logger.info('Using current user agency to determine organization')
organizationId = (context.currentUser.agency as Agency).organizationId
}
// Get the organization
const organization = await db.organization.findUnique({
where: { id: organizationId },
})

if (!organization) {
throw new Error(`Organization with id ${organizationId} not found`)
}

// Do something with the payload
logger.info(
`Kicking off treasury report generation for ${organization.name}`
)
logger.info(`Payload: ${JSON.stringify(payload)}`)

// Return the payload
return { response: payload }
}

export const createOrganizationAgencyAdmin: MutationResolvers['createOrganizationAgencyAdmin'] =
async ({ input }) => {
const {
Expand Down
65 changes: 65 additions & 0 deletions api/types/graphql.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ export type CreateValidationRulesInput = {
versionId: Version;
};

export type DownloadTreasuryFileInput = {
fileType: Scalars['String'];
};

export type ExpenditureCategory = {
__typename?: 'ExpenditureCategory';
Uploads: Array<Maybe<Upload>>;
Expand Down Expand Up @@ -202,7 +206,9 @@ export type Mutation = {
deleteUploadValidation: UploadValidation;
deleteUser: User;
deleteValidationRules: ValidationRules;
downloadTreasuryFile?: Maybe<TreasuryFilePayload>;
downloadUploadFile: Scalars['String'];
kickOffTreasuryReportGeneration?: Maybe<TreasuryReportGenerationPayload>;
updateAgency: Agency;
updateExpenditureCategory: ExpenditureCategory;
updateInputTemplate: InputTemplate;
Expand Down Expand Up @@ -354,11 +360,21 @@ export type MutationdeleteValidationRulesArgs = {
};


export type MutationdownloadTreasuryFileArgs = {
input: DownloadTreasuryFileInput;
};


export type MutationdownloadUploadFileArgs = {
id: Scalars['Int'];
};


export type MutationkickOffTreasuryReportGenerationArgs = {
input: NewTreasuryGenerationInput;
};


export type MutationupdateAgencyArgs = {
id: Scalars['Int'];
input: UpdateAgencyInput;
Expand Down Expand Up @@ -436,6 +452,11 @@ export type MutationupdateValidationRulesArgs = {
input: UpdateValidationRulesInput;
};

export type NewTreasuryGenerationInput = {
organizationId?: InputMaybe<Scalars['Int']>;
payload: Scalars['JSON'];
};

export type Organization = {
__typename?: 'Organization';
agencies: Array<Maybe<Agency>>;
Expand Down Expand Up @@ -673,6 +694,16 @@ export type SubrecipientUpload = {
version: Version;
};

export type TreasuryFilePayload = {
__typename?: 'TreasuryFilePayload';
fileLink?: Maybe<Scalars['String']>;
};

export type TreasuryReportGenerationPayload = {
__typename?: 'TreasuryReportGenerationPayload';
response?: Maybe<Scalars['JSON']>;
};

export type UpdateAgencyInput = {
abbreviation?: InputMaybe<Scalars['String']>;
code?: InputMaybe<Scalars['String']>;
Expand Down Expand Up @@ -907,12 +938,14 @@ export type ResolversTypes = {
CreateValidationRulesInput: CreateValidationRulesInput;
Date: ResolverTypeWrapper<Scalars['Date']>;
DateTime: ResolverTypeWrapper<Scalars['DateTime']>;
DownloadTreasuryFileInput: DownloadTreasuryFileInput;
ExpenditureCategory: ResolverTypeWrapper<MergePrismaWithSdlTypes<PrismaExpenditureCategory, MakeRelationsOptional<ExpenditureCategory, AllMappedModels>, AllMappedModels>>;
InputTemplate: ResolverTypeWrapper<MergePrismaWithSdlTypes<PrismaInputTemplate, MakeRelationsOptional<InputTemplate, AllMappedModels>, AllMappedModels>>;
Int: ResolverTypeWrapper<Scalars['Int']>;
JSON: ResolverTypeWrapper<Scalars['JSON']>;
JSONObject: ResolverTypeWrapper<Scalars['JSONObject']>;
Mutation: ResolverTypeWrapper<{}>;
NewTreasuryGenerationInput: NewTreasuryGenerationInput;
Organization: ResolverTypeWrapper<MergePrismaWithSdlTypes<PrismaOrganization, MakeRelationsOptional<Organization, AllMappedModels>, AllMappedModels>>;
OutputTemplate: ResolverTypeWrapper<MergePrismaWithSdlTypes<PrismaOutputTemplate, MakeRelationsOptional<OutputTemplate, AllMappedModels>, AllMappedModels>>;
Project: ResolverTypeWrapper<MergePrismaWithSdlTypes<PrismaProject, MakeRelationsOptional<Project, AllMappedModels>, AllMappedModels>>;
Expand All @@ -925,6 +958,8 @@ export type ResolversTypes = {
SubrecipientStatus: SubrecipientStatus;
SubrecipientUpload: ResolverTypeWrapper<MergePrismaWithSdlTypes<PrismaSubrecipientUpload, MakeRelationsOptional<SubrecipientUpload, AllMappedModels>, AllMappedModels>>;
Time: ResolverTypeWrapper<Scalars['Time']>;
TreasuryFilePayload: ResolverTypeWrapper<TreasuryFilePayload>;
TreasuryReportGenerationPayload: ResolverTypeWrapper<TreasuryReportGenerationPayload>;
UpdateAgencyInput: UpdateAgencyInput;
UpdateExpenditureCategoryInput: UpdateExpenditureCategoryInput;
UpdateInputTemplateInput: UpdateInputTemplateInput;
Expand Down Expand Up @@ -968,12 +1003,14 @@ export type ResolversParentTypes = {
CreateValidationRulesInput: CreateValidationRulesInput;
Date: Scalars['Date'];
DateTime: Scalars['DateTime'];
DownloadTreasuryFileInput: DownloadTreasuryFileInput;
ExpenditureCategory: MergePrismaWithSdlTypes<PrismaExpenditureCategory, MakeRelationsOptional<ExpenditureCategory, AllMappedModels>, AllMappedModels>;
InputTemplate: MergePrismaWithSdlTypes<PrismaInputTemplate, MakeRelationsOptional<InputTemplate, AllMappedModels>, AllMappedModels>;
Int: Scalars['Int'];
JSON: Scalars['JSON'];
JSONObject: Scalars['JSONObject'];
Mutation: {};
NewTreasuryGenerationInput: NewTreasuryGenerationInput;
Organization: MergePrismaWithSdlTypes<PrismaOrganization, MakeRelationsOptional<Organization, AllMappedModels>, AllMappedModels>;
OutputTemplate: MergePrismaWithSdlTypes<PrismaOutputTemplate, MakeRelationsOptional<OutputTemplate, AllMappedModels>, AllMappedModels>;
Project: MergePrismaWithSdlTypes<PrismaProject, MakeRelationsOptional<Project, AllMappedModels>, AllMappedModels>;
Expand All @@ -984,6 +1021,8 @@ export type ResolversParentTypes = {
Subrecipient: MergePrismaWithSdlTypes<PrismaSubrecipient, MakeRelationsOptional<Subrecipient, AllMappedModels>, AllMappedModels>;
SubrecipientUpload: MergePrismaWithSdlTypes<PrismaSubrecipientUpload, MakeRelationsOptional<SubrecipientUpload, AllMappedModels>, AllMappedModels>;
Time: Scalars['Time'];
TreasuryFilePayload: TreasuryFilePayload;
TreasuryReportGenerationPayload: TreasuryReportGenerationPayload;
UpdateAgencyInput: UpdateAgencyInput;
UpdateExpenditureCategoryInput: UpdateExpenditureCategoryInput;
UpdateInputTemplateInput: UpdateInputTemplateInput;
Expand Down Expand Up @@ -1141,7 +1180,9 @@ export type MutationResolvers<ContextType = RedwoodGraphQLContext, ParentType ex
deleteUploadValidation: Resolver<ResolversTypes['UploadValidation'], ParentType, ContextType, RequireFields<MutationdeleteUploadValidationArgs, 'id'>>;
deleteUser: Resolver<ResolversTypes['User'], ParentType, ContextType, RequireFields<MutationdeleteUserArgs, 'id'>>;
deleteValidationRules: Resolver<ResolversTypes['ValidationRules'], ParentType, ContextType, RequireFields<MutationdeleteValidationRulesArgs, 'id'>>;
downloadTreasuryFile: Resolver<Maybe<ResolversTypes['TreasuryFilePayload']>, ParentType, ContextType, RequireFields<MutationdownloadTreasuryFileArgs, 'input'>>;
downloadUploadFile: Resolver<ResolversTypes['String'], ParentType, ContextType, RequireFields<MutationdownloadUploadFileArgs, 'id'>>;
kickOffTreasuryReportGeneration: Resolver<Maybe<ResolversTypes['TreasuryReportGenerationPayload']>, ParentType, ContextType, RequireFields<MutationkickOffTreasuryReportGenerationArgs, 'input'>>;
updateAgency: Resolver<ResolversTypes['Agency'], ParentType, ContextType, RequireFields<MutationupdateAgencyArgs, 'id' | 'input'>>;
updateExpenditureCategory: Resolver<ResolversTypes['ExpenditureCategory'], ParentType, ContextType, RequireFields<MutationupdateExpenditureCategoryArgs, 'id' | 'input'>>;
updateInputTemplate: Resolver<ResolversTypes['InputTemplate'], ParentType, ContextType, RequireFields<MutationupdateInputTemplateArgs, 'id' | 'input'>>;
Expand Down Expand Up @@ -1185,7 +1226,9 @@ export type MutationRelationResolvers<ContextType = RedwoodGraphQLContext, Paren
deleteUploadValidation?: RequiredResolverFn<ResolversTypes['UploadValidation'], ParentType, ContextType, RequireFields<MutationdeleteUploadValidationArgs, 'id'>>;
deleteUser?: RequiredResolverFn<ResolversTypes['User'], ParentType, ContextType, RequireFields<MutationdeleteUserArgs, 'id'>>;
deleteValidationRules?: RequiredResolverFn<ResolversTypes['ValidationRules'], ParentType, ContextType, RequireFields<MutationdeleteValidationRulesArgs, 'id'>>;
downloadTreasuryFile?: RequiredResolverFn<Maybe<ResolversTypes['TreasuryFilePayload']>, ParentType, ContextType, RequireFields<MutationdownloadTreasuryFileArgs, 'input'>>;
downloadUploadFile?: RequiredResolverFn<ResolversTypes['String'], ParentType, ContextType, RequireFields<MutationdownloadUploadFileArgs, 'id'>>;
kickOffTreasuryReportGeneration?: RequiredResolverFn<Maybe<ResolversTypes['TreasuryReportGenerationPayload']>, ParentType, ContextType, RequireFields<MutationkickOffTreasuryReportGenerationArgs, 'input'>>;
updateAgency?: RequiredResolverFn<ResolversTypes['Agency'], ParentType, ContextType, RequireFields<MutationupdateAgencyArgs, 'id' | 'input'>>;
updateExpenditureCategory?: RequiredResolverFn<ResolversTypes['ExpenditureCategory'], ParentType, ContextType, RequireFields<MutationupdateExpenditureCategoryArgs, 'id' | 'input'>>;
updateInputTemplate?: RequiredResolverFn<ResolversTypes['InputTemplate'], ParentType, ContextType, RequireFields<MutationupdateInputTemplateArgs, 'id' | 'input'>>;
Expand Down Expand Up @@ -1459,6 +1502,26 @@ export interface TimeScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes
name: 'Time';
}

export type TreasuryFilePayloadResolvers<ContextType = RedwoodGraphQLContext, ParentType extends ResolversParentTypes['TreasuryFilePayload'] = ResolversParentTypes['TreasuryFilePayload']> = {
fileLink: OptArgsResolverFn<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type TreasuryFilePayloadRelationResolvers<ContextType = RedwoodGraphQLContext, ParentType extends ResolversParentTypes['TreasuryFilePayload'] = ResolversParentTypes['TreasuryFilePayload']> = {
fileLink?: RequiredResolverFn<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type TreasuryReportGenerationPayloadResolvers<ContextType = RedwoodGraphQLContext, ParentType extends ResolversParentTypes['TreasuryReportGenerationPayload'] = ResolversParentTypes['TreasuryReportGenerationPayload']> = {
response: OptArgsResolverFn<Maybe<ResolversTypes['JSON']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type TreasuryReportGenerationPayloadRelationResolvers<ContextType = RedwoodGraphQLContext, ParentType extends ResolversParentTypes['TreasuryReportGenerationPayload'] = ResolversParentTypes['TreasuryReportGenerationPayload']> = {
response?: RequiredResolverFn<Maybe<ResolversTypes['JSON']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type UploadResolvers<ContextType = RedwoodGraphQLContext, ParentType extends ResolversParentTypes['Upload'] = ResolversParentTypes['Upload']> = {
agency: OptArgsResolverFn<ResolversTypes['Agency'], ParentType, ContextType>;
agencyId: OptArgsResolverFn<ResolversTypes['Int'], ParentType, ContextType>;
Expand Down Expand Up @@ -1596,6 +1659,8 @@ export type Resolvers<ContextType = RedwoodGraphQLContext> = {
Subrecipient: SubrecipientResolvers<ContextType>;
SubrecipientUpload: SubrecipientUploadResolvers<ContextType>;
Time: GraphQLScalarType;
TreasuryFilePayload: TreasuryFilePayloadResolvers<ContextType>;
TreasuryReportGenerationPayload: TreasuryReportGenerationPayloadResolvers<ContextType>;
Upload: UploadResolvers<ContextType>;
UploadValidation: UploadValidationResolvers<ContextType>;
User: UserResolvers<ContextType>;
Expand Down
Loading
Loading