diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
new file mode 100644
index 0000000000..013d242156
--- /dev/null
+++ b/.github/workflows/cd.yml
@@ -0,0 +1,50 @@
+name: cd
+
+on:
+ push:
+ branches: [ main ]
+
+jobs:
+ deploy:
+ name: Deploy
+ runs-on: ubuntu-latest
+ env:
+ DATABASE_URL: ${{ secrets.DATABASE_URL }}
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v6
+
+ - name: Set up Go
+ uses: actions/setup-go@v6
+ with:
+ go-version: "1.26.0"
+
+ - name: Build app
+ run: ./scripts/buildprod.sh
+
+ - id: 'auth'
+ uses: 'google-github-actions/auth@v2'
+ with:
+ credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
+
+ - name: 'Set up Cloud SDK'
+ uses: 'google-github-actions/setup-gcloud@v3'
+
+ - name: 'Use gcloud CLI'
+ run: 'gcloud info'
+
+
+ - name: Install goose
+ run: go install github.com/pressly/goose/v3/cmd/goose@latest
+
+ - name: Run migrations
+ run: ./scripts/migrateup.sh
+
+
+ - name: Build and push Docker image
+ run: gcloud builds submit --tag us-central1-docker.pkg.dev/notely-504410/notely-ar-repo/anthonytobiesqdev/notely:latest .
+
+ - name: Deploy to Cloud Run
+ run: gcloud run deploy notely --image us-central1-docker.pkg.dev/notely-504410/notely-ar-repo/anthonytobiesqdev/notely:latest --region us-central1 --allow-unauthenticated --project notely-504410 --max-instances=4
+
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000000..a5057f985d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,52 @@
+name: ci
+
+on:
+ pull_request:
+ branches: [main]
+
+jobs:
+ tests:
+ name: Tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v6
+
+ - name: Set up Go
+ uses: actions/setup-go@v6
+ with:
+ go-version: "1.26.0"
+
+ - name: Run Unit tests
+ run: go test -cover ./...
+
+ - name: Install gosec
+ run: go install github.com/securego/gosec/v2/cmd/gosec@latest
+
+ - name: Run Security checks
+ run: gosec ./...
+
+ style:
+ name: Style
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v6
+
+ - name: Set up Go
+ uses: actions/setup-go@v6
+ with:
+ go-version: "1.26.0"
+
+ - name: Install staticcheck
+ run: go install honnef.co/go/tools/cmd/staticcheck@latest
+
+ - name: Run Style checks
+ run: test -z $(go fmt ./...)
+
+ - name: Run Static checks
+ run: staticcheck ./...
+
+
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000..30cf57ed7c
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml
new file mode 100644
index 0000000000..644cdf0bd2
--- /dev/null
+++ b/.idea/go.imports.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/learn-cicd-starter.iml b/.idea/learn-cicd-starter.iml
new file mode 100644
index 0000000000..9c408ccfbd
--- /dev/null
+++ b/.idea/learn-cicd-starter.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000..d0adac68cd
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000000..35eb1ddfbb
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index c2bec0368b..af9cba268b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# learn-cicd-starter (Notely)
+
This repo contains the starter code for the "Notely" application for the "Learn CICD" course on [Boot.dev](https://boot.dev).
@@ -21,3 +22,6 @@ go build -o notely && ./notely
*This starts the server in non-database mode.* It will serve a simple webpage at `http://localhost:8080`.
You do *not* need to set up a database or any interactivity on the webpage yet. Instructions for that will come later in the course!
+
+Anthony Tobi's version of Boot.dev's Notely app.
+
diff --git a/internal/auth/get_api_key_test.go b/internal/auth/get_api_key_test.go
new file mode 100644
index 0000000000..4b102474e1
--- /dev/null
+++ b/internal/auth/get_api_key_test.go
@@ -0,0 +1,67 @@
+package auth
+
+import (
+ "errors"
+ "net/http"
+ "testing"
+)
+
+func TestGetAPIKey(t *testing.T) {
+ tests := []struct {
+ name string
+ headers http.Header
+ expectedKey string
+ expectedError error
+ }{
+ {
+ name: "Valid header",
+ headers: http.Header{"Authorization": []string{"ApiKey my-secret-key"}},
+ expectedKey: "my-secret-key",
+ expectedError: nil,
+ },
+ {
+ name: "Missing Authorization header",
+ headers: http.Header{},
+ expectedKey: "",
+ expectedError: ErrNoAuthHeaderIncluded,
+ },
+ {
+ name: "Empty Authorization header",
+ headers: http.Header{"Authorization": []string{""}},
+ expectedKey: "",
+ expectedError: ErrNoAuthHeaderIncluded,
+ },
+ {
+ name: "Malformed Authorization header - no space separator",
+ headers: http.Header{"Authorization": []string{"ApiKey-my-secret-key"}},
+ expectedKey: "",
+ expectedError: errors.New("malformed authorization header"),
+ },
+ {
+ name: "Malformed Authorization header - no ApiKey keyword",
+ headers: http.Header{"Authorization": []string{"Bearer my-secret-key"}},
+ expectedKey: "",
+ expectedError: errors.New("malformed authorization header"),
+ },
+ {
+ name: "Malformed Authorization header - no key provided",
+ headers: http.Header{"Authorization": []string{"ApiKey"}},
+ expectedKey: "",
+ expectedError: errors.New("malformed authorization header"),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ key, err := GetAPIKey(tt.headers)
+
+ if key != tt.expectedKey {
+ t.Errorf("expected key: %v, got: %v", tt.expectedKey, key)
+ }
+
+ if (err != nil && tt.expectedError == nil) || (err == nil && tt.expectedError != nil) || (err != nil && tt.expectedError != nil && err.Error() != tt.expectedError.Error()) {
+ t.Errorf("expected error: %v, got: %v", tt.expectedError, err)
+ }
+ })
+ }
+}
diff --git a/json.go b/json.go
index 1e6e7985e1..81a66b64c3 100644
--- a/json.go
+++ b/json.go
@@ -30,5 +30,9 @@ func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
return
}
w.WriteHeader(code)
- w.Write(dat)
+ _, err = w.Write(dat)
+ if err != nil {
+ log.Printf("Error writing response: %s", err)
+ return
+ }
}
diff --git a/main.go b/main.go
index 19d7366c5f..6b59c2cc5b 100644
--- a/main.go
+++ b/main.go
@@ -7,6 +7,8 @@ import (
"log"
"net/http"
"os"
+ "strconv"
+ "time"
"github.com/go-chi/chi"
"github.com/go-chi/cors"
@@ -89,10 +91,18 @@ func main() {
router.Mount("/v1", v1Router)
srv := &http.Server{
- Addr: ":" + port,
- Handler: router,
+ Addr: ":" + port,
+ Handler: router,
+ ReadHeaderTimeout: 5 * time.Second, // Fixes G112: Prevents Slowloris attacks
+ ReadTimeout: 10 * time.Second, // Recommended production hygiene
+ WriteTimeout: 10 * time.Second,
+ IdleTimeout: 120 * time.Second,
}
- log.Printf("Serving on port: %s\n", port)
+ portNum, err := strconv.Atoi(port)
+ if err != nil {
+ log.Fatalf("Invalid PORT: %v", err)
+ }
+ log.Printf("Serving on port: %d\n", portNum)
log.Fatal(srv.ListenAndServe())
}
diff --git a/static/index.html b/static/index.html
index 72be101028..5d4ad73c09 100644
--- a/static/index.html
+++ b/static/index.html
@@ -7,7 +7,7 @@
- Notely
+ Welcome to Notely