amplify-backend
amplify-backend copied to clipboard
Gen 2 + RDS postgres - How to do tenant isolation with the list command
Environment information
{
"name": "nubiops-react-application",
"private": true,
"version": "0.7.2",
"type": "module",
"scripts": {
"dev": "vite",
"build:prod": "tsc vite build",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"lint:fix": "eslint --fix ./src",
"typecheck": "tsc --noEmit",
"format:write": "prettier --write \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache",
"format:check": "prettier --check \"**/*.{js,jsx,mjs,ts,tsx,mdx}\" --cache",
"test": "jest --watch"
},
"dependencies": {
"@auth0/auth0-react": "2.3.0",
"@aws-amplify/api-graphql": "^4.7.15",
"@aws-amplify/auth": "^6.12.4",
"@aws-amplify/cli": "^13.0.1",
"@aws-amplify/codegen-ui-react": "^2.20.3",
"@aws-amplify/core": "^6.11.4",
"@aws-amplify/data-schema": "^1.20.5",
"@aws-amplify/ui": "^6.10.2",
"@aws-amplify/ui-react": "^6.11.1",
"@aws-amplify/ui-react-storage": "3.10.2",
"@aws-lambda-powertools/idempotency": "2.19.1",
"@aws-sdk/client-cost-explorer": "^3.804.0",
"@aws-sdk/client-dynamodb": "3.804.0",
"@aws-sdk/client-secrets-manager": "^3.804.0",
"@aws-sdk/client-ses": "3.804.0",
"@aws-sdk/client-sso-oidc": "3.804.0",
"@aws-sdk/client-sts": "3.804.0",
"@aws-sdk/lib-dynamodb": "3.804.0",
"@babel/eslint-parser": "7.27.1",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@elgorditosalsero/react-gtm-hook": "2.7.2",
"@emotion/cache": "11.14.0",
"@emotion/react": "11.14.0",
"@emotion/server": "11.11.0",
"@emotion/styled": "11.14.0",
"@fontsource/inter": "^5.2.5",
"@fontsource/plus-jakarta-sans": "5.2.5",
"@fontsource/roboto-mono": "5.2.5",
"@fullcalendar/core": "6.1.17",
"@fullcalendar/daygrid": "6.1.17",
"@fullcalendar/interaction": "6.1.17",
"@fullcalendar/list": "6.1.17",
"@fullcalendar/react": "6.1.17",
"@fullcalendar/timegrid": "6.1.17",
"@fullcalendar/timeline": "6.1.17",
"@hookform/resolvers": "5.0.1",
"@mui/icons-material": "7.1.0",
"@mui/lab": "7.0.0-beta.12",
"@mui/material": "7.1.0",
"@mui/styled-engine-sc": "7.1.0",
"@mui/system": "7.1.0",
"@mui/utils": "7.1.0",
"@mui/x-data-grid": "7.28.3",
"@mui/x-data-grid-pro": "7.28.3",
"@mui/x-date-pickers": "7.28.3",
"@mui/x-date-pickers-pro": "7.28.3",
"@mui/x-license": "7.28.0",
"@phosphor-icons/react": "2.1.7",
"@react-pdf/renderer": "4.3.0",
"@supabase/supabase-js": "2.49.4",
"@tiptap/extension-link": "2.12.0",
"@tiptap/extension-placeholder": "2.12.0",
"@tiptap/react": "2.12.0",
"@tiptap/starter-kit": "2.12.0",
"amplify": "^0.0.11",
"aws-amplify": "^6.14.4",
"axios": "^1.9.0",
"core-js": "3.42.0",
"dayjs": "1.11.13",
"embla-carousel": "8.6.0",
"embla-carousel-react": "8.6.0",
"firebase": "11.6.1",
"i18next": "25.1.1",
"install": "^0.13.0",
"jsforce": "^3.8.1",
"lz-string": "^1.5.0",
"mapbox-gl": "3.11.1",
"notistack": "^3.0.2",
"pg": "^8.15.6",
"pkce-challenge": "^5.0.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-dropzone": "14.3.8",
"react-helmet-async": "2.0.5",
"react-hook-form": "7.56.2",
"react-i18next": "15.5.1",
"react-icons": "5.5.0",
"react-map-gl": "8.0.4",
"react-markdown": "10.1.0",
"react-modal": "3.16.3",
"react-router": "^7.5.3",
"react-router-dom": "7.5.3",
"react-simple-maps": "3.0.0",
"react-syntax-highlighter": "15.6.1",
"react-to-print": "3.1.0",
"react-tooltip": "^5.28.1",
"recharts": "2.15.3",
"rehype-katex": "^7.0.1",
"remark-gfm": "4.0.1",
"remark-math": "^6.0.0",
"sonner": "2.0.3",
"styled-components": "6.1.17",
"stylis": "4.3.6",
"stylis-plugin-rtl": "2.1.1",
"uuid": "11.1.0",
"vite": "6.3.5",
"zod": "3.24.4"
},
"devDependencies": {
"@auth0/auth0-react": "2.3.9",
"@aws-amplify/backend": "1.16.1",
"@aws-amplify/backend-cli": "1.7.1",
"@aws-lambda-powertools/logger": "2.19.1",
"@aws-sdk/client-cognito-identity-provider": "^3.804.0",
"@babel/plugin-transform-class-properties": "7.27.1",
"@babel/plugin-transform-object-rest-spread": "7.27.2",
"@eslint/config-array": "0.20.0",
"@eslint/object-schema": "^2.1.6",
"@ianvs/prettier-plugin-sort-imports": "4.4.1",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.3.0",
"@types/aws-lambda": "^8.10.149",
"@types/jest": "29.5.14",
"@types/mapbox-gl": "3.4.1",
"@types/node": "22.15.15",
"@types/pg": "^8.15.0",
"@types/react": "19.1.3",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "19.1.3",
"@types/react-map-gl": "^6.1.7",
"@types/react-simple-maps": "3.0.6",
"@types/react-syntax-highlighter": "15.5.13",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "8.32.0",
"@typescript-eslint/parser": "8.32.0",
"@vercel/style-guide": "6.0.0",
"@vitejs/plugin-react": "4.4.1",
"aws-cdk": "2.1013.0",
"aws-cdk-lib": "2.194.0",
"constructs": "10.4.2",
"esbuild": "0.25.4",
"eslint": "9.26.0",
"eslint-config-prettier": "10.1.3",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-react-refresh": "0.4.20",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"prettier": "3.5.3",
"rollup-plugin-visualizer": "5.14.0",
"tsx": "4.19.4",
"typescript": "5.8.3",
"vite": "^5.4.10"
}
}
Describe the bug
Hello, I'm having some difficulty with getting my React Gen 2 App working with RDS postgres v2 database working with tenant isolation.
Background: I have an amplify gen 2 React app working with DynamoDB and I have effectively implemented the tenant isolation there.
I've followed the example in the Gen 2 Documentation: https://docs.amplify.aws/react/build-a-backend/data/connect-to-existing-data-sources/connect-postgres-mysql-database/
The App if pretty far along on Dynamodb. I've generated the 'schema.sql.ts' with the command: npx ampx generate schema-from-database --connection-uri-secret SQL_CONNECTION_STRING --out amplify/data/schema.sql.ts
I've been adding RDS postgres and it currently can do CRUD operations on the database. This command will bring back all of the rows of the database regardless of tenant field value. const contractSQLModelListData = await client.models.contractsql.list();
I've tried to limit the access similar to the way I've done it in the dynamodb as you can see from the attached data/resource.ts file, but it is not limiting the client call. Clearly, I need to filter from the front end, but that's not sufficient, I need to limit this on the backend.
As you can see, I'm also using the sql files to do advanced querying of the table.
How do I modify this configuration to limit these queries based on tenant id?
BTW, I'm opening this as a bug per request from the team on the Amplify Office hours.
I've been working on this for days, and would greatly appreciate it if we can jump on a Teams meeting. Please email me [email protected]
Reproduction steps
This is how I'm creating the resource.ts. note that i cut out a bunch of models that are not relevant, but included some that have the group access permissions included.
// amplify/data/resource.ts
import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
import { postConfirmation } from "../auth/post-confirmation/resource";
import { writeGlobalLogFunction } from "../functions/write-global-log/resource";
import { manageDealApprovalFunction } from "../functions/manage-deal-approval/resource";
import { manageInviteUserFunction } from "../functions/manage-invite-user/resource";
import { selectAPlanFunction } from "../functions/select-a-plan/resource";
import { doInitializationsFunction } from "../functions/do-initializations/resource";
import { manageContactEmailFunction } from "../functions/manage-contact-email/resource";
import { getIPAddressFunction } from "../functions/getPAddressFunction/resource";
import { manageIntegrationsFunction } from '../functions/integration-manager/resource';
import { vendorCostImporterFunction } from '../functions/vendor-cost-importer/resource';
import { CRMImporterFunction } from '../functions/crm-importer/resource';
import { CRMSyncDealFunction } from '../functions/crm-sync-deal/resource';
import { OAuth2TokenFunction } from '../functions/OAuth2TokenFunction/resource';
import { sqlCRUDfunction } from '../functions/sql-crud/resource';
import { CRMCallbackFunction } from '../functions/crm-callback/resource';
import { getDBCountsByResellerIDFunction } from '../functions/getDBCountsByResellerID/resource';
import {
globalAVGAdminGroupName,
tenantOwnerGroup,
globalAllNonAVGAdminGroups,
// ContractInterface,
} from '../../src/globals';
import { schema as generatedSqlSchema } from './schema.sql';
// Add a global authorization rule
const sqlSchema = generatedSqlSchema.setAuthorization((models) => [
models.contractsql.authorization(allow => [
allow.owner().to(['read', 'create', 'update', 'delete']),
allow.groupDefinedIn('tenant').to(['read']),
allow.groupDefinedIn('tenanteditgroup').to(['read', 'create', 'update', 'delete']),
allow.groupDefinedIn('resellerid').to(['read']),
allow.group(globalAVGAdminGroupName).to(['read', 'create', 'update', 'delete']),
]),
models.globallogsql.authorization(allow => [
allow.owner().to(['read', 'create', 'update', 'delete']),
allow.groupDefinedIn('tenant').to(['read']),
allow.groupDefinedIn('resellerid').to(['read']),
allow.group(globalAVGAdminGroupName).to(['read', 'create', 'update', 'delete']),
]),
models.dbversionsql.authorization(allow => [
allow.owner().to(['read', 'create', 'update', 'delete']),
allow.groupDefinedIn('resellerid').to(['read']),
allow.authenticated("userPools"),
]),
// examples of field level security
// models.GlobalLogSQL.fields.tenant.authorization(allow => [
// allow.owner().to(['read', 'create', 'update', 'delete']),
// allow.groupDefinedIn('tenant').to(['read']),
// allow.groupDefinedIn('resellerId').to(['read']),
// allow.group(globalAVGAdminGroupName).to(['read', 'create', 'update', 'delete']),
// ]),
// models.dbVersionSQL.fields.resellerId.authorization(allow => [
// allow.owner().to(['read', 'create', 'update', 'delete']),
// allow.groupDefinedIn('resellerId').to(['read']),
// allow.authenticated("userPools"),
// ]),
]
).addToSchema({
createContractSQLModel: a.mutation()
.arguments({
id: a.string().required(),
tenant: a.string().required(),
resellerid: a.string(),
contractnumber: a.string().required(),
tenanteditgroup: a.string(),
version: a.string(),
name: a.string(),
updatedyyyymmdd: a.string(),
createdat: a.string(),
updatedat: a.string(),
createdby: a.string(),
updatedby: a.string(),
approvedbyid: a.string(),
approvedbyname: a.string(),
updatedbyname: a.string(),
changehistoryjson: a.string(),
salespersonid: a.string(),
salespersonname: a.string(),
salespersonemail: a.string(),
supportcontactname: a.string(),
supportcontactemail: a.string(),
teamnamesjson: a.string(),
territoryid: a.string(),
territoryname: a.string(),
divisionid: a.string(),
divisionname: a.string(),
regionid: a.string(),
regionname: a.string(),
geographyid: a.string(),
geographyname: a.string(),
customerid: a.string(),
customercompanyname: a.string(),
customerdepartment: a.string(),
customercontactname: a.string(),
customercontactemail: a.string(),
customercontactphone: a.string(),
customeraddress: a.string(),
productshortlistjson: a.string(),
lineitemdetailsjson: a.string(),
quoteddate: a.string(),
approveddate: a.string(),
trialperiodstart: a.string(),
trialperiodend: a.string(),
contractperiodstart: a.string(),
contractperiodend: a.string(),
billingmethod: a.string(),
billingfrequency: a.string(),
dealnotes: a.string(),
approvalnotes: a.string(),
dealsubtotalbeforediscount: a.float(),
requesteddiscount: a.float(),
approveddiscount: a.float(),
dealdiscountamount: a.float(),
dealsubtotal: a.float(),
salescommission: a.float(),
deallevelgrossmargin: a.float(),
dealcost: a.float(),
applicabletaxrate: a.float(),
taxamount: a.float(),
dealtotal: a.float(),
currency: a.string(),
exchangerate: a.float(),
customerdistributorid: a.string(),
customerdistributorname: a.string(),
distributordiscount: a.float(),
customerresellerid: a.string(),
customerresellername: a.string(),
resellerdiscount: a.float(),
requesterid: a.string(),
requestername: a.string(),
requesteremail: a.string(),
approverid: a.string(),
approvername: a.string(),
approveremail: a.string(),
contracttype: a.string(),
trialdays: a.float(),
contractdays: a.float(),
isapproved: a.boolean(),
approvalstatus: a.string(),
approvaldate: a.string(),
approvalcomments: a.string(),
enabled: a.boolean(),
deleted: a.boolean(),
metadatajson: a.string(),
contractpdf: a.string(),
relatedfiles: a.string(),
imagename: a.string(),
base64image: a.string(),
notes: a.string(),
})
.returns(a.json())
.authorization(allow => allow.authenticated())
.handler(a.handler.sqlReference('./sqlFiles/createContract.sql')),
updateContractSQLModel: a.mutation()
.arguments({
id: a.string().required(),
tenant: a.string().required(),
resellerid: a.string(),
contractnumber: a.string().required(),
tenanteditgroup: a.string(),
version: a.string(),
name: a.string(),
updatedyyyymmdd: a.string(),
createdat: a.string(),
updatedat: a.string(),
createdby: a.string(),
updatedby: a.string(),
approvedbyid: a.string(),
approvedbyname: a.string(),
updatedbyname: a.string(),
changehistoryjson: a.string(),
salespersonid: a.string(),
salespersonname: a.string(),
salespersonemail: a.string(),
supportcontactname: a.string(),
supportcontactemail: a.string(),
teamnamesjson: a.string(),
territoryid: a.string(),
territoryname: a.string(),
divisionid: a.string(),
divisionname: a.string(),
regionid: a.string(),
regionname: a.string(),
geographyid: a.string(),
geographyname: a.string(),
customerid: a.string(),
customercompanyname: a.string(),
customerdepartment: a.string(),
customercontactname: a.string(),
customercontactemail: a.string(),
customercontactphone: a.string(),
customeraddress: a.string(),
productshortlistjson: a.string(),
lineitemdetailsjson: a.string(),
quoteddate: a.string(),
approveddate: a.string(),
trialperiodstart: a.string(),
trialperiodend: a.string(),
contractperiodstart: a.string(),
contractperiodend: a.string(),
billingmethod: a.string(),
billingfrequency: a.string(),
dealnotes: a.string(),
approvalnotes: a.string(),
dealsubtotalbeforediscount: a.float(),
requesteddiscount: a.float(),
approveddiscount: a.float(),
dealdiscountamount: a.float(),
dealsubtotal: a.float(),
salescommission: a.float(),
deallevelgrossmargin: a.float(),
dealcost: a.float(),
applicabletaxrate: a.float(),
taxamount: a.float(),
dealtotal: a.float(),
currency: a.string(),
exchangerate: a.float(),
customerdistributorid: a.string(),
customerdistributorname: a.string(),
distributordiscount: a.float(),
customerresellerid: a.string(),
customerresellername: a.string(),
resellerdiscount: a.float(),
requesterid: a.string(),
requestername: a.string(),
requesteremail: a.string(),
approverid: a.string(),
approvername: a.string(),
approveremail: a.string(),
contracttype: a.string(),
trialdays: a.float(),
contractdays: a.float(),
isapproved: a.boolean(),
approvalstatus: a.string(),
approvaldate: a.string(),
approvalcomments: a.string(),
enabled: a.boolean(),
deleted: a.boolean(),
metadatajson: a.string(),
contractpdf: a.string(),
relatedfiles: a.string(),
imagename: a.string(),
base64image: a.string(),
owner: a.string(),
notes: a.string(),
})
.returns(a.json())
.authorization(allow => allow.authenticated())
.handler(a.handler.sqlReference('./sqlFiles/updateContract.sql')),
listAllContractSQLModel: a.query()
.arguments({
tenant: a.string().required()
})
.returns(a.json().array())
.authorization(allow => allow.authenticated())
.handler(a.handler.sqlReference('./sqlFiles/listAllContract.sql')),
listAllContractSQLByTenantModel: a.query()
.arguments({
tenant: a.string().required()
})
.returns(a.json().array())
.authorization(allow => allow.authenticated())
.handler(a.handler.sqlReference('./sqlFiles/listAllContractByTenant.sql')),
listAllContractSQLByTenantAndGeographyIdModel: a.query()
.arguments({
tenant: a.string().required(),
geographyid: a.string().required()
})
.returns(a.json().array())
.authorization(allow => allow.authenticated())
.handler(a.handler.sqlReference('./sqlFiles/listAllContractByTenantAndGeographyId.sql')),
listAllContractSQLByTenantAndRegionIdModel: a.query()
.arguments({
tenant: a.string().required(),
regionid: a.string().required()
})
.returns(a.json().array())
.authorization(allow => allow.authenticated())
.handler(a.handler.sqlReference('./sqlFiles/listAllContractByTenantAndRegionId.sql')),
listAllContractSQLByTenantAndTerritoryIdModel: a.query()
.arguments({
tenant: a.string().required(),
territoryid: a.string().required()
})
.returns(a.json().array())
.authorization(allow => allow.authenticated())
.handler(a.handler.sqlReference('./sqlFiles/listAllContractByTenantAndTerritoryId.sql')),
listAllContractSQLByTenantAndUserModel: a.query()
.arguments({
tenant: a.string().required(),
owner: a.string().required()
})
.returns(a.json().array())
.authorization(allow => allow.authenticated())
.handler(a.handler.sqlReference('./sqlFiles/listAllContractByTenantAndOwner.sql')),
});
const schema = a.schema({
// #region APIs
// note the 'manageInviteUserModel' mutation doesn't actually hit a real table
// it is an API only mutation which hits a backend lambda function 'manageInviteUserFunction'
// #region manageInviteUserModel
manageInviteUserModel: a.mutation()
.arguments({
id: a.id().required(),
resellerId: a.string().required(),
tenant: a.string().required(),
tenantEditGroup: a.string().required(),
accountNumber: a.string().required(),
requesterEmail: a.string().required(),
requesterUserId: a.string().required(),
requesterName: a.string().required(),
newUserName: a.string().required(),
newUserEmail: a.string().required(),
newUserRole: a.string().required(),
isApprover: a.boolean().required(),
companyName: a.string().required(),
territoryId: a.string().required(),
territoryName: a.string().required(),
divisionId: a.string().required(),
divisionName: a.string().required(),
regionId: a.string().required(),
regionName: a.string().required(),
geographyId: a.string().required(),
geographyName: a.string().required(),
timeZone: a.string().required(),
action: a.string().required(),
})
.returns(a.string())
.authorization(allow => [allow.authenticated("userPools")])
.handler(a.handler.function(manageInviteUserFunction)),
// #endregion manageInviteUserModel
// #region InvitedTeamMember
InvitedTeamMember: a.model({
id: a.id(),
tenant: a.id(),
tenantEditGroup: a.id(),
resellerId: a.id(),
accountId: a.id(),
accountNumber: a.string(),
requesterUserId: a.string().required(),
requesterName: a.string().required(),
requesterEmail: a.string().required(),
newUserName: a.string().required(),
newUserEmail: a.string().required(),
newUserRole: a.string().required(),
isApprover: a.boolean(),
companyName: a.string().required(),
territoryId: a.string().required(),
territoryName: a.string().required(),
divisionId: a.string().required(),
divisionName: a.string().required(),
regionId: a.string().required(),
regionName: a.string().required(),
geographyId: a.string().required(),
geographyName: a.string().required(),
timeZone: a.string(),
groups: a.string(),
updatedYYYYMMDD: a.date(),
createdAt: a.string(),
updatedAt: a.string(),
createdBy: a.id(),
updatedBy: a.id(),
enabled: a.boolean(),
created: a.boolean(),
owner: a.string(),
version: a.string(),
notes: a.string(),
})
.secondaryIndexes((index) => [
index("resellerId"),
index("tenant"),
index("tenant").sortKeys([ "updatedYYYYMMDD"]),
])
.authorization(allow => [
allow.groupDefinedIn('tenant').to(['read', 'create', 'update']),
allow.groupDefinedIn('tenantEditGroup').to(['read', 'create', 'update']),
allow.groupDefinedIn('resellerId').to(['read', 'create', 'update']),
allow.group(tenantOwnerGroup).to(['read', 'create', 'update']),
allow.group(globalAVGAdminGroupName).to(['read', 'create', 'update', 'delete']),
]),
// #endregion InvitedTeamMember
// #region Contract
Contract: a.model({
id: a.id(),
tenant: a.id(),
resellerId: a.id(),
tenantEditGroup: a.id(),
contractNumber: a.string(),
version: a.string(),
name: a.string(),
status: a.string(), // draft, sent, approved, etc.
updatedYYYYMMDD: a.date(),
createdAt: a.string(),
updatedAt: a.string(),
createdBy: a.id(),
updatedBy: a.id(),
// Approval and Status Tracking
isApproved: a.boolean(),
approvalStatus: a.string(),
approvalDate: a.string(),
approvedById: a.id(),
approvedByName: a.string(),
updatedByName: a.string(),
changeHistoryJSON: a.string(),
//sales team information
salesPersonId: a.id(),
salesPersonName: a.string(),
salesPersonEmail: a.string(),
supportContactName: a.string(),
supportContactEmail: a.string(),
teamNamesJSON: a.string(),
territoryId: a.id(),
territoryName: a.string(),
divisionId: a.id(),
divisionName: a.string(),
regionId: a.id(),
regionName: a.string(),
geographyId: a.id(),
geographyName: a.string(),
// Customer Information
customerId: a.id(),
customer: a.belongsTo('CustomerAccount', 'customerId'),
customerCompanyName: a.string(),
customerDepartment: a.string(),
customerContactName: a.string(),
customerContactEmail: a.string(),
customerContactPhone: a.string(),
customerAddress: a.string(),
productShortListJSON: a.string(),
lineItemDetailsJSON: a.string(),
// Deal Information
quotedDate: a.string(),
approvedDate: a.string(),
trialPeriodStart: a.string(),
trialPeriodEnd: a.string(),
contractPeriodStart: a.string(),
contractPeriodEnd: a.string(),
billingMethod: a.string(),
billingFrequency: a.string(),
dealNotes: a.string(),
approvalNotes: a.string(),
// Deal Financials
dealSubTotalBeforeDiscount: a.float(),
requestedDiscount: a.float(),
approvedDiscount: a.float(),
dealDiscountAmount: a.float(),
dealSubtotal: a.float(),
salesCommission: a.float(),
dealLevelGrossMargin: a.float(),
dealCost: a.float(),
applicableTaxRate: a.float(),
taxAmount: a.float(),
dealTotal: a.float(),
currency: a.string(),
exchangeRate: a.float(),
customerDistributorId: a.id(),
customerDistributorName: a.string(),
distributorDiscount: a.float(),
customerResellerId: a.id(),
customerResellerName: a.string(),
resellerDiscount: a.float(),
requesterId: a.id(),
requesterName: a.string(),
requesterEmail: a.string(),
approverId: a.id(),
approverName: a.string(),
approverEmail: a.string(),
contractType: a.string(), // e.g. trial, paid, etc.
trialDays: a.float(),
contractDays: a.float(),
approvalComments: a.string(),
enabled: a.boolean(),
deleted: a.boolean(),
// Contract Item Details
metaDataJSON: a.string(),
// Contract Metadata and Files
contractPDF: a.string(),
relatedFiles: a.string(),
imageName: a.string(),
base64Image: a.string(),
owner: a.string(),
notes: a.string(),
})
.secondaryIndexes(index => [
index("resellerId"),
index("tenant"),
index("tenant").sortKeys([ "updatedYYYYMMDD"]),
index("tenant").sortKeys(["customerId"]),
index("territoryId"),
index("divisionId"),
index("regionId"),
index("geographyId"),
index("salesPersonId"),
index("customerId"),
])
.authorization(allow => [
allow.owner().to(['read', 'create', 'update', 'delete']),
allow.groupDefinedIn('tenant').to(['read']),
allow.groupDefinedIn('tenantEditGroup').to(['read', 'create', 'update', 'delete']),
allow.groupDefinedIn('resellerId').to(['read']),
allow.group(globalAVGAdminGroupName).to(['read', 'create', 'update', 'delete']),
]),
// #endregion Contract
// .....
// #region lambda Authorizations
}).authorization((allow) => [
allow.resource(postConfirmation),
allow.resource(manageInviteUserFunction),
allow.resource(writeGlobalLogFunction),
allow.resource(selectAPlanFunction),
allow.resource(manageDealApprovalFunction),
allow.resource(manageContactEmailFunction),
allow.resource(manageIntegrationsFunction),
allow.resource(vendorCostImporterFunction),
allow.resource(OAuth2TokenFunction),
allow.resource(CRMCallbackFunction),
allow.resource(CRMImporterFunction),
allow.resource(CRMSyncDealFunction),
allow.resource(sqlCRUDfunction),
allow.resource(getDBCountsByResellerIDFunction),
]);
const combinedSchema = a.combine([schema, sqlSchema]);
export type sqlSchema = ClientSchema<typeof sqlSchema>;
export type Schema = ClientSchema<typeof combinedSchema>;
export const data = defineData({
schema: combinedSchema,
authorizationModes: {
defaultAuthorizationMode: 'userPool',
apiKeyAuthorizationMode: {
expiresInDays: 365, // API Key expiration in days
// expires: 365, // API Key expiration in days
},
},
});