Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
05334c9
chore: added db cluster creation on infra deployment
vladyslav-savchenko-softcery Sep 19, 2025
a0ddf56
chore: added ssl configuration in db module
vladyslav-savchenko-softcery Sep 19, 2025
5a098ce
refactor(api): use separate DB connection vars instead of DB_URL
vladyslav-savchenko-softcery Sep 21, 2025
19da6c5
chore(api): added temo logs for db vars
vladyslav-savchenko-softcery Sep 21, 2025
8411897
chore(api): test logs for db ca
vladyslav-savchenko-softcery Sep 21, 2025
5d59fdd
chore(infra): updated infra with database cluster, updated s3 back wi…
vladyslav-savchenko-softcery Sep 22, 2025
b7e8c1a
chore(api): updated config staging sh with database configuration vars
vladyslav-savchenko-softcery Sep 22, 2025
30561e2
chore: updated gh workflow with new DB secrets instead of old DB URL
vladyslav-savchenko-softcery Sep 22, 2025
058880f
test: updated gh workflow with current branch, updated variables crea…
vladyslav-savchenko-softcery Sep 22, 2025
fc2f916
chore: updated terraform version in gh workflow
vladyslav-savchenko-softcery Sep 22, 2025
95e11fa
chore(infra): returned create_database to true
vladyslav-savchenko-softcery Sep 22, 2025
dcdfce7
chore(infra): returned create_database to true
vladyslav-savchenko-softcery Sep 22, 2025
ec16633
test: comment out db vars in workflow
vladyslav-savchenko-softcery Sep 22, 2025
3bcd0f3
test: remove db vars in workflow
vladyslav-savchenko-softcery Sep 22, 2025
7c325eb
chore(api): removed test console logs
vladyslav-savchenko-softcery Sep 22, 2025
7e84c21
infra: added logtail
vladyslav-savchenko-softcery Sep 23, 2025
ba8fe99
chore: changed actual branch in infra and gh workflow to staging
vladyslav-savchenko-softcery Sep 23, 2025
e9deb04
chore(infra): refactored the code by splitting in separate output and…
vladyslav-savchenko-softcery Sep 24, 2025
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
7 changes: 4 additions & 3 deletions .github/workflows/release-staging.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

name: Release – Staging

on:
Expand All @@ -19,16 +20,16 @@ jobs:
timeout-minutes: 15
environment: staging
env:
TF_VERSION: 1.4.6
TF_VERSION: 1.13.3
APP_NAME: 'stag-saas-template'
SUPABASE_URL: ${{secrets.SUPABASE_URL}}
SUPABASE_SECRET_KEY: ${{secrets.SUPABASE_SECRET_KEY}}
GOOGLE_OAUTH_CLIENT_ID: ${{secrets.GOOGLE_OAUTH_CLIENT_ID}}
GOOGLE_OAUTH_SECRET: ${{secrets.GOOGLE_OAUTH_SECRET}}
DB_URL: ${{secrets.DB_URL}}
STRIPE_API_KEY: ${{secrets.STRIPE_API_KEY}}
STRIPE_WEBHOOK_SIGNING_SECRET: ${{secrets.STRIPE_WEBHOOK_SIGNING_SECRET}}
JWT_SECRET: ${{secrets.JWT_SECRET}}
LOGTAIL_SOURCE_TOKEN: ${{secrets.LOGTAIL_SOURCE_TOKEN}}
API_BASE_URL: ${{vars.API_BASE_URL}}

steps:
Expand All @@ -43,7 +44,6 @@ jobs:
SUPABASE_SECRET_KEY=${{ env.SUPABASE_SECRET_KEY }}
GOOGLE_OAUTH_CLIENT_ID=${{ env.GOOGLE_OAUTH_CLIENT_ID }}
GOOGLE_OAUTH_SECRET=${{ env.GOOGLE_OAUTH_SECRET }}
DB_URL=${{ env.DB_URL }}
STRIPE_API_KEY=${{ env.STRIPE_API_KEY }}
STRIPE_WEBHOOK_SIGNING_SECRET=${{ env.STRIPE_WEBHOOK_SIGNING_SECRET }}
JWT_SECRET=${{ env.JWT_SECRET }}
Expand All @@ -58,6 +58,7 @@ jobs:
run: |
sed -i "s/autoreplace_do_token/$DIGITAL_OCEAN_TOKEN/g" variables.tf
sed -i "s/autoreplace_github_token/$GITHUB_TOKEN/g" variables.tf
sed -i "s/autoreplace_logtail_token/$LOGTAIL_SOURCE_TOKEN/g" variables.tf

- name: Terraform init and apply
working-directory: ./infra/environments/staging
Expand Down
29 changes: 29 additions & 0 deletions api/config/config.staging.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,35 @@
# Database Configuration
# IMPORTANT: Only uncomment and configure these if create_database = false in your terraform.tfvars
# If using DigitalOcean managed database (create_database = true), these are automatically provided by Terraform
# DO NOT include these variables when using Terraform-managed database
#DB_HOST="YOUR_DB_HOST"
#DB_PORT="YOUR_DB_PORT"
#DB_USER="YOUR_DB_USER"
#DB_PASSWORD="YOUR_DB_PASSWORD"
#DB_NAME="YOUR_DB_NAME"
#DB_CA="" # Add CA certificate content if needed for SSL connections

# Supabase Configuration
SUPABASE_URL=https://your-supabase-app-name.supabase.co
SUPABASE_SECRET_KEY=YOUR_SUPABASE_SECRET_KEY_HERE

# Google OAuth Configuration
GOOGLE_OAUTH_CLIENT_ID=YOUR_GOOGLE_OAUTH_CLIENT_ID_HERE
GOOGLE_OAUTH_SECRET=YOUR_GOOGLE_OAUTH_SECRET_HERE
GOOGLE_OAUTH_CALLBACK_URL=https://stag-saas-template-46ccu.ondigitalocean.app/api/auth/oauth/google-callback

# Stripe Configuration
STRIPE_API_KEY=YOUR_STRIPE_API_KEY_HERE
STRIPE_WEBHOOK_SIGNING_SECRET=YOUR_STRIPE_WEBHOOK_SIGNING_SECRET_HERE

# JWT Configuration
JWT_SECRET=YOUR_JWT_SECRET_HERE

# Application Configuration
PASSWORD_RESET_REDIRECT_URL=https://stag-saas-template-46ccu.ondigitalocean.app/reset
CLIENT_AUTH_REDIRECT_URL=https://stag-saas-template-46ccu.ondigitalocean.app/sign-in
PASSWORD_RECOVERY_TIME=900
BILLING_SUCCESS_REDIRECT_URL=https://stag-saas-template-46ccu.ondigitalocean.app/billing
TRIAL_PERIOD_DURATION_DAYS=14

TEST=test
20 changes: 18 additions & 2 deletions api/src/shared/application/models/app-config.model.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import { IsInt, IsPositive, IsString } from 'class-validator';
import { IsInt, IsOptional, IsPositive, IsString } from 'class-validator';

export class AppConfigModel {
@IsString()
TEST: string;

@IsString()
DB_URL: string;
DB_HOST: string;

@IsString()
DB_PORT: string;

@IsString()
DB_USER: string;

@IsString()
DB_PASSWORD: string;

@IsString()
DB_NAME: string;

@IsOptional()
@IsString()
DB_CA?: string;

@IsString()
SUPABASE_URL: string;
Expand Down
25 changes: 21 additions & 4 deletions api/src/shared/infrastructure/database/database.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,27 @@ import { DrizzleDbContext } from './drizzle/db-context/drizzle-db-context';
import { mergeDbdSchema } from './drizzle/schema/merged-schema';

export const drizzlePostgresModule = DrizzlePostgresModule.registerAsync({
useFactory: (appConfig: IAppConfigService) => ({
db: { config: { connectionString: appConfig.get('DB_URL') }, connection: 'pool' },
schema: mergeDbdSchema,
}),
useFactory: (appConfig: IAppConfigService) => {
const databaseCa = appConfig.get('DB_CA');

return {
db: {
config: {
host: appConfig.get('DB_HOST'),
port: parseInt(appConfig.get('DB_PORT')),
user: appConfig.get('DB_USER'),
password: appConfig.get('DB_PASSWORD'),
database: appConfig.get('DB_NAME'),
ssl: databaseCa ? {
rejectUnauthorized: true,
ca: databaseCa,
} : false,
},
connection: 'pool'
},
schema: mergeDbdSchema,
};
},
inject: [BaseToken.APP_CONFIG],
});

Expand Down
16 changes: 14 additions & 2 deletions infra/environments/staging/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ terraform {
}

backend "s3" {
endpoint = "fra1.digitaloceanspaces.com"
endpoints = {
s3 = "https://fra1.digitaloceanspaces.com"
}
region = "us-west-1"
bucket = "saas-template-space"
key = "staging_terraform.tfstate"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_region_validation = true
skip_s3_checksum = true
skip_requesting_account_id = true
}
}

Expand All @@ -29,7 +34,7 @@ module "env_config" {
module "saas_template_app" {
source = "../../modules/app"
name = "stag-saas-template"
region = "lon"
region = "fra"
source_dir_static = "./web"
source_dir_api = "./api"
environment_slug = "node-js"
Expand All @@ -52,4 +57,11 @@ module "saas_template_app" {

logtail_source_name = "saas-staging-logtail"
logtail_source_token = var.logtail_source_token

# Database configuration
project_name = var.project_name
create_database = var.create_database
database_size = var.database_size
database_node_count = var.database_node_count
database_region = var.database_region
}
12 changes: 12 additions & 0 deletions infra/environments/staging/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Database outputs
output "database_uri" {
value = module.saas_template_app.database_uri
sensitive = true
description = "Database connection URI (if database is created)"
}

# Application outputs
output "app_url" {
value = module.saas_template_app.app_url
description = "The live URL of the deployed app"
}
32 changes: 31 additions & 1 deletion infra/environments/staging/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,41 @@ variable "do_token" {
variable "logtail_source_token" {
description = "Logtail Token for the application logs"
type = string
default = ""
default = "autoreplace_logtail_token"
}

variable "github_token" {
description = "Github token for the build / deployment process"
type = string
default = "autoreplace_github_token"
}

variable "project_name" {
description = "Name of the DigitalOcean project"
type = string
default = "SaaS Template"
}

variable "create_database" {
description = "Whether to create a managed database"
type = bool
default = true
}

variable "database_size" {
description = "Size of the database cluster"
type = string
default = "db-s-1vcpu-1gb"
}

variable "database_node_count" {
description = "Number of nodes in the database cluster"
type = number
default = 1
}

variable "database_region" {
description = "Region for the database cluster"
type = string
default = "fra1"
}
29 changes: 29 additions & 0 deletions infra/modules/app/database.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Database cluster and related resources
resource "digitalocean_database_cluster" "postgres" {
count = var.create_database ? 1 : 0
name = "${var.name}-db"
engine = "pg"
version = "15"
size = var.database_size
region = var.database_region
node_count = var.database_node_count

project_id = digitalocean_project.saas_template.id
}

resource "digitalocean_database_db" "app_database" {
count = var.create_database ? 1 : 0
cluster_id = digitalocean_database_cluster.postgres[0].id
name = "app"
}

resource "digitalocean_database_user" "app_user" {
count = var.create_database ? 1 : 0
cluster_id = digitalocean_database_cluster.postgres[0].id
name = "appuser"
}

data "digitalocean_database_ca" "postgres" {
count = var.create_database ? 1 : 0
cluster_id = digitalocean_database_cluster.postgres[0].id
}
73 changes: 65 additions & 8 deletions infra/modules/app/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ provider "digitalocean" {


resource "digitalocean_project" "saas_template" {
name = "SaaS Template"
name = var.project_name
description = "A project for the SaaS Template application"
purpose = "Web Application"
environment = "Development"
}

resource "digitalocean_app" "saas_template" {
depends_on = [ digitalocean_project.saas_template ]
depends_on = [
digitalocean_project.saas_template,
digitalocean_database_cluster.postgres
]
project_id = digitalocean_project.saas_template.id
spec {
name = var.name
Expand Down Expand Up @@ -69,6 +72,60 @@ resource "digitalocean_app" "saas_template" {
}
}

dynamic "env" {
for_each = var.create_database ? [1] : []
content {
key = "DB_HOST"
scope = "RUN_TIME"
value = digitalocean_database_cluster.postgres[0].host
}
}

dynamic "env" {
for_each = var.create_database ? [1] : []
content {
key = "DB_PORT"
scope = "RUN_TIME"
value = tostring(digitalocean_database_cluster.postgres[0].port)
}
}

dynamic "env" {
for_each = var.create_database ? [1] : []
content {
key = "DB_USER"
scope = "RUN_TIME"
value = digitalocean_database_user.app_user[0].name
}
}

dynamic "env" {
for_each = var.create_database ? [1] : []
content {
key = "DB_PASSWORD"
scope = "RUN_TIME"
value = digitalocean_database_user.app_user[0].password
}
}

dynamic "env" {
for_each = var.create_database ? [1] : []
content {
key = "DB_NAME"
scope = "RUN_TIME"
value = digitalocean_database_db.app_database[0].name
}
}

dynamic "env" {
for_each = var.create_database ? [1] : []
content {
key = "DB_CA"
scope = "RUN_TIME"
value = data.digitalocean_database_ca.postgres[0].certificate
}
}

github {
repo = var.gh_repository
deploy_on_push = var.deploy_on_push
Expand All @@ -87,13 +144,13 @@ resource "digitalocean_app" "saas_template" {
port = var.api_http_port
}

# log_destination {
# name = var.logtail_source_name
log_destination {
name = var.logtail_source_name

# logtail {
# token = var.logtail_source_token
# }
# }
logtail {
token = var.logtail_source_token
}
}
}

ingress {
Expand Down
Loading