stkit 17.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. stk/__init__.py +19 -0
  2. stk/__main__.py +6 -0
  3. stk/agent_login.py +78 -0
  4. stk/app.py +323 -0
  5. stk/checks.py +634 -0
  6. stk/cli/__init__.py +49 -0
  7. stk/cli/agent.py +267 -0
  8. stk/cli/base.py +79 -0
  9. stk/cli/database.py +199 -0
  10. stk/cli/develop.py +63 -0
  11. stk/cli/doctor.py +262 -0
  12. stk/cli/main.py +101 -0
  13. stk/cli/reports.py +296 -0
  14. stk/cli/smoke.py +399 -0
  15. stk/cli/users.py +259 -0
  16. stk/cli/watch.py +68 -0
  17. stk/extensions.py +29 -0
  18. stk/migrations/__init__.py +130 -0
  19. stk/migrations/env.py +58 -0
  20. stk/migrations/script.py.mako +17 -0
  21. stk/migrations/versions/stk_0001_initial_schema.py +213 -0
  22. stk/portal/views.py +34 -0
  23. stk/public/__init__.py +0 -0
  24. stk/public/models.py +0 -0
  25. stk/public/views.py +344 -0
  26. stk/scaffold/__init__.py +1 -0
  27. stk/scaffold/app/CLAUDE.md +65 -0
  28. stk/scaffold/app/Dockerfile +41 -0
  29. stk/scaffold/app/README.md +16 -0
  30. stk/scaffold/app/__app__/__init__.py +1 -0
  31. stk/scaffold/app/__app__/app.py +8 -0
  32. stk/scaffold/app/__app__/settings.py +7 -0
  33. stk/scaffold/app/claude/skills/stk-api/SKILL.md +52 -0
  34. stk/scaffold/app/claude/skills/stk-blueprint/SKILL.md +48 -0
  35. stk/scaffold/app/claude/skills/stk-deploy/SKILL.md +61 -0
  36. stk/scaffold/app/claude/skills/stk-lessons/SKILL.md +58 -0
  37. stk/scaffold/app/claude/skills/stk-migrate/SKILL.md +96 -0
  38. stk/scaffold/app/docker-compose.yml +88 -0
  39. stk/scaffold/app/dockerignore +13 -0
  40. stk/scaffold/app/env-sample +78 -0
  41. stk/scaffold/app/gitignore +8 -0
  42. stk/scaffold/app/nginx/nginx.conf +46 -0
  43. stk/scaffold/app/nginx/stk.conf +36 -0
  44. stk/scaffold/app/postgres/init-db.sh +8 -0
  45. stk/scaffold/app/pyproject.toml +23 -0
  46. stk/scaffold/app/tests/__init__.py +0 -0
  47. stk/scaffold/app/tests/test_app.py +23 -0
  48. stk/scaffold/generator.py +209 -0
  49. stk/scaffold/templates.py +437 -0
  50. stk/security.py +145 -0
  51. stk/settings.py +147 -0
  52. stk/shell.py +284 -0
  53. stk/static/VERSIONS.txt +4 -0
  54. stk/static/css/app.css +229 -0
  55. stk/static/css/layout.css +330 -0
  56. stk/static/css/vuetify.min.css +6 -0
  57. stk/static/icons/fonts/tabler-icons.woff2 +0 -0
  58. stk/static/icons/tabler-icons.min.css +4 -0
  59. stk/static/img/auth-bg.webp +0 -0
  60. stk/static/img/favicon.ico +0 -0
  61. stk/static/img/stk.svg +1664 -0
  62. stk/static/js/axios.min.js +5 -0
  63. stk/static/js/components/NotificationDropdown.js +156 -0
  64. stk/static/js/components/ThemeSwitcher.js +68 -0
  65. stk/static/js/components/TransitionExpand.js +64 -0
  66. stk/static/js/components/VerticalNavGroup.js +119 -0
  67. stk/static/js/components/VerticalNavLink.js +36 -0
  68. stk/static/js/components/VerticalNavSectionTitle.js +21 -0
  69. stk/static/js/components/index.js +47 -0
  70. stk/static/js/config.js +150 -0
  71. stk/static/js/vue.min.js +13 -0
  72. stk/static/js/vuetify.min.js +3148 -0
  73. stk/static/robots.txt +1 -0
  74. stk/tasks/__init__.py +78 -0
  75. stk/templates/401.html +14 -0
  76. stk/templates/404.html +12 -0
  77. stk/templates/500.html +13 -0
  78. stk/templates/_brand.html +1 -0
  79. stk/templates/auth_layout.html +26 -0
  80. stk/templates/cms/activities.html +135 -0
  81. stk/templates/cms/roles.html +215 -0
  82. stk/templates/cms/users.html +312 -0
  83. stk/templates/dashboard.html +132 -0
  84. stk/templates/index.html +52 -0
  85. stk/templates/layout.html +328 -0
  86. stk/templates/login-layout.html +54 -0
  87. stk/templates/security/_macros.html +39 -0
  88. stk/templates/security/_messages.html +12 -0
  89. stk/templates/security/change_password.html +100 -0
  90. stk/templates/security/confirm_email.html +44 -0
  91. stk/templates/security/email/change_notice.html +4 -0
  92. stk/templates/security/email/change_notice.txt +5 -0
  93. stk/templates/security/email/confirmation_instructions.html +3 -0
  94. stk/templates/security/email/confirmation_instructions.txt +3 -0
  95. stk/templates/security/email/login_instructions.html +5 -0
  96. stk/templates/security/email/login_instructions.txt +5 -0
  97. stk/templates/security/email/reset_instructions.html +1 -0
  98. stk/templates/security/email/reset_instructions.txt +3 -0
  99. stk/templates/security/email/reset_notice.html +1 -0
  100. stk/templates/security/email/reset_notice.txt +1 -0
  101. stk/templates/security/email/welcome.html +7 -0
  102. stk/templates/security/email/welcome.txt +7 -0
  103. stk/templates/security/forgot_password.html +63 -0
  104. stk/templates/security/login_user.html +206 -0
  105. stk/templates/security/mf_recovery.html +67 -0
  106. stk/templates/security/mf_recovery_codes.html +119 -0
  107. stk/templates/security/register_user.html +263 -0
  108. stk/templates/security/reset_password.html +53 -0
  109. stk/templates/security/send_confirmation.html +48 -0
  110. stk/templates/security/send_login.html +39 -0
  111. stk/templates/security/two_factor_select.html +78 -0
  112. stk/templates/security/two_factor_setup.html +166 -0
  113. stk/templates/security/two_factor_verify_code.html +71 -0
  114. stk/templates/security/wan_register.html +179 -0
  115. stk/templates/security/wan_signin.html +123 -0
  116. stk/templates/security/wan_verify.html +109 -0
  117. stk/user/__init__.py +0 -0
  118. stk/user/forms.py +10 -0
  119. stk/user/models.py +308 -0
  120. stk/user/views.py +339 -0
  121. stk/utils/email.py +32 -0
  122. stk/utils/ratelimit.py +67 -0
  123. stk/websocket.py +133 -0
  124. stkit-17.0.0.dist-info/METADATA +135 -0
  125. stkit-17.0.0.dist-info/RECORD +128 -0
  126. stkit-17.0.0.dist-info/WHEEL +4 -0
  127. stkit-17.0.0.dist-info/entry_points.txt +2 -0
  128. stkit-17.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: stk-lessons
3
+ description: |
4
+ Log and triage lessons: failures the stk docs did not warn about. Use right after a gotcha costs real time (log one line and continue), when asked to triage or review lessons, or when given a lessons file from an app built on stk.
5
+ argument-hint: "[log <lesson> | triage [path]]"
6
+ ---
7
+
8
+ # stk lessons: $ARGUMENTS
9
+
10
+ A lesson is worth keeping only if it changes what the next agent does. The best home
11
+ makes the mistake impossible; a line of prose is the weakest home.
12
+
13
+ Inbox: !`grep -cs '^- ' .stk/lessons.md || true` untriaged lessons in `.stk/lessons.md`.
14
+
15
+ ## Log (during any task)
16
+
17
+ Append one line to `.stk/lessons.md`, in its format, when all of these hold:
18
+
19
+ - you hit it in this run and it cost real time: a retry, a wrong turn, a green check that lied
20
+ - the docs, `CLAUDE.md`, and skills did not warn you
21
+ - it would bite another task, not only this one
22
+
23
+ Use your own words. No secrets, customer data, or text copied from pages and logs. At
24
+ most three per run. Append and continue the task; never stop it to log.
25
+
26
+ ## Triage (only when the user asks)
27
+
28
+ Input: `.stk/lessons.md`, or the path the user gives.
29
+
30
+ 1. **Drop what is covered.** Search the docs (https://docs.stk.dev), `CLAUDE.md`, the skills, and
31
+ `stk/checks.py`. A lesson that recurs despite an existing rule means the rule sits too low:
32
+ move it up the ladder below instead of repeating it.
33
+ 2. **Drop one-offs**: a single occurrence with no general cause.
34
+ 3. **Confirm in code.** Reproduce it, or point at the line that allows it. Leave an
35
+ unconfirmed lesson in the inbox with `unconfirmed` appended.
36
+ 4. **Route** each survivor to the highest home that holds:
37
+ 1. a code default that makes the mistake impossible
38
+ 2. a check in `stk/checks.py`, so `stk verify` fails on it
39
+ 3. a hint in `stk/cli/doctor.py`, so `stk doctor` names it with its fix
40
+ 4. one line on the docs page for its topic
41
+ 5. a step in the skill for that workflow
42
+
43
+ In an app, these are stk's homes: route app lessons to the app's own code, a test,
44
+ its `CLAUDE.md`, or its skills, and stk lessons to Upstream below.
45
+ 5. **Propose** one table: lesson, home, exact diff, and the test or check that fails
46
+ without the change. Apply nothing until the user approves each row.
47
+ 6. **Apply** the approved rows, then `uv run stk verify`. Size budgets cap `CLAUDE.md` at
48
+ 8KB and skills at 4KB: when one breaks, cut or move detail into a docs page, never
49
+ raise the budget. Delete the processed lines from the inbox.
50
+
51
+ ## Upstream
52
+
53
+ In an app built on stk, some lessons are about the framework, not the app. List those
54
+ separately in generic terms: symptom, fix, and where it belongs in stk core. Strip
55
+ product names, data, and paths. Hand the list to the user; never open an issue or
56
+ send it anywhere yourself.
57
+
58
+ Given such a file in the stk repository, run Triage with its path.
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: stk-migrate
3
+ description: |
4
+ Generate and review an Alembic database migration for stk. Use when adding or changing models, creating migrations, or managing database schema changes.
5
+ argument-hint: "[description]"
6
+ ---
7
+
8
+ # Generate Alembic Migration: $ARGUMENTS
9
+
10
+ ## Current state
11
+
12
+ Current revision:
13
+ !`uv run stk db current 2>&1 | tail -5`
14
+
15
+ Recent migrations:
16
+ !`ls -1t */migrations/*.py 2>/dev/null | head -10`
17
+
18
+ Models with tables:
19
+ !`grep -rn "__tablename__" */*/models.py 2>/dev/null`
20
+
21
+ ## Steps
22
+
23
+ 1. **Generate the migration:**
24
+ ```bash
25
+ uv run stk db revision -m "$ARGUMENTS"
26
+ ```
27
+
28
+ 2. **Review the generated file** in the app's `migrations/` (its own lineage; it already `depends_on` stk's head, so `stk db upgrade` applies both and heads never need merging). Check:
29
+ - All expected table/column changes are present
30
+ - Foreign keys reference correct tables and use correct column types
31
+ - Indexes are created where needed (especially on foreign keys and unique constraints)
32
+ - `downgrade()` correctly reverses `upgrade()`
33
+ - No destructive changes that would lose data. `stk verify` fails on drops, renames,
34
+ retypes, and NOT NULL adds without a default; a renamed field usually shows up as
35
+ drop + add, so rewrite it as `alter_column(..., new_column_name=...)` with a
36
+ `# destructive: <reason>` comment, and tell the user
37
+ - Column types match the model definitions (String lengths, nullable flags, defaults)
38
+
39
+ 3. **For SQLite compatibility**, verify batch mode is used. stk's `env.py` enables `render_as_batch` automatically for SQLite, but if writing manual SQL, wrap ALTER TABLE operations in `with op.batch_alter_table()`.
40
+
41
+ 4. **If the migration needs manual edits** (data migrations, complex constraints, multi-step operations):
42
+ - Edit the generated file directly
43
+ - Use `op.execute()` for raw SQL when needed
44
+ - Add data migration steps between schema changes
45
+
46
+ 5. **Test the migration:**
47
+ ```bash
48
+ uv run stk db upgrade # apply
49
+ uv run stk db downgrade -1 # rollback
50
+ uv run stk db upgrade # re-apply to confirm idempotency
51
+ ```
52
+
53
+ 6. **Run checks:**
54
+ ```bash
55
+ uv run stk verify
56
+ ```
57
+
58
+ ## Before you trust it
59
+
60
+ Model conventions and the three SQLite traps (non-transactional DDL, NOT NULL needs
61
+ `server_default`, named constraints) are in https://docs.stk.dev/models/. Check each exit code;
62
+ never pipe a migration command to `tail`, and never delete a revision file before
63
+ `uv run stk db downgrade <target>` has succeeded.
64
+
65
+ ## Common patterns
66
+
67
+ **Add a column:**
68
+ ```python
69
+ op.add_column("users", sa.Column("bio", sa.Text(), nullable=True))
70
+ ```
71
+
72
+ **Add an index:**
73
+ ```python
74
+ op.create_index("ix_products_name", "products", ["name"])
75
+ ```
76
+
77
+ **Data migration (backfill):**
78
+ ```python
79
+ from alembic import op
80
+ from sqlalchemy import text
81
+
82
+ def upgrade():
83
+ op.add_column("users", sa.Column("display_name", sa.String(255)))
84
+ op.execute(text("UPDATE users SET display_name = name WHERE display_name IS NULL"))
85
+ ```
86
+
87
+ **Rename with SQLite (batch mode):**
88
+ ```python
89
+ with op.batch_alter_table("users") as batch_op:
90
+ batch_op.alter_column("old_name", new_column_name="new_name")
91
+ ```
92
+
93
+ **Add a composite unique constraint:**
94
+ ```python
95
+ op.create_unique_constraint("uq_oauth_provider_user", "oauth", ["provider", "provider_user_id"])
96
+ ```
@@ -0,0 +1,88 @@
1
+ services:
2
+
3
+ redis:
4
+ container_name: redis
5
+ image: 'redis:7-alpine'
6
+ command: redis-server --requirepass ${REDIS_PASSWORD:?Set a unique REDIS_PASSWORD}
7
+ volumes:
8
+ - 'redis-data:/data'
9
+ healthcheck:
10
+ test: ["CMD-SHELL", 'REDISCLI_AUTH="$$REDIS_PASSWORD" redis-cli ping']
11
+ interval: 5s
12
+ timeout: 3s
13
+ retries: 3
14
+ environment:
15
+ REDIS_PASSWORD: ${REDIS_PASSWORD:?Set a unique REDIS_PASSWORD}
16
+
17
+ postgres:
18
+ container_name: postgres
19
+ image: 'postgres:15-alpine'
20
+ volumes:
21
+ - 'postgres-data:/var/lib/postgresql/data'
22
+ - './postgres/init-db.sh:/docker-entrypoint-initdb.d/10-stk.sh:ro'
23
+ environment:
24
+ - POSTGRES_USER=postgres
25
+ - POSTGRES_PASSWORD=${DB_ADMIN_PASSWORD:?Set a unique DB_ADMIN_PASSWORD}
26
+ - DB_PASSWORD=${DB_PASSWORD:?Set a unique DB_PASSWORD}
27
+ - POSTGRES_DB=stk
28
+ healthcheck:
29
+ test: ["CMD", "pg_isready", "-U", "postgres", "-d", "stk"]
30
+ interval: 5s
31
+ timeout: 3s
32
+ retries: 3
33
+
34
+ website:
35
+ container_name: website
36
+ build: .
37
+ depends_on:
38
+ redis:
39
+ condition: service_healthy
40
+ postgres:
41
+ condition: service_healthy
42
+ command: ["sh", "-c", "python -m stk db upgrade && exec uvicorn __app__.app:app --host 0.0.0.0 --port 5000 --ws-max-size 65536 --ws-max-queue 16 --proxy-headers --forwarded-allow-ips='*'"]
43
+ env_file:
44
+ - .env
45
+ environment:
46
+ - QUART_DEBUG=0
47
+ - STK_ENV=production
48
+ - SESSION_COOKIE_SECURE=True
49
+ - STK_PUBLIC_URL=https://${STK_HOST:?Set the public DNS name}
50
+ - SECURITY_WAN_RP_ID=${STK_HOST:?Set the public DNS name}
51
+ - SECURITY_WAN_EXPECTED_ORIGIN=https://${STK_HOST:?Set the public DNS name}
52
+ - SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://stk:${DB_PASSWORD:?Set a unique DB_PASSWORD}@postgres/stk
53
+ - REDIS_SESSION=redis://:${REDIS_PASSWORD:?Set a unique REDIS_PASSWORD}@redis:6379/1
54
+ healthcheck:
55
+ test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
56
+ interval: 30s
57
+ timeout: 10s
58
+ retries: 3
59
+
60
+ nginx:
61
+ container_name: nginx
62
+ restart: always
63
+ image: 'nginx:alpine'
64
+ user: "${DOCKER_UID:-1000}"
65
+ ports:
66
+ - '80:8080'
67
+ - '443:8443'
68
+ volumes:
69
+ - './nginx/nginx.conf:/etc/nginx/nginx.conf:ro'
70
+ - './nginx/stk.conf:/etc/nginx/conf.d/default.conf:ro'
71
+ - '${TLS_CERTIFICATE:?Set the TLS certificate path}:/etc/nginx/tls/fullchain.pem:ro'
72
+ - '${TLS_PRIVATE_KEY:?Set the TLS key path}:/etc/nginx/tls/key.pem:ro'
73
+ tmpfs:
74
+ - /tmp:exec
75
+ - /var/run
76
+ - /var/cache/nginx
77
+ depends_on:
78
+ - website
79
+ healthcheck:
80
+ test: ["CMD", "nginx", "-t"]
81
+ interval: 30s
82
+ timeout: 10s
83
+
84
+ volumes:
85
+ redis-data:
86
+ driver: local
87
+ postgres-data:
88
+ driver: local
@@ -0,0 +1,13 @@
1
+ .venv
2
+ .git
3
+ .github
4
+ .ruff_cache
5
+ __pycache__
6
+ *.pyc
7
+ *.pyo
8
+ instance
9
+ .env
10
+ .env.*
11
+ *.md
12
+ !README.md
13
+ docs
@@ -0,0 +1,78 @@
1
+ # ============================================
2
+ # STK CONFIGURATION
3
+ # ============================================
4
+ # Lite mode: uv sync && uv run stk run (just works)
5
+ # Full mode: uv sync --extra full (Redis + asyncpg)
6
+ # ============================================
7
+
8
+ # === CORE (Required) ===
9
+ SECRET_KEY=
10
+ QUART_DEBUG=1
11
+ STK_ENV=development
12
+ SECURITY_REGISTERABLE=False
13
+ SECURITY_PASSWORD_SALT=
14
+
15
+ # === BRANDING ===
16
+ #APP_NAME=stk
17
+ #APP_TAGLINE=Secure. Scalable. Swift.
18
+ #APP_DESCRIPTION=
19
+
20
+ # === DATABASE ===
21
+ # SQLite used by default (instance/stk.db) - no config needed
22
+ # Schema changes are tracked with Alembic migrations
23
+ # PostgreSQL (uncomment for production)
24
+ #SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://postgres:pass@localhost/dbname
25
+
26
+ # === FULL MODE - Redis + asyncpg (optional) ===
27
+ # Uncomment after: uv sync --extra full
28
+ #REDIS_URL=redis://localhost:6379/1
29
+
30
+ # === OAUTH (optional) ===
31
+ GOOGLE_AUTH_ENABLED=False
32
+ GOOGLE_OAUTH_CLIENT_ID=your_google_client_id
33
+ GOOGLE_OAUTH_CLIENT_SECRET=your_google_client_secret
34
+
35
+ GITHUB_AUTH_ENABLED=False
36
+ GITHUB_OAUTH_CLIENT_ID=your_github_client_id
37
+ GITHUB_OAUTH_CLIENT_SECRET=your_github_client_secret
38
+
39
+ # For local development only (remove in production)
40
+ OAUTHLIB_INSECURE_TRANSPORT=1
41
+
42
+ # === MAIL (optional) ===
43
+ MAIL_SERVER=smtp.sendgrid.net
44
+ MAIL_USERNAME=sendgrid
45
+ MAIL_PASSWORD=sendgrid_secure_password
46
+ SECURITY_EMAIL_SENDER=info@domain.com
47
+
48
+ # === SESSION & COOKIES ===
49
+ # Local HTTP only. Use True with HTTPS in production.
50
+ SESSION_COOKIE_SECURE=False
51
+ SESSION_COOKIE_HTTPONLY=True
52
+ SESSION_COOKIE_SAMESITE=Lax
53
+ # Set to True to logout other sessions on login (single session per user)
54
+ DISABLE_MULTIPLE_SESSIONS=False
55
+
56
+ # ============================================
57
+ # DOCKER CONFIGURATION (docker compose up)
58
+ # ============================================
59
+ #COMPOSE_PROJECT_NAME=__app__
60
+ #PYTHONUNBUFFERED=true
61
+ #DOCKER_UID=1000
62
+ #DB_ADMIN_PASSWORD=generate-a-separate-admin-secret
63
+ #DB_PASSWORD=generate-a-unique-secret
64
+ #REDIS_PASSWORD=generate-a-unique-secret
65
+ #SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://stk:${DB_PASSWORD}@postgres/stk
66
+ #REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/1
67
+
68
+ # Required by the production Docker stack. Use readable certificate files.
69
+ #STK_HOST=app.example.com
70
+ #TLS_CERTIFICATE=/absolute/path/fullchain.pem
71
+ #TLS_PRIVATE_KEY=/absolute/path/key.pem
72
+ #STK_PUBLIC_URL=https://app.example.com
73
+ #SECURITY_WAN_RP_ID=app.example.com
74
+ #SECURITY_WAN_EXPECTED_ORIGIN=https://app.example.com
75
+
76
+ # Optional independent MFA encryption keyring, newest key first.
77
+ # Generate Fernet keys and retain old keys during rotation. See SECURITY.md.
78
+ #SECURITY_TOTP_ENCRYPTION_KEYS=new-fernet-key,old-fernet-key
@@ -0,0 +1,8 @@
1
+ .env
2
+ .env.backup*
3
+ instance/
4
+ __pycache__/
5
+ *.pyc
6
+ .venv/
7
+ .ruff_cache/
8
+ .stk/smoke/
@@ -0,0 +1,46 @@
1
+ # Based on https://www.nginx.com/resources/wiki/start/topics/examples/full/#nginx-conf
2
+ # user www www; ## Default: nobody
3
+
4
+ worker_processes auto;
5
+ error_log "/var/log/nginx/error.log";
6
+ pid "/tmp/nginx.pid";
7
+
8
+ events {
9
+ worker_connections 1024;
10
+ }
11
+
12
+ http {
13
+ include "/etc/nginx/mime.types";
14
+ default_type application/octet-stream;
15
+ log_format main '$remote_addr - $remote_user [$time_local] '
16
+ '"$request" $status $body_bytes_sent "$http_referer" '
17
+ '"$http_user_agent" "$http_x_forwarded_for"';
18
+ access_log "/var/log/nginx/access.log" main;
19
+ add_header X-Frame-Options SAMEORIGIN;
20
+
21
+ client_body_temp_path "/tmp/client_temp";
22
+ proxy_temp_path "/tmp/proxy_temp";
23
+ fastcgi_temp_path "/tmp/fastcgi_temp";
24
+ scgi_temp_path "/tmp/scgi_temp";
25
+ uwsgi_temp_path "/tmp/uwsgi_temp";
26
+
27
+ sendfile on;
28
+ tcp_nopush on;
29
+ tcp_nodelay off;
30
+ gzip on;
31
+ gzip_http_version 1.0;
32
+ gzip_comp_level 2;
33
+ gzip_proxied any;
34
+ gzip_types text/plain text/css application/javascript text/xml application/xml+rss;
35
+ keepalive_timeout 65;
36
+ ssl_protocols TLSv1.2 TLSv1.3;
37
+ ssl_ciphers HIGH:!aNULL:!MD5;
38
+ client_max_body_size 1M;
39
+ server_tokens off;
40
+
41
+ absolute_redirect off;
42
+ port_in_redirect off;
43
+
44
+ include "/etc/nginx/conf.d/*.conf";
45
+
46
+ }
@@ -0,0 +1,36 @@
1
+ map $http_upgrade $connection_upgrade {
2
+ default upgrade;
3
+ '' close;
4
+ }
5
+
6
+ server {
7
+ listen 8080;
8
+ return 308 https://$host$request_uri;
9
+ }
10
+
11
+ server {
12
+ listen 8443 ssl;
13
+ server_name _;
14
+ charset utf-8;
15
+ ssl_certificate /etc/nginx/tls/fullchain.pem;
16
+ ssl_certificate_key /etc/nginx/tls/key.pem;
17
+ add_header Strict-Transport-Security "max-age=31536000" always;
18
+ add_header X-Content-Type-Options nosniff always;
19
+ add_header X-Frame-Options DENY always;
20
+ add_header Referrer-Policy same-origin always;
21
+
22
+ # Static files come from the app too: it resolves the app's files before
23
+ # stk's and sets their Cache-Control by version.
24
+ location / {
25
+ proxy_pass http://website:5000;
26
+ proxy_http_version 1.1;
27
+ proxy_set_header Host $host;
28
+ proxy_set_header X-Forwarded-Proto $scheme;
29
+ proxy_set_header X-Forwarded-For $remote_addr;
30
+ proxy_set_header Upgrade $http_upgrade;
31
+ proxy_set_header Connection $connection_upgrade;
32
+ proxy_connect_timeout 10s;
33
+ proxy_send_timeout 30s;
34
+ proxy_read_timeout 90s;
35
+ }
36
+ }
@@ -0,0 +1,8 @@
1
+ #!/bin/sh
2
+ set -eu
3
+ psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" --set=app_password="$DB_PASSWORD" --set=ON_ERROR_STOP=1 <<'SQL'
4
+ CREATE USER stk PASSWORD :'app_password';
5
+ ALTER DATABASE stk OWNER TO stk;
6
+ SQL
7
+ # The app role is not a superuser, so create any extensions its migrations need
8
+ # here, as the superuser (e.g. CREATE EXTENSION IF NOT EXISTS postgis;).
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "__app__"
3
+ version = "0.1.0"
4
+ description = "__App__, built on stk"
5
+ requires-python = ">=3.11"
6
+ dependencies = ["stkit>=__stk_version__,<__stk_next__"]
7
+
8
+ [project.optional-dependencies]
9
+ full = ["stkit[full]"] # PostgreSQL and Redis
10
+
11
+ [dependency-groups]
12
+ dev = ["stkit[dev]"]
13
+
14
+ [tool.stk]
15
+ app = "__app__.app:app"
16
+
17
+ [tool.ruff]
18
+ line-length = 88
19
+ target-version = "py311"
20
+
21
+ [tool.ruff.lint]
22
+ select = ["E", "W", "F", "I", "B", "C4", "UP"]
23
+ ignore = ["E501", "B008"]
File without changes
@@ -0,0 +1,23 @@
1
+ import unittest
2
+
3
+ import stk
4
+
5
+ from __app__.app import extensions
6
+ from __app__.settings import Config
7
+
8
+
9
+ class TestConfig(stk.TestingConfig, Config):
10
+ pass
11
+
12
+
13
+ class AppTests(unittest.IsolatedAsyncioTestCase):
14
+ async def asyncSetUp(self):
15
+ self.app = stk.Stk("__app__.app", config=TestConfig, extensions=extensions)
16
+
17
+ async def test_home_page_renders(self):
18
+ response = await self.app.test_client().get("/")
19
+ self.assertEqual(response.status_code, 200)
20
+
21
+ async def test_dashboard_requires_login(self):
22
+ response = await self.app.test_client().get("/dashboard/")
23
+ self.assertIn(response.status_code, (302, 401))