forked from cartsnitch/cartsnitch
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37c75c3887 | |||
| 8a0b2c03a1 | |||
| aa893d9cc1 | |||
| 91c062130c | |||
| 0aef2455fd | |||
| 6602b8c105 | |||
| dbbc8d2e7b | |||
| 1267caf43c | |||
| 015401861a | |||
| 9891e1aefb | |||
| 69ad161e36 | |||
| 485f890df3 | |||
| bf3ed0ede3 | |||
| 3f41eb7346 | |||
| 6cbd1ef298 | |||
| 94214f762e | |||
| 562c6ef6f6 | |||
| ccc8189d88 | |||
| 86594e4a8e | |||
| c2f1a83c1d | |||
| 6f8e5a9577 | |||
| bbfa816e57 | |||
| 5904eb03a2 | |||
| 87b6433ff7 | |||
| d7c9938f7e | |||
| 02434060ee |
+6
-12
@@ -4,23 +4,17 @@ import pg from "pg";
|
|||||||
|
|
||||||
const { Pool } = pg;
|
const { Pool } = pg;
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
connectionString:
|
||||||
|
process.env.DATABASE_URL ??
|
||||||
|
"postgresql://cartsnitch:cartsnitch@localhost:5432/cartsnitch",
|
||||||
|
});
|
||||||
|
|
||||||
const secret = process.env.BETTER_AUTH_SECRET;
|
const secret = process.env.BETTER_AUTH_SECRET;
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
throw new Error("BETTER_AUTH_SECRET environment variable is required");
|
throw new Error("BETTER_AUTH_SECRET environment variable is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
const databaseUrl = process.env.DATABASE_URL;
|
|
||||||
if (!databaseUrl) {
|
|
||||||
console.warn(
|
|
||||||
"WARNING: DATABASE_URL is not set — using default localhost connection. " +
|
|
||||||
"Set DATABASE_URL for production deployments."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pool = new Pool({
|
|
||||||
connectionString: databaseUrl ?? "postgresql://cartsnitch:cartsnitch@localhost:5432/cartsnitch",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: pool,
|
database: pool,
|
||||||
basePath: "/auth",
|
basePath: "/auth",
|
||||||
|
|||||||
Generated
+3
-3
@@ -9805,9 +9805,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.4.2",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||||
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
|
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
"""Service-specific configuration for ReceiptWitness."""
|
"""Service-specific configuration for ReceiptWitness."""
|
||||||
|
|
||||||
from pydantic import model_validator
|
|
||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
_PLACEHOLDER_VALUES = {"change-me-in-production"}
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiptWitnessSettings(BaseSettings):
|
class ReceiptWitnessSettings(BaseSettings):
|
||||||
model_config = {"env_prefix": "RW_"}
|
model_config = {"env_prefix": "RW_"}
|
||||||
|
|
||||||
@@ -34,34 +30,5 @@ class ReceiptWitnessSettings(BaseSettings):
|
|||||||
# Mailgun inbound email webhook
|
# Mailgun inbound email webhook
|
||||||
mailgun_webhook_signing_key: str = ""
|
mailgun_webhook_signing_key: str = ""
|
||||||
|
|
||||||
@model_validator(mode="after")
|
|
||||||
def validate_required_vars(self):
|
|
||||||
errors = []
|
|
||||||
if not self.session_encryption_key or self.session_encryption_key in _PLACEHOLDER_VALUES:
|
|
||||||
errors.append(
|
|
||||||
"RW_SESSION_ENCRYPTION_KEY must be set to a secure value. "
|
|
||||||
'Generate one with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"'
|
|
||||||
)
|
|
||||||
if self.notifications_enabled and not self.resend_api_key:
|
|
||||||
errors.append(
|
|
||||||
"RW_RESEND_API_KEY must be set when RW_NOTIFICATIONS_ENABLED=true. "
|
|
||||||
"Get an API key from https://resend.com/api-keys"
|
|
||||||
)
|
|
||||||
if errors:
|
|
||||||
raise ValueError(
|
|
||||||
"ReceiptWitness startup failed — missing required config:\n"
|
|
||||||
+ "\n".join(f" - {e}" for e in errors)
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
settings = ReceiptWitnessSettings()
|
||||||
class _LazySettings:
|
|
||||||
_instance: ReceiptWitnessSettings | None = None
|
|
||||||
|
|
||||||
def __getattr__(self, name: str):
|
|
||||||
if _LazySettings._instance is None:
|
|
||||||
_LazySettings._instance = ReceiptWitnessSettings()
|
|
||||||
return getattr(_LazySettings._instance, name)
|
|
||||||
|
|
||||||
|
|
||||||
settings = _LazySettings()
|
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
"""Shared test fixtures."""
|
"""Shared test fixtures."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
||||||
|
|
||||||
os.environ.setdefault("RW_SESSION_ENCRYPTION_KEY", "test-secret-key-for-unit-tests-only-32bytes!")
|
|
||||||
os.environ.setdefault("RW_MAILGUN_WEBHOOK_SIGNING_KEY", "test-mailgun-signing-key")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def meijer_receipt_data() -> dict:
|
def meijer_receipt_data() -> dict:
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from receiptwitness.config import ReceiptWitnessSettings
|
|
||||||
|
|
||||||
|
|
||||||
def test_valid_config():
|
|
||||||
s = ReceiptWitnessSettings(
|
|
||||||
session_encryption_key="7reF42nmTwbdN21PBoubGp7h_FU8qSimstmlaMLoRK8="
|
|
||||||
)
|
|
||||||
assert s.session_encryption_key
|
|
||||||
|
|
||||||
|
|
||||||
def test_missing_session_encryption_key_raises():
|
|
||||||
with pytest.raises(ValueError, match="RW_SESSION_ENCRYPTION_KEY"):
|
|
||||||
ReceiptWitnessSettings(session_encryption_key="")
|
|
||||||
|
|
||||||
|
|
||||||
def test_placeholder_session_encryption_key_raises():
|
|
||||||
with pytest.raises(ValueError, match="RW_SESSION_ENCRYPTION_KEY"):
|
|
||||||
ReceiptWitnessSettings(session_encryption_key="change-me-in-production")
|
|
||||||
|
|
||||||
|
|
||||||
def test_notifications_enabled_without_resend_key_raises():
|
|
||||||
with pytest.raises(ValueError, match="RW_RESEND_API_KEY"):
|
|
||||||
ReceiptWitnessSettings(
|
|
||||||
session_encryption_key="7reF42nmTwbdN21PBoubGp7h_FU8qSimstmlaMLoRK8=",
|
|
||||||
notifications_enabled=True,
|
|
||||||
resend_api_key="",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_notifications_disabled_without_resend_key_ok():
|
|
||||||
s = ReceiptWitnessSettings(
|
|
||||||
session_encryption_key="7reF42nmTwbdN21PBoubGp7h_FU8qSimstmlaMLoRK8=",
|
|
||||||
notifications_enabled=False,
|
|
||||||
resend_api_key="",
|
|
||||||
)
|
|
||||||
assert s.notifications_enabled is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_notifications_enabled_with_resend_key_ok():
|
|
||||||
s = ReceiptWitnessSettings(
|
|
||||||
session_encryption_key="7reF42nmTwbdN21PBoubGp7h_FU8qSimstmlaMLoRK8=",
|
|
||||||
notifications_enabled=True,
|
|
||||||
resend_api_key="re_test_1234567890",
|
|
||||||
)
|
|
||||||
assert s.resend_api_key == "re_test_1234567890"
|
|
||||||
Reference in New Issue
Block a user