Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ tmp/
todo.md
development/caddy/data

readme.ai
.scratch/*
.scratch/*
.aider*
55 changes: 55 additions & 0 deletions .scratch/teams.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[
{
"name": "contributors",
"id": 16403217,
"node_id": "T_kwDODCn5bM4A-ksR",
"slug": "contributors",
"description": "can make updates",
"privacy": "closed",
"notification_setting": "notifications_enabled",
"url": "https://api.github.com/organizations/204077420/team/16403217",
"html_url": "https://github.com/orgs/CivicPatch/teams/contributors",
"members_url": "https://api.github.com/organizations/204077420/team/16403217/members{/member}",
"repositories_url": "https://api.github.com/organizations/204077420/team/16403217/repos",
"type": "organization",
"organization_id": 204077420,
"permission": "pull",
"created_at": "2026-02-25T05:00:37Z",
"updated_at": "2026-02-25T05:00:37Z",
"members_count": 1,
"repos_count": 0,
"organization": {
"login": "CivicPatch",
"id": 204077420,
"node_id": "O_kgDODCn5bA",
"url": "https://api.github.com/orgs/CivicPatch",
"repos_url": "https://api.github.com/orgs/CivicPatch/repos",
"events_url": "https://api.github.com/orgs/CivicPatch/events",
"hooks_url": "https://api.github.com/orgs/CivicPatch/hooks",
"issues_url": "https://api.github.com/orgs/CivicPatch/issues",
"members_url": "https://api.github.com/orgs/CivicPatch/members{/member}",
"public_members_url": "https://api.github.com/orgs/CivicPatch/public_members{/member}",
"avatar_url": "https://avatars.githubusercontent.com/u/204077420?v=4",
"description": null,
"name": null,
"company": null,
"blog": null,
"location": null,
"email": null,
"twitter_username": null,
"is_verified": false,
"has_organization_projects": true,
"has_repository_projects": true,
"public_repos": 4,
"public_gists": 0,
"followers": 0,
"following": 0,
"html_url": "https://github.com/CivicPatch",
"created_at": "2025-03-19T19:05:55Z",
"updated_at": "2026-01-23T20:18:40Z",
"archived_at": null,
"type": "Organization"
},
"parent": null
}
]
6 changes: 3 additions & 3 deletions api.civicpatch.org/.env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
GITHUB_CLIENT_ID=<civicpatch-tools credentials>
GITHUB_CLIENT_SECRET=<civicpatch-tools credentials>
GITHUB_APP_CLIENT_ID=<civicpatch-tools credentials>
GITHUB_APP_CLIENT_SECRET=<civicpatch-tools credentials>
JWT_SECRET_KEY=
DATABASE_HASH_KEY=
APPROVED_GITHUB_PROVIDER_USER_IDS=<separated by commas>
CRUDDER_DB_PASSWORD=<password>
CRUDDER_DB_URL="postgres://civicpatch:<password>@:6000/api.civicpatch.org_db?search_path=public&sslmode=disable"
CIVICPATCH_API_DB_URL="postgres://civicpatch:<password>@:6000/api.civicpatch.org_db?search_path=public&sslmode=disable"

INSTANCE_URL="http://localhost:8001"
MAINTAINER_EMAIL=maintainer@email.com
Expand Down
2 changes: 1 addition & 1 deletion api.civicpatch.org/database_operations/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# CONFIG
DATABASE_URL = os.getenv(
"CRUDDER_DB_URL",
"CIVICPATCH_API_DB_URL",
"postgresql://civicpatch:development_password@crudder_db:5432/development_db",
)
MIGRATIONS_DIR = "database_operations/migrations"
Expand Down
6 changes: 3 additions & 3 deletions api.civicpatch.org/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ services:
- api_civicpatch_org_test_db
environment:
APP_ENVIRONMENT: test
GITHUB_CLIENT_ID: test_github_client_id
GITHUB_CLIENT_SECRET: test_github_client_secret
GITHUB_APP_CLIENT_ID: test_GITHUB_APP_CLIENT_ID
GITHUB_APP_CLIENT_SECRET: test_GITHUB_APP_CLIENT_SECRET
JWT_SECRET_KEY: test_jwt_secret_key
DATABASE_HASH_KEY: test_database_hash_key
CRUDDER_DB_PASSWORD: test_password
CRUDDER_DB_URL: "postgres://civicpatch_test:test_password@api_civicpatch_org_test_db:5432/test_db?sslmode=disable"
CIVICPATCH_API_DB_URL: "postgres://civicpatch_test:test_password@api_civicpatch_org_test_db:5432/test_db?sslmode=disable"
INSTANCE_URL: "http://localhost:8002"
MAINTAINER_EMAIL: test_maintainer@email.com
STORAGE_ENDPOINT: http://localhost:9000
Expand Down
4 changes: 2 additions & 2 deletions api.civicpatch.org/src/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@

from schemas.common import Person, PeopleJobHistory

CRUDDER_DB_URL = os.getenv("CRUDDER_DB_URL")
CIVICPATCH_API_DB_URL = os.getenv("CIVICPATCH_API_DB_URL")
DATABASE_HASH_KEY = os.getenv("DATABASE_HASH_KEY")

pool = AsyncConnectionPool(CRUDDER_DB_URL, open=False)
pool = AsyncConnectionPool(CIVICPATCH_API_DB_URL, open=False)


def hash_string(string: str, hash_key: str) -> str:
Expand Down
209 changes: 205 additions & 4 deletions api.civicpatch.org/src/frontend/static/styles.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,207 @@
:root,
:host {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol";
}
--pico-font-family: "Noto Sans", sans-serif;
--pico-form-element-spacing-vertical: 0.5rem; /* Smaller vertical padding */
--pico-form-element-spacing-horizontal: 0.5rem; /* Smaller horizontal padding */
font-size: 14px;
--pico-border-radius: 0.4rem;
--pico-border-width: 0.03rem;
--pico-outline-width: 0.2rem;
}

*:focus {
outline: 2px solid rgb(var(--catppuccin-sapphire));
outline-offset: 2px;
border: 2px solid transparent;
border-radius: var(--pico-border-radius);
}

[data-theme="light"],
:root:not([data-theme="dark"]) {
--catppuccin-base: 239, 241, 245;
--catppuccin-mantle: 230, 233, 239;
--catppuccin-crust: 220, 224, 232;
--catppuccin-text: 76, 79, 105;
--catppuccin-subtext0: 108, 118, 139;
--catppuccin-subtext1: 92, 102, 124;
--catppuccin-overlay1: 140, 143, 161;
--catppuccin-surface0: 204, 208, 218; /* Corrected */
--catppuccin-surface1: 188, 192, 204; /* Corrected */
--catppuccin-surface2: 204, 208, 218; /* Corrected */
--catppuccin-blue: 30, 102, 245;
--catppuccin-red: 191, 97, 106;
--catppuccin-green: 163, 190, 140;
--catppuccin-yellow: 235, 203, 139;
--catppuccin-pink: 234, 118, 203; /* Corrected */
--catppuccin-mauve: 136, 57, 239;
--catppuccin-sky: 94, 205, 244;
--catppuccin-teal: 23, 146, 153; /* Corrected */
--catppuccin-sapphire: 32, 159, 181;
--catppuccin-lavender: 136, 192, 208;

--pico-color: rgb(var(--catppuccin-text));
--pico-h2-color: rgb(var(--catppuccin-text));
--pico-h3-color: rgb(var(--catppuccin-text));
--pico-background-color: rgb(var(--catppuccin-mantle));
--pico-text-selection-color: rgba(var(--catppuccin-sapphire), 0.3);
--pico-form-element-focus-color: rgb(var(--catppuccin-mauve));
--pico-form-element-border-color: rgb(var(--catppuccin-base));
--pico-primary-focus: rgb(var(--catppuccin-mantle));
--pico-primary: rgb(var(--catppuccin-text));
--pico-primary-background: rgb(var(--catppuccin-text));
--pico-primary-inverse: rgb(var(--catppuccin-base));
--pico-primary-hover-background: rgb(var(--catppuccin-surface0));
--pico-secondary-background: rgb(var(--catppuccin-overlay1));
--pico-secondary-inverse: rgb(var(--catppuccin-base));
--pico-secondary-hover-background: rgb(var(--catppuccin-sapphire));
--pico-form-element-active-background-color: rgb(var(--catppuccin-surface1));
--pico-form-element-background-color: rgb(var(--catppuccin-surface0));
--pico-form-element-active-border-color: rgb(var(--catppuccin-mantle));
--pico-form-element-color: var(--catppuccin-base);
--pico-primary-border: rgb(var(--catppuccin-subtext0));
--pico-muted-border-color: rgba(var(--catppuccin-subtext0), 0.1);
--pico-contrast-background: rgb(var(--catppuccin-red));
--pico-contrast-inverse: rgb(var(--catppuccin-base));
--pico-contrast-hover-background: rgba(var(--catppuccin-red), 0.3);
--pico-primary-underline: rgb(var(--catppuccin-sapphire));
--pico-border-color: rgb(var(--catppuccin-mantle));

--pico-card-sectioning-background-color: rgb(var(--catppuccin-crust));
--pico-card-background-color: rgb(var(--catppuccin-mantle));

--pico-ins-color: rgba(var(--catppuccin-green), 0.5);
--pico-del-color: rgba(var(--catppuccin-red), 0.5);
}

@media only screen and (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--catppuccin-base: 30, 30, 46; /* Base */
--catppuccin-mantle: 24, 24, 37; /* Mantle */
--catppuccin-crust: 17, 17, 27; /* Crust */
--catppuccin-text: 205, 214, 244; /* Text */
--catppuccin-subtext0: 166, 173, 200; /* Subtext 0 */
--catppuccin-subtext1: 186, 194, 222; /* Subtext 1 */
--catppuccin-overlay1: 127, 132, 156;
--catppuccin-surface0: 49, 50, 68; /* Surface 0 */
--catppuccin-surface1: 69, 71, 90; /* Surface 1 */
--catppuccin-surface2: 88, 91, 112; /* Surface 2 */
--catppuccin-blue: 137, 180, 250; /* Blue */
--catppuccin-red: 243, 139, 168; /* Red */
--catppuccin-green: 166, 227, 161; /* Green */
--catppuccin-yellow: 249, 226, 175; /* Yellow */
--catppuccin-pink: 245, 194, 231; /* Pink */
--catppuccin-mauve: 203, 166, 247; /* Mauve */
--catppuccin-sky: 137, 220, 235; /* Sky */
--catppuccin-teal: 148, 226, 213; /* Teal */
--catppuccin-sapphire: 116, 199, 236; /* Sapphire */
--catppuccin-lavender: 180, 190, 254; /* Lavender */

--pico-color: rgb(var(--catppuccin-text));
--pico-h2-color: rgb(var(--catppuccin-text));
--pico-h3-color: rgb(var(--catppuccin-text));
--pico-background-color: rgb(var(--catppuccin-mantle));
--pico-text-selection-color: rgba(var(--catppuccin-sapphire), 0.3);
--pico-form-element-focus-color: rgb(var(--catppuccin-mauve));
--pico-form-element-border-color: rgb(var(--catppuccin-surface2));
--pico-primary-focus: rgb(var(--catppuccin-mantle));
--pico-primary: rgb(var(--catppuccin-text));
--pico-primary-background: rgb(var(--catppuccin-overlay1));
--pico-primary-inverse: rgb(var(--catppuccin-base));
--pico-primary-hover-background: rgb(var(--catppuccin-surface0));
--pico-secondary-background: rgb(var(--catppuccin-overlay1));
--pico-secondary-inverse: rgb(var(--catppuccin-base));
--pico-secondary-hover-background: rgb(var(--catppuccin-sapphire));
--pico-form-element-active-background-color: rgb(var(--catppuccin-surface1));
--pico-form-element-background-color: rgb(var(--catppuccin-surface0));
--pico-form-element-active-border-color: rgb(var(--catppuccin-mantle));
--pico-form-element-color: var(--catppuccin-base);
--pico-primary-border: rgb(var(--catppuccin-subtext0));
--pico-muted-border-color: rgb(var(--catppuccin-subtext0));
--pico-contrast-background: rgb(var(--catppuccin-red));
--pico-contrast-inverse: rgb(var(--catppuccin-base));
--pico-contrast-hover-background: rgba(var(--catppuccin-red), 0.3);
--pico-primary-underline: rgb(var(--catppuccin-sapphire));
--pico-border-color: rgb(var(--catppuccin-mantle));

--pico-card-sectioning-background-color: rgb(var(--catppuccin-crust));
--pico-card-background-color: rgb(var(--catppuccin-mantle));

--pico-ins-color: rgba(var(--catppuccin-green), 0.5);
--pico-del-color: rgba(var(--catppuccin-red), 0.5);
}
}

/**
button.primary {
background-color: rgb(var(--catppuccin-mauve));
background-image: linear-gradient(120deg,var(--catppuccin-pink),var(--catppuccin-mauve));
color: rgb(var(--catppuccin-base));
font-weight: 500;
}
button.primary:hover {
background-color: rgba(var(--catppuccin-mauve), 0.5);
}
*/

input:not([type=submit],
[type=button],
[type=reset],
[type=checkbox],
[type=radio],
[type=file]),
:where(select, textarea) {
--pico-outline-width: 0.2rem;
}

[type="checkbox"]:checked {
--pico-background-color: rgb(var(--catppuccin-sapphire));
--pico-border-color: rgb(var(--catppuccin-sapphire));
}

main.container-fluid {
display: flex;
flex-direction: column;
gap: 1rem;
}

@media (min-width: 576px) {
:root,
:host {
--pico-font-size: 106.25%;
}
}
@media (min-width: 768px) {
:root,
:host {
--pico-font-size: 112.5%;
padding: 0.5rem 1rem;
}
}
@media (min-width: 1024px) {
:root,
:host {
--pico-font-size: 118.75%;
padding: 1rem 2rem;
}
}
@media (min-width: 1280px) {
:root,
:host {
--pico-font-size: 125%;
padding: 2rem 4rem;
}
}
@media (min-width: 1536px) {
:root,
:host {
padding: 5rem 15rem;
}
}

.visually-hidden {
position: absolute !important;
height: 1px; width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap;
}
38 changes: 25 additions & 13 deletions api.civicpatch.org/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,18 @@ def url_for(request: FastAPIRequest, name: str) -> URL:
# Ref: https://github.com/tomasvotava/fastapi-sso/blob/master/docs/how-to-guides/use-with-fastapi-security.md

INSTANCE_URL = os.getenv("INSTANCE_URL", "http://127.0.0.1:8001")
GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
GITHUB_CALLBACK_URL = f"{INSTANCE_URL}/api/v1/auth/github/callback"
CRUDDER_DB_URL = os.getenv("CRUDDER_DB_URL")

GITHUB_APP_ID = os.getenv("GITHUB_APP_ID")
GITHUB_APP_CLIENT_ID = os.getenv("GITHUB_APP_CLIENT_ID")
GITHUB_APP_CLIENT_SECRET = os.getenv("GITHUB_APP_CLIENT_SECRET")
GITHUB_APP_PRIVATE_KEY_BASE64 = os.getenv("GITHUB_APP_PRIVATE_KEY_BASE64")
GITHUB_APP_INSTALLATION_ID = os.getenv("GITHUB_APP_INSTALLATION_ID")

GITHUB_CALLBACK_URL = f"{INSTANCE_URL}/auth/github/callback"
CIVICPATCH_API_DB_URL = os.getenv("CIVICPATCH_API_DB_URL")

MAINTAINER_EMAIL = os.getenv("MAINTAINER_EMAIL")
APP_ENVIRONMENT = os.getenv("APP_ENVIRONMENT")
GITHUB_WORKFLOW_TOKEN = os.getenv("GITHUB_WORKFLOW_TOKEN")
DATABASE_HASH_KEY = os.getenv("DATABASE_HASH_KEY")

JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY")
Expand All @@ -88,11 +92,15 @@ def url_for(request: FastAPIRequest, name: str) -> URL:
if not all(
[
MAINTAINER_EMAIL,
GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET,
CRUDDER_DB_URL,

GITHUB_APP_ID,
GITHUB_APP_CLIENT_ID,
GITHUB_APP_CLIENT_SECRET,
GITHUB_APP_PRIVATE_KEY_BASE64,
GITHUB_APP_INSTALLATION_ID,

CIVICPATCH_API_DB_URL,
APP_ENVIRONMENT,
GITHUB_WORKFLOW_TOKEN,
DATABASE_HASH_KEY,
JWT_SECRET_KEY,
STORAGE_ENDPOINT,
Expand All @@ -104,11 +112,15 @@ def url_for(request: FastAPIRequest, name: str) -> URL:
var
for var, val in {
"MAINTAINER_EMAIL": MAINTAINER_EMAIL,
"GITHUB_CLIENT_ID": GITHUB_CLIENT_ID,
"GITHUB_CLIENT_SECRET": GITHUB_CLIENT_SECRET,
"CRUDDER_DB_URL": CRUDDER_DB_URL,

"GITHUB_APP_ID": GITHUB_APP_ID,
"GITHUB_APP_CLIENT_ID": GITHUB_APP_CLIENT_ID,
"GITHUB_APP_CLIENT_SECRET": GITHUB_APP_CLIENT_SECRET,
"GITHUB_APP_PRIVATE_KEY_BASE64": GITHUB_APP_PRIVATE_KEY_BASE64,
"GITHUB_APP_INSTALLATION_ID": GITHUB_APP_INSTALLATION_ID,

"CIVICPATCH_API_DB_URL": CIVICPATCH_API_DB_URL,
"APP_ENVIRONMENT": APP_ENVIRONMENT,
"GITHUB_WORKFLOW_TOKEN": GITHUB_WORKFLOW_TOKEN,
"DATABASE_HASH_KEY": DATABASE_HASH_KEY,
"JWT_SECRET_KEY": JWT_SECRET_KEY,
"STORAGE_ENDPOINT": STORAGE_ENDPOINT,
Expand Down
Loading