Compare commits

...

86 Commits

Author SHA1 Message Date
dependabot[bot]
3cbda2fd50 Bump newrelic from 11.0.1 to 11.1.0 (#2086)
Bumps [newrelic](https://github.com/newrelic/newrelic-python-agent) from 11.0.1 to 11.1.0.
- [Release notes](https://github.com/newrelic/newrelic-python-agent/releases)
- [Commits](https://github.com/newrelic/newrelic-python-agent/compare/v11.0.1...v11.1.0)

---
updated-dependencies:
- dependency-name: newrelic
  dependency-version: 11.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-05 12:39:56 +00:00
dependabot[bot]
2f3cdb39be Bump regex from 2025.10.23 to 2025.11.3 (#2087)
Bumps [regex](https://github.com/mrabarnett/mrab-regex) from 2025.10.23 to 2025.11.3.
- [Changelog](https://github.com/mrabarnett/mrab-regex/blob/hg/changelog.txt)
- [Commits](https://github.com/mrabarnett/mrab-regex/compare/2025.10.23...2025.11.3)

---
updated-dependencies:
- dependency-name: regex
  dependency-version: 2025.11.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-05 07:30:54 -05:00
dependabot[bot]
864f4caf76 Bump moto from 5.1.15 to 5.1.16 (#2084)
Bumps [moto](https://github.com/getmoto/moto) from 5.1.15 to 5.1.16.
- [Release notes](https://github.com/getmoto/moto/releases)
- [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getmoto/moto/compare/5.1.15...5.1.16)

---
updated-dependencies:
- dependency-name: moto
  dependency-version: 5.1.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 22:33:48 +00:00
dependabot[bot]
ceea463086 Bump hypothesis from 6.142.5 to 6.145.0 (#2085)
Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.142.5 to 6.145.0.
- [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
- [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.142.5...hypothesis-python-6.145.0)

---
updated-dependencies:
- dependency-name: hypothesis
  dependency-version: 6.145.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 13:24:20 -05:00
ccostino
b776bffb20 Merge pull request #2083 from GSA/dependabot/pip/marshmallow-4.1.0
Bump marshmallow from 4.0.1 to 4.1.0
2025-11-04 11:32:10 -05:00
dependabot[bot]
b660c4052c Bump marshmallow from 4.0.1 to 4.1.0
Bumps [marshmallow](https://github.com/marshmallow-code/marshmallow) from 4.0.1 to 4.1.0.
- [Changelog](https://github.com/marshmallow-code/marshmallow/blob/dev/CHANGELOG.rst)
- [Commits](https://github.com/marshmallow-code/marshmallow/compare/4.0.1...4.1.0)

---
updated-dependencies:
- dependency-name: marshmallow
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 21:10:55 +00:00
dependabot[bot]
065f862a52 Bump deprecated from 1.3.0 to 1.3.1 (#2080)
Bumps [deprecated](https://github.com/laurent-laporte-pro/deprecated) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/laurent-laporte-pro/deprecated/releases)
- [Changelog](https://github.com/laurent-laporte-pro/deprecated/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/laurent-laporte-pro/deprecated/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: deprecated
  dependency-version: 1.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-02 20:14:59 +00:00
dependabot[bot]
19c8f26476 Bump cyclonedx-python-lib from 11.4.0 to 11.5.0 (#2081)
Bumps [cyclonedx-python-lib](https://github.com/CycloneDX/cyclonedx-python-lib) from 11.4.0 to 11.5.0.
- [Release notes](https://github.com/CycloneDX/cyclonedx-python-lib/releases)
- [Changelog](https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/CycloneDX/cyclonedx-python-lib/compare/v11.4.0...v11.5.0)

---
updated-dependencies:
- dependency-name: cyclonedx-python-lib
  dependency-version: 11.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-02 20:04:31 +00:00
dependabot[bot]
62b8ea71a4 Bump hypothesis from 6.142.4 to 6.142.5 (#2082)
Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.142.4 to 6.142.5.
- [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
- [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.142.4...hypothesis-python-6.142.5)

---
updated-dependencies:
- dependency-name: hypothesis
  dependency-version: 6.142.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-02 14:55:17 -05:00
dependabot[bot]
4a57413dca Bump deprecated from 1.2.18 to 1.3.0 (#2077)
Bumps [deprecated](https://github.com/laurent-laporte-pro/deprecated) from 1.2.18 to 1.3.0.
- [Release notes](https://github.com/laurent-laporte-pro/deprecated/releases)
- [Changelog](https://github.com/laurent-laporte-pro/deprecated/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/laurent-laporte-pro/deprecated/compare/v1.2.18...v1.3.0)

---
updated-dependencies:
- dependency-name: deprecated
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-30 02:15:52 +00:00
dependabot[bot]
ffd23dc1a2 Bump alembic from 1.17.0 to 1.17.1 (#2076)
Bumps [alembic](https://github.com/sqlalchemy/alembic) from 1.17.0 to 1.17.1.
- [Release notes](https://github.com/sqlalchemy/alembic/releases)
- [Changelog](https://github.com/sqlalchemy/alembic/blob/main/CHANGES)
- [Commits](https://github.com/sqlalchemy/alembic/commits)

---
updated-dependencies:
- dependency-name: alembic
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-30 02:06:33 +00:00
dependabot[bot]
838fef13f2 Bump virtualenv from 20.35.3 to 20.35.4 (#2078)
Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.35.3 to 20.35.4.
- [Release notes](https://github.com/pypa/virtualenv/releases)
- [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pypa/virtualenv/compare/20.35.3...20.35.4)

---
updated-dependencies:
- dependency-name: virtualenv
  dependency-version: 20.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-29 21:58:00 -04:00
ccostino
99649b4b1b Merge pull request #2063 from GSA/2971-include-recent-template-used-per-service-within-organization
Dashboard endpoint to combine data
2025-10-29 11:34:25 -04:00
ccostino
bf68dde3f6 Merge pull request #2075 from GSA/disable-demo-infra-drift-check
Disable demo infrastructure drift check
2025-10-29 11:20:49 -04:00
Carlo Costino
fe3c54707c Disable demo infrastructure drift check
This changeset disables the infrastructure drift check for our demo environment because we are no longer using the demo environment, and the infrastructure is in a known broken state.  More work will follow on in the future to clean things up fully.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-10-29 10:27:59 -04:00
dependabot[bot]
cb4b769557 Bump aiohttp from 3.13.1 to 3.13.2 (#2074)
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-29 07:35:43 -04:00
Beverly Nguyen
e5546dddbc Remove unnecessary UUID/string conversions 2025-10-28 20:44:48 -07:00
Beverly Nguyen
e9a013753b removed converting uuid to strings 2025-10-28 20:33:28 -07:00
dependabot[bot]
aeef6bddcd Bump python-dotenv from 1.1.1 to 1.2.1 (#2071)
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.1.1 to 1.2.1.
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/theskumar/python-dotenv/compare/v1.1.1...v1.2.1)

---
updated-dependencies:
- dependency-name: python-dotenv
  dependency-version: 1.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-28 08:53:13 -04:00
ccostino
a313acee95 Merge pull request #2070 from GSA/dependabot/pip/bleach-6.3.0
Bump bleach from 6.2.0 to 6.3.0
2025-10-28 08:35:32 -04:00
dependabot[bot]
00afb7d890 Bump bleach from 6.2.0 to 6.3.0
Bumps [bleach](https://github.com/mozilla/bleach) from 6.2.0 to 6.3.0.
- [Changelog](https://github.com/mozilla/bleach/blob/main/CHANGES)
- [Commits](https://github.com/mozilla/bleach/compare/v6.2.0...v6.3.0)

---
updated-dependencies:
- dependency-name: bleach
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 12:25:03 +00:00
ccostino
2714b4bfce Merge pull request #2069 from GSA/dependabot/pip/hypothesis-6.142.4
Bump hypothesis from 6.142.3 to 6.142.4
2025-10-28 08:22:58 -04:00
Beverly Nguyen
608aa3bac6 test 2025-10-27 16:39:42 -07:00
Beverly Nguyen
2d730befd3 fix testing 2025-10-27 16:39:42 -07:00
Beverly Nguyen
5b76a75ac6 isort 2025-10-27 16:39:42 -07:00
Beverly Nguyen
e6226dd82a formatting 2025-10-27 16:39:42 -07:00
Beverly Nguyen
bc08521d83 added get_organization_dashboard 2025-10-27 16:39:42 -07:00
Beverly Nguyen
6115e0f778 Created a dashboard endpoint combines service usage data with recent template names and primary contact emails, providing all data needed for the organization dashboard table in a single API call. 2025-10-27 16:26:03 -07:00
dependabot[bot]
d7cd9a4985 Bump hypothesis from 6.142.3 to 6.142.4
Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.142.3 to 6.142.4.
- [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
- [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.142.3...hypothesis-python-6.142.4)

---
updated-dependencies:
- dependency-name: hypothesis
  dependency-version: 6.142.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 21:21:38 +00:00
ccostino
5d025df04f Merge pull request #2068 from GSA/invalidphone
remove logging messages
2025-10-27 16:09:18 -04:00
ccostino
d7c716baf4 Merge pull request #2062 from GSA/terraform_upgrade2
add second provider to all tiers
2025-10-27 14:24:38 -04:00
ccostino
9ff68111a9 Merge pull request #2066 from GSA/org_tests
fix test
2025-10-27 13:17:17 -04:00
ccostino
81a68ba4f3 Merge pull request #2067 from GSA/dependabot/pip/pip-25.3
Bump pip from 25.2 to 25.3
2025-10-27 13:07:59 -04:00
dependabot[bot]
5b897c9d59 Bump pip from 25.2 to 25.3
Bumps [pip](https://github.com/pypa/pip) from 25.2 to 25.3.
- [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/pip/compare/25.2...25.3)

---
updated-dependencies:
- dependency-name: pip
  dependency-version: '25.3'
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 16:38:35 +00:00
Kenneth Kehl
d0a7ba5791 remove logging messages 2025-10-27 09:14:57 -07:00
dependabot[bot]
9ba2e8b1bf Bump flake8-bugbear from 24.12.12 to 25.10.21 (#2052)
* Bump flake8-bugbear from 24.12.12 to 25.10.21

Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 24.12.12 to 25.10.21.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/24.12.12...25.10.21)

---
updated-dependencies:
- dependency-name: flake8-bugbear
  dependency-version: 25.10.21
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix flake8-bugbear B042 violations in exception classes

* Trigger CI re-run

* Regenerate poetry.lock with Poetry 2.1.3 for CI compatibility

* Regenerated poetry hash again

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Janousek <alex.janousek@gsa.gov>
2025-10-27 15:52:51 +00:00
Kenneth Kehl
b760d45c8b fix test 2025-10-27 07:59:59 -07:00
ccostino
61ce3b0b5c Merge pull request #2065 from GSA/dependabot/pip/phonenumbers-9.0.17
Bump phonenumbers from 9.0.16 to 9.0.17
2025-10-27 08:55:36 -04:00
dependabot[bot]
a9f09f5f10 Bump phonenumbers from 9.0.16 to 9.0.17
Bumps [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) from 9.0.16 to 9.0.17.
- [Commits](https://github.com/daviddrysdale/python-phonenumbers/compare/v9.0.16...v9.0.17)

---
updated-dependencies:
- dependency-name: phonenumbers
  dependency-version: 9.0.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 12:34:25 +00:00
ccostino
585fef8ae9 Merge pull request #2064 from GSA/dependabot/pip/faker-37.12.0
Bump faker from 37.11.0 to 37.12.0
2025-10-27 08:27:38 -04:00
dependabot[bot]
134ff85e99 Bump faker from 37.11.0 to 37.12.0
Bumps [faker](https://github.com/joke2k/faker) from 37.11.0 to 37.12.0.
- [Release notes](https://github.com/joke2k/faker/releases)
- [Changelog](https://github.com/joke2k/faker/blob/master/CHANGELOG.md)
- [Commits](https://github.com/joke2k/faker/compare/v37.11.0...v37.12.0)

---
updated-dependencies:
- dependency-name: faker
  dependency-version: 37.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 21:05:50 +00:00
Kenneth Kehl
b52df1a561 add second provider to all tiers 2025-10-24 11:48:14 -07:00
dependabot[bot]
9d332475b7 Bump cyclonedx-python-lib from 11.3.0 to 11.4.0 (#2058)
Bumps [cyclonedx-python-lib](https://github.com/CycloneDX/cyclonedx-python-lib) from 11.3.0 to 11.4.0.
- [Release notes](https://github.com/CycloneDX/cyclonedx-python-lib/releases)
- [Changelog](https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/CycloneDX/cyclonedx-python-lib/compare/v11.3.0...v11.4.0)

---
updated-dependencies:
- dependency-name: cyclonedx-python-lib
  dependency-version: 11.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-24 14:07:20 -04:00
ccostino
3eff196cd4 Merge pull request #2059 from GSA/terraform_upgrade1
rebuild module.ses_email
2025-10-24 12:51:23 -04:00
Kenneth Kehl
287c051234 revert 2025-10-24 09:30:12 -07:00
Kenneth Kehl
f447f2b962 revert 2025-10-24 09:29:20 -07:00
Kenneth Kehl
96a584a124 revert 2025-10-24 09:24:08 -07:00
Kenneth Kehl
1081073293 revert 2025-10-24 09:17:24 -07:00
Kenneth Kehl
c88c25d167 rebuild module.ses_email 2025-10-24 08:57:09 -07:00
Kenneth Kehl
4579270830 rebuild module.ses_email 2025-10-24 08:37:04 -07:00
Kenneth Kehl
cfb645c5ad rebuild module.ses_email 2025-10-24 08:28:26 -07:00
Kenneth Kehl
08f8104f76 rebuild module.ses_email 2025-10-24 08:12:33 -07:00
Kenneth Kehl
e3e44f68b1 rebuild module.ses_email 2025-10-24 08:00:46 -07:00
Kenneth Kehl
bb6438f29b Merge pull request #2057 from GSA/terraform_upgrade1
migrate module.ses_email to new provider
2025-10-24 07:59:28 -07:00
Kenneth Kehl
43beeae5c6 try again 2025-10-23 13:17:54 -07:00
Kenneth Kehl
2db396e0c0 try again 2025-10-23 13:00:23 -07:00
Kenneth Kehl
7d63d3b9d3 try again 2025-10-23 12:53:51 -07:00
Kenneth Kehl
b81fe0b246 migrate csv_upload_bucket to new provider 2025-10-23 11:35:27 -07:00
ccostino
a907a63a97 Merge pull request #2056 from GSA/terraform_upgrade1
restore module redis-v70
2025-10-23 14:23:16 -04:00
Kenneth Kehl
9fa3db8a95 restore module redis-v70 2025-10-23 11:06:17 -07:00
Kenneth Kehl
6d644a9e41 restore module redis-v70 2025-10-23 10:57:25 -07:00
ccostino
f686fee2eb Merge pull request #2055 from GSA/terraform_upgrade1
start upgrading terraform provider
2025-10-23 13:29:45 -04:00
Kenneth Kehl
41f12cce1f whoops 2025-10-23 09:54:27 -07:00
Kenneth Kehl
1af8e7ccfb start upgrading terraform provider 2025-10-23 09:50:56 -07:00
ccostino
375336d455 Merge pull request #2053 from GSA/dependabot/pip/hypothesis-6.142.3
Bump hypothesis from 6.142.2 to 6.142.3
2025-10-22 17:30:55 -04:00
dependabot[bot]
edf3cd3740 Bump hypothesis from 6.142.2 to 6.142.3
Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.142.2 to 6.142.3.
- [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
- [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.142.2...hypothesis-python-6.142.3)

---
updated-dependencies:
- dependency-name: hypothesis
  dependency-version: 6.142.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-22 21:21:32 +00:00
ccostino
8699e65590 Merge pull request #2051 from GSA/dependabot/pip/cyclonedx-python-lib-11.3.0
Bump cyclonedx-python-lib from 11.2.0 to 11.3.0
2025-10-22 17:19:21 -04:00
dependabot[bot]
dd6124b83d Bump cyclonedx-python-lib from 11.2.0 to 11.3.0
Bumps [cyclonedx-python-lib](https://github.com/CycloneDX/cyclonedx-python-lib) from 11.2.0 to 11.3.0.
- [Release notes](https://github.com/CycloneDX/cyclonedx-python-lib/releases)
- [Changelog](https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/CycloneDX/cyclonedx-python-lib/compare/v11.2.0...v11.3.0)

---
updated-dependencies:
- dependency-name: cyclonedx-python-lib
  dependency-version: 11.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-22 21:08:23 +00:00
Beverly Nguyen
92ecdffa3d Merge pull request #2050 from GSA/2968-per-service-quota-display
Get service usage
2025-10-22 12:15:18 -07:00
dependabot[bot]
ec300c0dff Bump regex from 2025.9.18 to 2025.10.23 (#2049)
Bumps [regex](https://github.com/mrabarnett/mrab-regex) from 2025.9.18 to 2025.10.23.
- [Changelog](https://github.com/mrabarnett/mrab-regex/blob/hg/changelog.txt)
- [Commits](https://github.com/mrabarnett/mrab-regex/compare/2025.9.18...2025.10.23)

---
updated-dependencies:
- dependency-name: regex
  dependency-version: 2025.10.23
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-22 09:01:56 -04:00
Beverly Nguyen
3a82ef7fd3 we don't need created_at here 2025-10-21 15:32:18 -07:00
Beverly Nguyen
09907cc4af removed the next() lookup 2025-10-21 14:50:30 -07:00
Beverly Nguyen
0267c52098 updating imports 2025-10-21 14:42:19 -07:00
Beverly Nguyen
ccec5758ec get service usage 2025-10-21 14:06:54 -07:00
dependabot[bot]
b852e6e56e Bump hypothesis from 6.142.1 to 6.142.2 (#2048)
Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.142.1 to 6.142.2.
- [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
- [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.142.1...hypothesis-python-6.142.2)

---
updated-dependencies:
- dependency-name: hypothesis
  dependency-version: 6.142.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-21 09:13:14 -04:00
ccostino
82a11edf27 Merge pull request #2043 from GSA/2967-calculate-an-organizations-message-usage-for-current-agreement-period
Add organization message totals endpoint
2025-10-20 11:10:26 -04:00
dependabot[bot]
1501547cf3 Bump cryptography from 46.0.2 to 46.0.3 (#2044)
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.2 to 46.0.3.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/46.0.2...46.0.3)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 14:35:39 +00:00
dependabot[bot]
a0c256528b Bump hypothesis from 6.141.1 to 6.142.1 (#2045)
Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.141.1 to 6.142.1.
- [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
- [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.141.1...hypothesis-python-6.142.1)

---
updated-dependencies:
- dependency-name: hypothesis
  dependency-version: 6.142.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 10:26:27 -04:00
dependabot[bot]
c8b1049ef1 Bump moto from 5.1.14 to 5.1.15 (#2046)
Bumps [moto](https://github.com/getmoto/moto) from 5.1.14 to 5.1.15.
- [Release notes](https://github.com/getmoto/moto/releases)
- [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getmoto/moto/compare/5.1.14...5.1.15)

---
updated-dependencies:
- dependency-name: moto
  dependency-version: 5.1.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 04:54:32 -04:00
dependabot[bot]
fdf69dfe90 Bump aiohttp from 3.13.0 to 3.13.1 (#2047)
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.13.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 01:11:38 -04:00
Beverly Nguyen
27d7efb19e update test 2025-10-16 15:05:48 -07:00
Beverly Nguyen
1a4eafb78e update route name 2025-10-16 14:07:44 -07:00
Beverly Nguyen
7febff7628 Add organization message totals endpoint
- Add GET /organizations/<org_id>/message-totals endpoint
  - Returns aggregated messages_sent, messages_remaining, and total_message_limit
  - Add dao_get_notification_counts_for_organization() for bulk queries
2025-10-16 12:37:44 -07:00
dependabot[bot]
95f24f7341 Bump numpy from 2.3.3 to 2.3.4 (#2041)
Bumps [numpy](https://github.com/numpy/numpy) from 2.3.3 to 2.3.4.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](https://github.com/numpy/numpy/compare/v2.3.3...v2.3.4)

---
updated-dependencies:
- dependency-name: numpy
  dependency-version: 2.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 13:43:48 +00:00
dependabot[bot]
66a8554007 Bump cyclonedx-python-lib from 11.1.0 to 11.2.0 (#2040)
Bumps [cyclonedx-python-lib](https://github.com/CycloneDX/cyclonedx-python-lib) from 11.1.0 to 11.2.0.
- [Release notes](https://github.com/CycloneDX/cyclonedx-python-lib/releases)
- [Changelog](https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/CycloneDX/cyclonedx-python-lib/compare/v11.1.0...v11.2.0)

---
updated-dependencies:
- dependency-name: cyclonedx-python-lib
  dependency-version: 11.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 13:33:14 +00:00
dependabot[bot]
e414a50e20 Bump hypothesis from 6.141.0 to 6.141.1 (#2042)
Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.141.0 to 6.141.1.
- [Release notes](https://github.com/HypothesisWorks/hypothesis/releases)
- [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.141.0...hypothesis-python-6.141.1)

---
updated-dependencies:
- dependency-name: hypothesis
  dependency-version: 6.141.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 09:23:10 -04:00
24 changed files with 958 additions and 553 deletions

View File

@@ -36,6 +36,7 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
run: terraform init
- name: Terraform apply
working-directory: terraform/staging
env:

View File

@@ -44,44 +44,44 @@ jobs:
exit $exit_code
fi
check_demo_drift:
runs-on: ubuntu-latest
name: Check for drift of demo terraform configuration
environment: demo
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: 'production'
# check_demo_drift:
# runs-on: ubuntu-latest
# name: Check for drift of demo terraform configuration
# environment: demo
# steps:
# - name: Checkout
# uses: actions/checkout@v4
# with:
# ref: 'production'
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
# # Looks like we need to install Terraform ourselves now!
# # https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
# - name: Setup Terraform
# uses: hashicorp/setup-terraform@v3
# with:
# terraform_version: "^1.7.5"
# terraform_wrapper: false
- name: Check for drift
env:
AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }}
TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
run: |
cd terraform/demo
terraform init
terraform plan -detailed-exitcode
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "No changes detected. Intrastructure is up-to-date."
elif [ $exit_code -eq 2 ]; then
echo "Changes detected. Infrastructure drift found."
exit 1
else
echo "Error running terraform plan."
exit $exit_code
fi
# - name: Check for drift
# env:
# AWS_ACCESS_KEY_ID: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }}
# AWS_SECRET_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_SECRET_ACCESS_KEY }}
# TF_VAR_cf_user: ${{ secrets.CLOUDGOV_USERNAME }}
# TF_VAR_cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
# run: |
# cd terraform/demo
# terraform init
# terraform plan -detailed-exitcode
# exit_code=$?
# if [ $exit_code -eq 0 ]; then
# echo "No changes detected. Intrastructure is up-to-date."
# elif [ $exit_code -eq 2 ]; then
# echo "Changes detected. Infrastructure drift found."
# exit 1
# else
# echo "Error running terraform plan."
# exit $exit_code
# fi
check_prod_drift:
runs-on: ubuntu-latest

View File

@@ -41,6 +41,7 @@ jobs:
id: validation
run: terraform validate -no-color
- name: Terraform plan
id: plan
env:

View File

@@ -29,6 +29,7 @@ GENERAL_TOKEN_ERROR_MESSAGE = TOKEN_MESSAGE_ONE + TOKEN_MESSAGE_TWO
class AuthError(Exception):
def __init__(self, message, code, service_id=None, api_key_id=None):
super().__init__(message, code, service_id, api_key_id)
self.message = {"token": [message]}
self.short_message = message
self.code = code

View File

@@ -4,6 +4,7 @@ from flask import current_app
class DocumentDownloadError(Exception):
def __init__(self, message, status_code):
super().__init__(message, status_code)
self.message = message
self.status_code = status_code

View File

@@ -10,6 +10,7 @@ class SmsClientResponseException(ClientException):
"""
def __init__(self, message):
super().__init__(message)
self.message = message
def __str__(self):

View File

@@ -7,7 +7,10 @@ from sqlalchemy.sql.expression import case, literal
from app import db
from app.dao.date_util import get_calendar_year_dates, get_calendar_year_for_datetime
from app.dao.organization_dao import dao_get_organization_live_services
from app.dao.organization_dao import (
dao_get_organization_live_services,
dao_get_organization_services,
)
from app.enums import KeyType, NotificationStatus, NotificationType
from app.models import (
AnnualBilling,
@@ -613,6 +616,7 @@ def fetch_sms_billing_for_organization(organization_id, financial_year):
func.coalesce(chargeable_sms, 0).label("chargeable_billable_sms"),
func.coalesce(sms_cost, 0).label("sms_cost"),
Service.active,
Service.restricted,
)
.select_from(Service)
.outerjoin(
@@ -695,10 +699,16 @@ def query_organization_sms_usage_for_year(organization_id, year):
)
def fetch_usage_year_for_organization(organization_id, year):
def fetch_usage_year_for_organization(
organization_id, year, include_all_services=False
):
year_start, year_end = get_calendar_year_dates(year)
today = utc_now().date()
services = dao_get_organization_live_services(organization_id)
if include_all_services:
services = dao_get_organization_services(organization_id)
else:
services = dao_get_organization_live_services(organization_id)
# if year end date is less than today, we are calculating for data in the past and have no need for deltas.
if year_end >= today:
@@ -709,7 +719,7 @@ def fetch_usage_year_for_organization(organization_id, year):
service_with_usage = {}
# initialise results
for service in services:
service_with_usage[str(service.id)] = {
service_with_usage[service.id] = {
"service_id": service.id,
"service_name": service.name,
"free_sms_limit": 0,
@@ -719,13 +729,14 @@ def fetch_usage_year_for_organization(organization_id, year):
"sms_cost": 0.0,
"emails_sent": 0,
"active": service.active,
"restricted": service.restricted,
}
sms_usages = fetch_sms_billing_for_organization(organization_id, year)
email_usages = fetch_email_usage_for_organization(
organization_id, year_start, year_end
)
for usage in sms_usages:
service_with_usage[str(usage.service_id)] = {
service_with_usage[usage.service_id] = {
"service_id": usage.service_id,
"service_name": usage.service_name,
"free_sms_limit": usage.free_sms_fragment_limit,
@@ -735,9 +746,10 @@ def fetch_usage_year_for_organization(organization_id, year):
"sms_cost": float(usage.sms_cost),
"emails_sent": 0,
"active": usage.active,
"restricted": usage.restricted,
}
for email_usage in email_usages:
service_with_usage[str(email_usage.service_id)][
service_with_usage[email_usage.service_id][
"emails_sent"
] = email_usage.emails_sent

View File

@@ -27,7 +27,12 @@ from app import create_uuid, db
from app.dao.dao_utils import autocommit
from app.dao.inbound_sms_dao import Pagination
from app.enums import KeyType, NotificationStatus, NotificationType
from app.models import FactNotificationStatus, Notification, NotificationHistory
from app.models import (
FactNotificationStatus,
Notification,
NotificationHistory,
Template,
)
from app.utils import (
escape_special_characters,
get_midnight_in_utc,
@@ -340,6 +345,86 @@ def dao_get_notification_count_for_service_message_ratio(service_id, current_yea
return recent_count + old_count
def dao_get_notification_counts_per_service(service_ids, current_year):
"""
Get notification counts for multiple services in a single organization.
"""
if not service_ids:
return {}
start_date = datetime(current_year, 6, 16)
end_date = datetime(current_year + 1, 6, 16)
stmt1 = (
select(Notification.service_id, func.count().label("count"))
.where(
Notification.service_id.in_(service_ids),
Notification.status
not in [
NotificationStatus.CANCELLED,
NotificationStatus.CREATED,
NotificationStatus.SENDING,
],
Notification.created_at >= start_date,
Notification.created_at < end_date,
)
.group_by(Notification.service_id)
)
stmt2 = (
select(NotificationHistory.service_id, func.count().label("count"))
.where(
NotificationHistory.service_id.in_(service_ids),
NotificationHistory.status
not in [
NotificationStatus.CANCELLED,
NotificationStatus.CREATED,
NotificationStatus.SENDING,
],
NotificationHistory.created_at >= start_date,
NotificationHistory.created_at < end_date,
)
.group_by(NotificationHistory.service_id)
)
result_dict = {}
recent_results = db.session.execute(stmt1).all()
for service_id, count in recent_results:
result_dict[service_id] = count
history_results = db.session.execute(stmt2).all()
for service_id, count in history_results:
result_dict[service_id] = result_dict.get(service_id, 0) + count
return result_dict
def dao_get_recent_sms_template_per_service(service_ids):
if not service_ids:
return {}
stmt = (
select(
Notification.service_id,
Template.name.label("template_name"),
)
.join(Template, Template.id == Notification.template_id)
.where(
Notification.service_id.in_(service_ids),
Notification.notification_type == NotificationType.SMS,
Notification.key_type != KeyType.TEST,
)
.distinct(Notification.service_id)
.order_by(Notification.service_id, desc(Notification.created_at))
)
results = db.session.execute(stmt).all()
return {service_id: template_name for service_id, template_name in results}
def dao_get_failed_notification_count():
stmt = select(func.count(Notification.id)).where(
Notification.status == NotificationStatus.FAILED

View File

@@ -254,6 +254,21 @@ def dao_fetch_all_services_created_by_user(user_id):
return db.session.execute(stmt).scalars().all()
def dao_get_service_primary_contacts(service_ids):
if not service_ids:
return {}
stmt = select(
Service.id.label("service_id"),
Service.billing_contact_email_addresses.label("email_address"),
).where(Service.id.in_(service_ids))
results = db.session.execute(stmt).all()
return {service_id: email_address for service_id, email_address in results}
@autocommit
@version_class(
VersionOptions(ApiKey, must_write_history=False),

View File

@@ -15,7 +15,7 @@ class InvalidRequest(Exception):
fields = []
def __init__(self, message, status_code):
super().__init__()
super().__init__(message, status_code)
self.message = message
self.status_code = status_code
@@ -115,16 +115,20 @@ class TooManyRequestsError(InvalidRequest):
status_code = 429
message_template = "Exceeded send limits ({}) for today"
def __init__(self, sending_limit):
def __init__(self, sending_limit): # noqa: B042
self.message = self.message_template.format(sending_limit)
self.sending_limit = sending_limit
super().__init__(self.message, self.status_code)
class TotalRequestsError(InvalidRequest):
status_code = 429
message_template = "Exceeded total application limits ({}) for today"
def __init__(self, sending_limit):
def __init__(self, sending_limit): # noqa: B042
self.message = self.message_template.format(sending_limit)
self.sending_limit = sending_limit
super().__init__(self.message, self.status_code)
class RateLimitError(InvalidRequest):
@@ -133,7 +137,7 @@ class RateLimitError(InvalidRequest):
"Exceeded rate limit for key type {} of {} requests per {} seconds"
)
def __init__(self, sending_limit, interval, key_type):
def __init__(self, sending_limit, interval, key_type): # noqa: B042
# normal keys are spoken of as "live" in the documentation
# so using this in the error messaging
if key_type == KeyType.NORMAL:
@@ -142,12 +146,17 @@ class RateLimitError(InvalidRequest):
self.message = self.message_template.format(
key_type.upper(), sending_limit, interval
)
self.sending_limit = sending_limit
self.interval = interval
self.key_type = key_type
super().__init__(self.message, self.status_code)
class BadRequestError(InvalidRequest):
message = "An error occurred"
def __init__(self, fields=None, message=None, status_code=400):
self.status_code = status_code
def __init__(self, fields=None, message=None, status_code=400): # noqa: B042
self.fields = fields or []
self.message = message if message else self.message
self.status_code = status_code
super().__init__(self.message, self.status_code)

View File

@@ -1,5 +1,6 @@
class DVLAException(Exception):
def __init__(self, message):
super().__init__(message)
self.message = message

View File

@@ -1,4 +1,6 @@
import json
from datetime import datetime
from zoneinfo import ZoneInfo
from flask import Blueprint, abort, current_app, jsonify, request
from sqlalchemy.exc import IntegrityError
@@ -8,6 +10,10 @@ from app.config import QueueNames
from app.dao.annual_billing_dao import set_default_free_allowance_for_service
from app.dao.dao_utils import transaction
from app.dao.fact_billing_dao import fetch_usage_year_for_organization
from app.dao.notifications_dao import (
dao_get_notification_counts_per_service,
dao_get_recent_sms_template_per_service,
)
from app.dao.organization_dao import (
dao_add_service_to_organization,
dao_add_user_to_organization,
@@ -20,7 +26,10 @@ from app.dao.organization_dao import (
dao_remove_user_from_organization,
dao_update_organization,
)
from app.dao.services_dao import dao_fetch_service_by_id
from app.dao.services_dao import (
dao_fetch_service_by_id,
dao_get_service_primary_contacts,
)
from app.dao.templates_dao import dao_get_template_by_id
from app.dao.users_dao import get_user_by_id
from app.enums import KeyType
@@ -148,6 +157,7 @@ def get_organization_services_usage(organization_id):
year = int(request.args.get("year", "none"))
except ValueError:
return jsonify(result="error", message="No valid year provided"), 400
services = fetch_usage_year_for_organization(organization_id, year)
list_services = services.values()
sorted_services = sorted(
@@ -156,6 +166,47 @@ def get_organization_services_usage(organization_id):
return jsonify(services=sorted_services)
@organization_blueprint.route("/<uuid:organization_id>/dashboard", methods=["GET"])
def get_organization_dashboard(organization_id):
check_suspicious_id(organization_id)
try:
year = int(request.args.get("year", "none"))
except ValueError:
return jsonify(result="error", message="No valid year provided"), 400
services_with_usage = fetch_usage_year_for_organization(
organization_id, year, include_all_services=True
)
service_ids = [service_data["service_id"] for service_data in services_with_usage.values()]
if not service_ids:
return jsonify(services=[]), 200
recent_templates = dao_get_recent_sms_template_per_service(service_ids)
primary_contacts = dao_get_service_primary_contacts(service_ids)
for service_data in services_with_usage.values():
service_uuid = service_data["service_id"]
service_data["recent_sms_template_name"] = recent_templates.get(service_uuid)
service_data["primary_contact"] = primary_contacts.get(service_uuid)
services_list = list(services_with_usage.values())
sorted_services = sorted(
services_list,
key=lambda s: (
0 if (s["active"] and not s["restricted"]) else
1 if (s["active"] and s["restricted"]) else
2,
s["service_name"].lower()
)
)
return jsonify(services=sorted_services)
@organization_blueprint.route(
"/<uuid:organization_id>/users/<uuid:user_id>", methods=["POST"]
)
@@ -259,3 +310,49 @@ def send_notifications_on_mou_signed(organization_id):
organization.agreement_signed_by.email_address,
personalisation,
)
@organization_blueprint.route(
"/<uuid:organization_id>/message-allowance", methods=["GET"]
)
def get_organization_message_allowance(organization_id):
check_suspicious_id(organization_id)
dao_get_organization_by_id(organization_id)
services = dao_get_organization_services(organization_id)
if not services:
return (
jsonify(
{
"messages_sent": 0,
"messages_remaining": 0,
"total_message_limit": 0,
}
),
200,
)
current_year = datetime.now(tz=ZoneInfo("UTC")).year
service_ids = [service.id for service in services]
messages_by_service = dao_get_notification_counts_per_service(
service_ids, current_year
)
total_messages_sent = sum(messages_by_service.get(s.id, 0) for s in services)
total_message_limit = sum(s.total_message_limit for s in services)
total_messages_remaining = total_message_limit - total_messages_sent
return (
jsonify(
{
"messages_sent": total_messages_sent,
"messages_remaining": total_messages_remaining,
"total_message_limit": total_message_limit,
}
),
200,
)

View File

@@ -18,6 +18,7 @@ class TokenError(Exception):
else TOKEN_ERROR_DEFAULT_ERROR_MESSAGE
)
self.token = token
super().__init__(self.message, token)
class TokenExpiredError(TokenError):
@@ -48,6 +49,7 @@ class APIError(Exception):
def __init__(self, response: Response = None, message: str = None):
self.response = response
self._message = message
super().__init__(response, message)
def __str__(self):
return f"{self.status_code} - {self.message}"

View File

@@ -4,6 +4,7 @@ from flask import current_app
class AntivirusError(Exception):
def __init__(self, message=None, status_code=None):
super().__init__(message, status_code)
self.message = message
self.status_code = status_code

View File

@@ -4,6 +4,7 @@ from flask import current_app
class ZendeskError(Exception):
def __init__(self, response):
super().__init__(str(response))
self.response = response

View File

@@ -515,9 +515,6 @@ def is_us_phone_number(number):
try:
return _get_country_code(number) == us_prefix
except NumberParseException:
current_app.logger.exception(
"NumberParseException checking if phone number is US number"
)
return False
@@ -606,7 +603,6 @@ def validate_us_phone_number(number):
raise InvalidPhoneError("Phone number range is not in use")
raise InvalidPhoneError("Phone number is not possible")
except NumberParseException as exc:
current_app.logger.exception("NumberParseException validating US phone number")
raise InvalidPhoneError(exc._msg) from exc
@@ -663,9 +659,6 @@ def try_validate_and_format_phone_number(number, international=None, log_msg=Non
try:
return validate_and_format_phone_number(number, international)
except InvalidPhoneError as exc:
current_app.logger.exception(
"InvalidPhoneNumber while trying to validate and format phone number"
)
if log_msg:
current_app.logger.warning("{}: {}".format(log_msg, exc))
return number

937
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ readme = "README.md"
[tool.poetry.dependencies]
python = "^3.13.2"
alembic = "==1.17.0"
alembic = "==1.17.1"
amqp = "==5.3.1"
beautifulsoup4 = "==4.14.2"
boto3 = "^1.38.27"
@@ -24,7 +24,7 @@ click-datetime = "==0.4.0"
click-didyoumean = "==0.3.1"
click-plugins = "==1.1.1.2"
click-repl = "==0.3.0"
deprecated = "==1.2.18"
deprecated = "==1.3.1"
gevent = "==25.5.1"
expiringdict = "==1.2.2"
flask = "~=3.1"
@@ -36,30 +36,30 @@ gunicorn = {version = "==23.0.0", extras = ["gevent"]}
iso8601 = "==2.1.0"
jsonschema = {version = "==4.25.1", extras = ["format"]}
lxml = "==6.0.2"
marshmallow = "^4.0.1"
marshmallow = "^4.1.0"
marshmallow-sqlalchemy = "^1.4.2"
newrelic = "^11.0.1"
newrelic = "^11.1.0"
packaging = "==25.0"
poetry-dotenv-plugin = "==0.2.0"
psycopg2-binary = "==2.9.11"
pyjwt = "==2.10.1"
python-dotenv = "==1.1.1"
python-dotenv = "==1.2.1"
sqlalchemy = "==2.0.44"
werkzeug = "^3.0.6"
faker = "^37.8.0"
faker = "^37.12.0"
async-timeout = "^5.0.1"
bleach = "^6.1.0"
bleach = "^6.3.0"
geojson = "^3.2.0"
numpy = "^2.3.3"
numpy = "^2.3.4"
ordered-set = "^4.1.0"
phonenumbers = "^9.0.16"
phonenumbers = "^9.0.17"
python-json-logger = "^4.0.0"
regex = "^2025.9.18"
regex = "^2025.11.3"
shapely = "^2.1.2"
smartypants = "^2.0.1"
mistune = "^3.1.4"
blinker = "^1.9.0"
cryptography = "^46.0.2"
cryptography = "^46.0.3"
idna = "^3.11"
jmespath = "^1.0.1"
markupsafe = "^3.0.3"
@@ -74,11 +74,11 @@ itsdangerous = "^2.2.0"
jinja2 = "^3.1.6"
redis = "^5.0.8"
requests = "^2.32.5"
virtualenv = "^20.35.3"
virtualenv = "^20.35.4"
marshmallow-enum = "^1.5.1"
awscli = "^1.40.36"
typing-extensions = "^4.15.0"
aiohttp = "^3.12.15"
aiohttp = "^3.13.2"
pytest = "^8.4.2"
@@ -86,17 +86,17 @@ pytest = "^8.4.2"
awscli = "^1.33.32"
bandit = "*"
black = "^25.9.0"
cyclonedx-python-lib = "^11.1.0"
cyclonedx-python-lib = "^11.5.0"
cloudfoundry-client = "*"
exceptiongroup = "==1.3.0"
flake8 = "^7.3.0"
flake8-bugbear = "^24.12.12"
flake8-bugbear = "^25.10.21"
freezegun = "^1.5.5"
hypothesis = "^6.141.0"
hypothesis = "^6.145.1"
honcho = "*"
isort = "^7.0.0"
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
moto = "==5.1.14"
moto = "==5.1.16"
pip-audit = "*"
pre-commit = "^4.3.0"
pytest = "^8.4.2"

View File

@@ -5,6 +5,10 @@ terraform {
source = "cloudfoundry-community/cloudfoundry"
version = "0.53.1"
}
cfcommunity = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.53.1"
}
}
backend "s3" {
@@ -16,7 +20,16 @@ terraform {
}
}
# Official provider (should be default but aliased for now)
provider "cloudfoundry" {
alias = "official"
api_url = "https://api.fr.cloud.gov"
user = var.cf_user
password = var.cf_password
}
# Community provider (should be aliased but default for now)
provider "cfcommunity" {
api_url = "https://api.fr.cloud.gov"
user = var.cf_user
password = var.cf_password

View File

@@ -5,6 +5,10 @@ terraform {
source = "cloudfoundry-community/cloudfoundry"
version = "0.53.1"
}
cfcommunity = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.53.1"
}
}
backend "s3" {
@@ -16,7 +20,16 @@ terraform {
}
}
# Official provider (should be default but aliased for now)
provider "cloudfoundry" {
alias = "official"
api_url = "https://api.fr.cloud.gov"
user = var.cf_user
password = var.cf_password
}
# Community provider (should be aliased but default for now)
provider "cfcommunity" {
api_url = "https://api.fr.cloud.gov"
user = var.cf_user
password = var.cf_password

View File

@@ -12,6 +12,13 @@ resource "null_resource" "prevent_destroy" {
}
}
data "cloudfoundry_space" "space" {
provider = cloudfoundry.official
org = "9e428562-a2d9-41b4-9c23-1ef5237fb44e"
name = local.cf_space_name
}
module "database" {
source = "github.com/GSA-TTS/terraform-cloudgov//database?ref=v1.0.0"
@@ -21,11 +28,14 @@ module "database" {
rds_plan_name = "small-psql"
}
module "redis-v70" {
source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v1.0.0"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
module "redis-v70" {
source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v2.4.0"
# Right now the default is cfcommunity, remove this when default is cloudfoundry
providers = {
cloudfoundry = cloudfoundry.official
}
cf_space_id = data.cloudfoundry_space.space.id
name = "${local.app_name}-redis-v70-${local.env}"
redis_plan_name = "redis-dev"
json_params = jsonencode(
@@ -55,8 +65,7 @@ module "egress-space" {
}
module "ses_email" {
source = "../shared/ses"
source = "../shared/ses"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
name = "${local.app_name}-ses-${local.env}"

View File

@@ -2,6 +2,10 @@ terraform {
required_version = "~> 1.7"
required_providers {
cloudfoundry = {
source = "cloudfoundry/cloudfoundry"
version = "1.9.0"
}
cfcommunity = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.53.1"
}
@@ -16,7 +20,16 @@ terraform {
}
}
# Official provider (should be default but aliased for now)
provider "cloudfoundry" {
alias = "official"
api_url = "https://api.fr.cloud.gov"
user = var.cf_user
password = var.cf_password
}
# Community provider (should be aliased but default for now)
provider "cfcommunity" {
api_url = "https://api.fr.cloud.gov"
user = var.cf_user
password = var.cf_password

View File

@@ -924,7 +924,7 @@ def test_fetch_usage_year_for_organization(notify_db_session):
results = fetch_usage_year_for_organization(fixtures["org_1"].id, 2019)
assert len(results) == 3
first_row = results[str(fixtures["service_1_sms_and_letter"].id)]
first_row = results[fixtures["service_1_sms_and_letter"].id]
assert first_row["service_id"] == fixtures["service_1_sms_and_letter"].id
assert first_row["service_name"] == fixtures["service_1_sms_and_letter"].name
assert first_row["free_sms_limit"] == 10
@@ -933,7 +933,7 @@ def test_fetch_usage_year_for_organization(notify_db_session):
assert first_row["sms_cost"] == 0.0
assert first_row["emails_sent"] == 0
second_row = results[str(service_with_emails_for_org.id)]
second_row = results[service_with_emails_for_org.id]
assert second_row["service_id"] == service_with_emails_for_org.id
assert second_row["service_name"] == service_with_emails_for_org.name
assert second_row["free_sms_limit"] == 0
@@ -942,7 +942,7 @@ def test_fetch_usage_year_for_organization(notify_db_session):
assert second_row["sms_cost"] == 0
assert second_row["emails_sent"] == 1100
third_row = results[str(fixtures["service_with_out_ft_billing_this_year"].id)]
third_row = results[fixtures["service_with_out_ft_billing_this_year"].id]
assert (
third_row["service_id"] == fixtures["service_with_out_ft_billing_this_year"].id
)
@@ -1025,11 +1025,11 @@ def test_fetch_usage_year_for_organization_calculates_cost_from_multiple_rates(
)
assert len(results) == 1
assert results[str(service_1.id)]["free_sms_limit"] == 3
assert results[str(service_1.id)]["sms_remainder"] == 0
assert results[str(service_1.id)]["sms_billable_units"] == 6
assert results[str(service_1.id)]["chargeable_billable_sms"] == 3
assert results[str(service_1.id)]["sms_cost"] == 8.0
assert results[service_1.id]["free_sms_limit"] == 3
assert results[service_1.id]["sms_remainder"] == 0
assert results[service_1.id]["sms_billable_units"] == 6
assert results[service_1.id]["chargeable_billable_sms"] == 3
assert results[service_1.id]["sms_cost"] == 8.0
@freeze_time("2022-05-01 13:30")
@@ -1051,11 +1051,11 @@ def test_fetch_usage_year_for_organization_when_no_usage(notify_db_session):
)
assert len(results) == 1
assert results[str(service_1.id)]["free_sms_limit"] == 3
assert results[str(service_1.id)]["sms_remainder"] == 3
assert results[str(service_1.id)]["sms_billable_units"] == 0
assert results[str(service_1.id)]["chargeable_billable_sms"] == 0
assert results[str(service_1.id)]["sms_cost"] == 0.0
assert results[service_1.id]["free_sms_limit"] == 3
assert results[service_1.id]["sms_remainder"] == 3
assert results[service_1.id]["sms_billable_units"] == 0
assert results[service_1.id]["chargeable_billable_sms"] == 0
assert results[service_1.id]["sms_cost"] == 0.0
@freeze_time("2022-05-01 13:30")
@@ -1112,9 +1112,9 @@ def test_fetch_usage_year_for_organization_only_queries_present_year(notify_db_s
results = fetch_usage_year_for_organization(organization_id=org.id, year=last_year)
assert len(results) == 1
assert results[str(service_1.id)]["sms_billable_units"] == 2
assert results[str(service_1.id)]["chargeable_billable_sms"] == 2
assert results[str(service_1.id)]["sms_cost"] == 2.0
assert results[service_1.id]["sms_billable_units"] == 2
assert results[service_1.id]["chargeable_billable_sms"] == 2
assert results[service_1.id]["sms_cost"] == 2.0
@freeze_time("2020-02-27 13:30")
@@ -1165,8 +1165,8 @@ def test_fetch_usage_year_for_organization_only_returns_data_for_live_services(
results = fetch_usage_year_for_organization(organization_id=org.id, year=2020)
assert len(results) == 1
assert results[str(live_service.id)]["sms_billable_units"] == 19
assert results[str(live_service.id)]["emails_sent"] == 0
assert results[live_service.id]["sms_billable_units"] == 19
assert results[live_service.id]["emails_sent"] == 0
@freeze_time("2022-04-27 13:30")

View File

@@ -928,3 +928,112 @@ def test_missing_both():
{"org_id": ["Can't be empty"]},
{"name": ["Can't be empty"]},
]
@freeze_time("2025-01-15 10:00:00")
def test_get_organization_message_allowance(admin_request, sample_organization, mocker):
service_1 = create_service(service_name="Service 1")
service_2 = create_service(service_name="Service 2")
service_1.total_message_limit = 100000
service_2.total_message_limit = 50000
dao_add_service_to_organization(service_1, sample_organization.id)
dao_add_service_to_organization(service_2, sample_organization.id)
mock_get_counts = mocker.patch(
"app.organization.rest.dao_get_notification_counts_per_service"
)
mock_get_counts.return_value = {
service_1.id: 30000,
service_2.id: 20000,
}
response = admin_request.get(
"organization.get_organization_message_allowance",
organization_id=sample_organization.id,
_expected_status=200,
)
assert response["messages_sent"] == 50000
assert response["messages_remaining"] == 100000
assert response["total_message_limit"] == 150000
assert mock_get_counts.call_count == 1
mock_get_counts.assert_called_once()
args, _ = mock_get_counts.call_args
assert set(args[0]) == {service_1.id, service_2.id}
assert args[1] == 2025
def test_get_organization_message_allowance_no_services(
admin_request, sample_organization
):
response = admin_request.get(
"organization.get_organization_message_allowance",
organization_id=sample_organization.id,
_expected_status=200,
)
assert response["messages_sent"] == 0
assert response["messages_remaining"] == 0
assert response["total_message_limit"] == 0
def test_get_organization_message_allowance_invalid_org_id(admin_request):
fake_uuid = "00000000-0000-0000-0000-000000000000"
admin_request.get(
"organization.get_organization_message_allowance",
organization_id=fake_uuid,
_expected_status=404,
)
def test_get_organization_dashboard(admin_request, mocker):
org_id = uuid.uuid4()
service_id = uuid.uuid4()
mock_usage = mocker.patch("app.organization.rest.fetch_usage_year_for_organization")
mock_usage.return_value = {
service_id: {
"service_id": service_id,
"service_name": "Test Service",
"active": True,
"restricted": False,
"emails_sent": 100,
"sms_billable_units": 5,
"sms_remainder": 245,
"sms_cost": 1.50,
"free_sms_limit": 250,
"chargeable_billable_sms": 0,
}
}
mock_templates = mocker.patch("app.organization.rest.dao_get_recent_sms_template_per_service")
mock_templates.return_value = {service_id: "Welcome SMS"}
mock_contacts = mocker.patch("app.organization.rest.dao_get_service_primary_contacts")
mock_contacts.return_value = {service_id: "billing@example.com"}
response = admin_request.get(
"organization.get_organization_dashboard",
organization_id=org_id,
**{"year": 2025},
)
assert len(response["services"]) == 1
service_data = response["services"][0]
assert service_data["service_id"] == str(service_id)
assert service_data["service_name"] == "Test Service"
assert service_data["active"] is True
assert service_data["restricted"] is False
assert service_data["sms_billable_units"] == 5
assert service_data["free_sms_limit"] == 250
assert service_data["sms_remainder"] == 245
assert service_data["recent_sms_template_name"] == "Welcome SMS"
assert service_data["primary_contact"] == "billing@example.com"
mock_usage.assert_called_once_with(org_id, 2025, include_all_services=True)
mock_templates.assert_called_once_with([service_id])
mock_contacts.assert_called_once_with([service_id])