Skip to content

Commit 05b8e36

Browse files
authored
Merge pull request #40 from Buffden/ci-cd/setup-pipeline-for-deployment
refactorization
2 parents dd4d7cb + 7319700 commit 05b8e36

11 files changed

Lines changed: 55 additions & 52 deletions

File tree

README.md

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ A single-region, production-oriented URL shortener built with Spring Boot and An
3030
- Rate limiting (token bucket)
3131
- Soft delete support
3232
- Custom aliases (feature-flagged)
33-
- Enhanced observability
3433

3534
[![v2 HLD](diagrams/docs/architecture/00-baseline/v2/url-shortener-v2-hld.svg)](diagrams/docs/architecture/00-baseline/v2/url-shortener-v2-hld.svg)
3635

@@ -46,7 +45,7 @@ A single-region, production-oriented URL shortener built with Spring Boot and An
4645
| Migrations | Flyway |
4746
| Reverse proxy | Nginx |
4847
| Containerization | Docker, Docker Compose |
49-
| Cloud | AWS (EC2, RDS, ALB, S3, CloudFront, Route 53, SSM) |
48+
| Cloud | AWS (EC2, RDS, ALB, S3, CloudFront) |
5049
| CI/CD | GitHub Actions → GHCR → EC2 via SSM |
5150
| Observability | Micrometer, Prometheus, CloudWatch |
5251

@@ -58,8 +57,6 @@ A single-region, production-oriented URL shortener built with Spring Boot and An
5857
| --- | --- | --- |
5958
| `POST` | `/api/urls` | Shorten a URL |
6059
| `GET` | `/{shortCode}` | Redirect to original URL |
61-
| `GET` | `/actuator/health` | Health check |
62-
| `GET` | `/actuator/prometheus` | Metrics |
6360

6461
---
6562

@@ -69,29 +66,23 @@ A single-region, production-oriented URL shortener built with Spring Boot and An
6966

7067
- Docker & Docker Compose
7168
- Java 21 (for running backend without Docker)
72-
- Node 20+ (for running frontend without Docker)
7369

7470
### Full stack (backend + database + nginx)
7571

7672
```bash
77-
# Copy and fill in required env vars
78-
cp .env.example .env
79-
8073
docker compose up --build
8174
```
8275

8376
App available at `http://localhost:8080`.
8477

85-
### Backend only (with local Postgres)
78+
### Backend only
8679

8780
```bash
8881
cd tinyurl
8982
./gradlew bootRun
9083
```
9184

92-
Backend runs on `http://localhost:8080` by default.
93-
94-
### Run backend tests
85+
### Run tests
9586

9687
```bash
9788
cd tinyurl
@@ -102,17 +93,6 @@ cd tinyurl
10293
10394
---
10495

105-
## Environments
106-
107-
| Environment | How to run | URL |
108-
| --- | --- | --- |
109-
| Local (full stack) | `docker compose up` from repo root | `http://localhost:8080` |
110-
| Local (backend only) | `./gradlew bootRun` in `tinyurl/` | `http://localhost:8080` |
111-
| Local (frontend dev) | See [tinyurl-gui/README.md](tinyurl-gui/README.md) | `http://localhost:4200` |
112-
| Production | Auto-deploy on merge to `main` | [go.buffden.com](https://go.buffden.com) |
113-
114-
---
115-
11696
## Project Structure
11797

11898
```text
@@ -123,23 +103,13 @@ infra/
123103
postgres/ # DB init scripts
124104
docs/
125105
architecture/ # ADRs and architecture docs
126-
deployment/ # AWS deployment phases (A–F)
106+
deployment/ # AWS deployment runbook (phases A–F)
127107
diagrams/ # Architecture diagrams (SVG)
128108
docker-compose.yml # Local dev stack
129-
docker-compose.prod.yml # Production stack (no Postgres — uses RDS)
130109
```
131110

132111
---
133112

134113
## Deployment
135114

136-
Production runs on AWS (`us-east-1`). See [`docs/deployment/`](docs/deployment/README.md) for the full deployment runbook (infrastructure provisioning, secrets, CI/CD, observability, hardening).
137-
138-
```text
139-
Route 53
140-
tinyurl.buffden.com → CloudFront → S3 (Angular SPA)
141-
go.buffden.com → ALB → EC2 (Nginx + Spring Boot) → RDS PostgreSQL
142-
```
143-
144-
Docker image: `ghcr.io/buffden/tinyurl-api`
145-
Deploy trigger: merge to `main` via GitHub Actions
115+
Production is deployed on AWS. See [`docs/deployment/`](docs/deployment/README.md) for the full runbook.

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ services:
2929
postgres:
3030
condition: service_healthy
3131
environment:
32+
SPRING_PROFILES_ACTIVE: dev
3233
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/tinyurl
3334
SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME:?SPRING_DATASOURCE_USERNAME is required}
3435
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD:?SPRING_DATASOURCE_PASSWORD is required}

infra/nginx/nginx.conf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ http {
99
listen 80;
1010
server_name _;
1111

12+
add_header X-Frame-Options "DENY" always;
13+
add_header X-Content-Type-Options "nosniff" always;
14+
add_header X-XSS-Protection "0" always;
15+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
16+
1217
location / {
1318
proxy_pass http://backend_upstream;
1419
proxy_http_version 1.1;

infra/postgres/init/002_app_user.sh

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66

77
set -e
88

9+
# Escape single quotes in credentials to prevent SQL injection
10+
APP_USER="${SPRING_DATASOURCE_USERNAME//\'/\'\'}"
11+
APP_PASS="${SPRING_DATASOURCE_PASSWORD//\'/\'\'}"
12+
913
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
10-
CREATE USER $SPRING_DATASOURCE_USERNAME WITH PASSWORD '$SPRING_DATASOURCE_PASSWORD';
11-
GRANT CONNECT ON DATABASE $POSTGRES_DB TO $SPRING_DATASOURCE_USERNAME;
12-
GRANT USAGE ON SCHEMA public TO $SPRING_DATASOURCE_USERNAME;
13-
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE url_mappings TO $SPRING_DATASOURCE_USERNAME;
14-
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $SPRING_DATASOURCE_USERNAME;
14+
CREATE USER "$APP_USER" WITH PASSWORD '$APP_PASS';
15+
GRANT CONNECT ON DATABASE "$POSTGRES_DB" TO "$APP_USER";
16+
GRANT USAGE ON SCHEMA public TO "$APP_USER";
17+
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE url_mappings TO "$APP_USER";
18+
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO "$APP_USER";
1519
ALTER DEFAULT PRIVILEGES IN SCHEMA public
16-
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO $SPRING_DATASOURCE_USERNAME;
20+
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO "$APP_USER";
1721
ALTER DEFAULT PRIVILEGES IN SCHEMA public
18-
GRANT USAGE, SELECT ON SEQUENCES TO $SPRING_DATASOURCE_USERNAME;
22+
GRANT USAGE, SELECT ON SEQUENCES TO "$APP_USER";
1923
EOSQL

tinyurl/Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ RUN chmod +x gradlew && ./gradlew --no-daemon bootJar
1313
FROM eclipse-temurin:21-jre-alpine
1414
WORKDIR /app
1515

16+
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
17+
1618
COPY --from=build /workspace/build/libs/*.jar /app/app.jar
1719

20+
RUN chown appuser:appgroup /app/app.jar
21+
22+
USER appuser
23+
1824
EXPOSE 8080
1925

2026
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

tinyurl/src/main/java/com/tinyurl/config/AppProperties.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import org.springframework.boot.context.properties.ConfigurationProperties;
44

5+
import java.util.List;
6+
57
@ConfigurationProperties(prefix = "tinyurl")
68
public record AppProperties(
79
String baseUrl,
810
Integer defaultExpiryDays,
9-
Integer shortCodeMinLength
11+
Integer shortCodeMinLength,
12+
Cors cors
1013
) {
14+
public record Cors(List<String> allowedOrigins) {}
1115
}

tinyurl/src/main/java/com/tinyurl/config/CorsConfig.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.tinyurl.config;
22

3-
import org.springframework.beans.factory.annotation.Value;
43
import org.springframework.context.annotation.Bean;
54
import org.springframework.context.annotation.Configuration;
65
import org.springframework.web.cors.CorsConfiguration;
@@ -12,13 +11,16 @@
1211
@Configuration
1312
public class CorsConfig {
1413

15-
@Value("${tinyurl.cors.allowed-origins}")
16-
private List<String> allowedOrigins;
14+
private final AppProperties appProperties;
15+
16+
public CorsConfig(AppProperties appProperties) {
17+
this.appProperties = appProperties;
18+
}
1719

1820
@Bean
1921
public CorsFilter corsFilter() {
2022
CorsConfiguration config = new CorsConfiguration();
21-
config.setAllowedOrigins(allowedOrigins);
23+
config.setAllowedOrigins(appProperties.cors().allowedOrigins());
2224
config.setAllowedMethods(List.of("GET", "POST", "OPTIONS"));
2325
config.setAllowedHeaders(List.of("Content-Type", "Accept"));
2426
config.setAllowCredentials(false);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Development-only overrides.
2+
# Activate with: SPRING_PROFILES_ACTIVE=dev
3+
# Never use this profile in production or CI/CD environments.
4+
5+
management:
6+
endpoints:
7+
web:
8+
exposure:
9+
include: health,metrics,prometheus

tinyurl/src/main/resources/application-prod.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ management:
1212

1313
tinyurl:
1414
cors:
15-
allowed-origins: "https://tinyurl.buffden.com"
15+
allowed-origins:
16+
- "https://tinyurl.buffden.com"

tinyurl/src/main/resources/application.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ management:
2929
endpoints:
3030
web:
3131
exposure:
32-
# Default: expose metrics for dev/test. Restricted in production via application-prod.yaml
33-
include: health,metrics,prometheus
32+
# Restricted to health by default. Metrics/prometheus enabled via application-dev.yaml
33+
include: health
3434
metrics:
3535
tags:
3636
application: ${spring.application.name}
@@ -45,4 +45,5 @@ tinyurl:
4545
default-expiry-days: ${TINYURL_DEFAULT_EXPIRY_DAYS:180}
4646
short-code-min-length: ${TINYURL_SHORT_CODE_MIN_LENGTH:6}
4747
cors:
48-
allowed-origins: "http://localhost:4200"
48+
allowed-origins:
49+
- "http://localhost:4200"

0 commit comments

Comments
 (0)