Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
37469aa
Update port mappings in docker-compose.prod.yaml
wizgod May 27, 2025
f6acf00
Update TypeScript configuration in tsconfig.json
wizgod May 27, 2025
b8bed73
Add Vite client types reference to vite-env.d.ts
wizgod May 27, 2025
605c23f
Add Host and Port fields to AppConfig struct
wizgod May 27, 2025
2bfd991
Add environment-specific configurations to EnvToml
wizgod May 27, 2025
4c5a409
Update Dockerfile for Go version and env configuration
wizgod May 28, 2025
00ac774
Refactor backend URL configuration
wizgod May 28, 2025
73795d9
Refactor URL handling and server configuration
wizgod May 28, 2025
4832ea7
Update default port logic in initDependencyInjection
wizgod May 28, 2025
439859f
Update Dockerfile for Go version flexibility
wizgod May 28, 2025
f4d2ed0
Add CORS support to EnvToml and AppConfig
wizgod May 28, 2025
8c15987
Add Cors field to AppConfig for CORS settings
wizgod May 28, 2025
17ba03d
Enhance CORS middleware to support multiple origins
wizgod May 28, 2025
568fa0d
Update CORS middleware configuration in soarpipeline.go
wizgod May 28, 2025
43a3bd5
chore(docker): remove commented out line 2 from backend.prod.dockerfile
wizgod May 28, 2025
e68ccd1
refactor(env_toml): extract HostConfig, rename Cors to AllowedOrigins…
wizgod May 28, 2025
18b629e
refactor(models): rename Cors to AllowedOrigins for clarity in app_co…
wizgod May 28, 2025
2a65539
refactor(env): move backendHost to env.ts to isolate env logic and su…
wizgod May 28, 2025
ec47850
refactor(soarpipeline): update Cors usage to AllowedOrigins to match …
wizgod May 28, 2025
0125e38
Fixed unit tests failing
connellr023 Jul 7, 2025
689ed16
Fixed formatting
connellr023 Jul 7, 2025
438a6e0
Updated task checklist and updated readme information for updated .en…
connellr023 Jul 12, 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
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,28 @@
- Spike in chamber pressure.
- Decrease in oxidizer pressure.
- Decrease in oxidizer mass.
- [ ] Allow manual input for oxidizer shutoff time.
- [x] Allow manual input for test start and end.
- [ ] Define test end when chamber pressure returns to normal.

### Data Handling

- [ ] Provide a way to download the filtered data.
- [ ] Provide a way to download the filtered data. (Needs review)
- [ ] Display both unfiltered and filtered lines on the graph.
- [ ] Add a legend to the Plotly graph to toggle lines on/off.
- [x] Add a legend to the Plotly graph to toggle lines on/off.

### Graph Features

- [ ] Implement two data smoothing filters that do not distort the data.
- [ ] Add a button to enable full-screen mode for the graph.
- [ ] Display mass flow rate on the graph.
- [x] Implement two data smoothing filters that do not distort the data.
- [ ] Integrate the data smoothing filters into the graph.
- [x] Add a button to enable full-screen mode for the graph.
- [ ] Display mass flow rate on the graph. (Needs review)
- [ ] (Optional) Plot fill mass and pressure over time.

### Website Features

- [ ] Integrate a home page into the application.
- [x] Integrate a home page into the application.
- [ ] Implement a file organization system:
- [ ] Use a custom file extension or metadata to categorize files.
- [x] Use a custom file extension or metadata to categorize files.
- [ ] Enable filtering of files for different website sections.

## Example `.env.toml` file
Expand All @@ -54,6 +55,18 @@ in_production=false

# Place GMail addresses that are allowed to use the platform here.
whitelist = ["[email protected]", "[email protected]"]

# Development configuration.
[dev]
host = "http://localhost"
port = "8080"
allowedorigins = ["http://localhost:5173"]

# Production configuration.
[prod]
host = "https://api.soarpipeline.com"
port = "8080"
allowedorigins = ["https://soarpipeline.com", "https://api.soarpipeline.com"]
```

## Running the backend
Expand Down
9 changes: 7 additions & 2 deletions build/package/prod/backend.prod.dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
FROM golang:latest AS build
# Use the latest Go image if root access is available and version flexibility is acceptable.
# Use a specific Go version for consistency and when rootless Docker environments require it.
FROM golang:1.23 AS build

# Set the working directory in the container
WORKDIR /app

# Copy the Go module files
# Copy the Go module files. Make sure go.sum exists. If not, or if there's an error, run `go mod tidy` locally.
COPY go.mod go.sum ./
RUN go mod download

Expand All @@ -22,6 +24,9 @@ RUN apk --no-cache add ca-certificates
# Set the working directory in the container
WORKDIR /root/

# Copy the .env.toml file
COPY .env.toml .

# Copy the built Go application from the build stage
COPY --from=build /app/soarpipeline .

Expand Down
33 changes: 25 additions & 8 deletions cmd/soarpipeline/soarpipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,35 @@ const (
readTimeout = 10 * time.Second
writeTimeout = 15 * time.Second
idleTimeout = 10 * time.Second
)

const (
addr = ":8080"
envTomlFile = ".env.toml"
)

func initDependencyInjection() (*controllers.DependencyInjection, error) {
var env models.EnvToml

if _, err := toml.DecodeFile(envTomlFile, &env); err != nil {
return nil, err
}

// Determine correct base URL based on environment
host := env.Dev.Host
port := env.Dev.Port
if env.InProduction {
host = env.Prod.Host
port = env.Prod.Port
}

// For cleaner redirect URLs, omit port if it's the default for the scheme:
// - 443 for HTTPS (production)
// - 80 for HTTP (development)
portSuffix := ":" + port
useDefaultPort := (env.InProduction && (port == "443" || port == "8080")) || (!env.InProduction && port == "80")
if useDefaultPort {
portSuffix = ""
}

// This should match route for callback in the router
redirectURL := fmt.Sprintf("http://localhost%s/auth/google/callback", addr)
redirectURL := fmt.Sprintf("%s%s/auth/google/callback", host, portSuffix)

oauthCfg := oauth2.Config{
RedirectURL: redirectURL,
Expand All @@ -62,14 +75,17 @@ func main() {
}

i, err := initDependencyInjection()

if err != nil {
panic(err)
}

// Determine correct port to listen on
port := i.AppConfig.Port
addr := ":" + port

// Set up the router and middleware
r := chi.NewRouter()
middlewares.UseCorsMiddleware(r, i.AppConfig.InProduction)
middlewares.UseCorsMiddleware(r, i.AppConfig.AllowedOrigins)

// Subrouter for authentication
r.Route("/auth", func(r chi.Router) {
Expand Down Expand Up @@ -97,7 +113,8 @@ func main() {
})
})

fmt.Println("Server running on http://localhost" + addr)
fmt.Printf("Server listening on %s\n", addr)
fmt.Printf("Public-facing host is %s\n", i.AppConfig.Host)

// Start the server
server := &http.Server{
Expand Down
6 changes: 4 additions & 2 deletions docker-compose.prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ services:
context: .
dockerfile: ./build/package/prod/frontend.prod.dockerfile
ports:
- "80:80"
- "8081:80"
depends_on:
- backend
restart: unless-stopped

backend:
build:
context: .
dockerfile: ./build/package/prod/backend.prod.dockerfile
ports:
- "8080:8080"
- "8082:8080"
restart: unless-stopped
15 changes: 2 additions & 13 deletions internal/middlewares/cors.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,9 @@ const (
preflightCacheMaxAge = 300 * time.Second
)

const (
devCorsOrigin = "http://localhost:5173"
prodCorsOrigin = "https://soarpipeline.com"
)

func UseCorsMiddleware(router chi.Router, inProduction bool) {
allowedOrigin := devCorsOrigin

if inProduction {
allowedOrigin = prodCorsOrigin
}

func UseCorsMiddleware(router chi.Router, allowedOrigins []string) {
corsConfig := cors.New(cors.Options{
AllowedOrigins: []string{allowedOrigin},
AllowedOrigins: allowedOrigins,
AllowedMethods: []string{"GET", "POST", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
ExposedHeaders: []string{"Link"},
Expand Down
9 changes: 6 additions & 3 deletions internal/models/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import (
)

type AppConfig struct {
InProduction bool
SigningKey []byte
Whitelist set.HashSet[string]
InProduction bool
SigningKey []byte
Whitelist set.HashSet[string]
Host string
Port string
AllowedOrigins []string
}
33 changes: 25 additions & 8 deletions internal/models/env_toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ package models

import set "soarpipeline/pkg/set"

type HostConfig struct {
Host string `toml:"host"`
Port string `toml:"port"`
AllowedOrigins []string `toml:"allowedorigins"`
}

type EnvToml struct {
GoogleClientID string `toml:"google_client_id"`
GoogleClientSecret string `toml:"google_client_secret"`
SigningKey string `toml:"signing_key"`
InProduction bool `toml:"in_production"`
Whitelist []string `toml:"whitelist"`
GoogleClientID string `toml:"google_client_id"`
GoogleClientSecret string `toml:"google_client_secret"`
SigningKey string `toml:"signing_key"`
InProduction bool `toml:"in_production"`
Whitelist []string `toml:"whitelist"`
Dev HostConfig `toml:"dev"`
Prod HostConfig `toml:"prod"`
}

func (e *EnvToml) ToAppConfig() AppConfig {
Expand All @@ -17,10 +25,19 @@ func (e *EnvToml) ToAppConfig() AppConfig {
whitelistSet.Put(item)
}

// Select the appropriate environment config
env := e.Dev
if e.InProduction {
env = e.Prod
}

config := AppConfig{
InProduction: e.InProduction,
SigningKey: []byte(e.SigningKey),
Whitelist: whitelistSet,
InProduction: e.InProduction,
SigningKey: []byte(e.SigningKey),
Whitelist: whitelistSet,
Host: env.Host,
Port: env.Port,
AllowedOrigins: env.AllowedOrigins,
}

return config
Expand Down
7 changes: 7 additions & 0 deletions web/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Polyfill import.meta for Jest environment
global.import = {};
global.import.meta = {
env: {
VITE_BACKEND_HOST: "http://localhost:3000",
},
};
2 changes: 1 addition & 1 deletion web/src/__tests__/formatBytes.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatBytes } from "$lib/utils/usage";
import { formatBytes } from "$lib/utils/formatBytes";

describe("formatBytes", () => {
test("returns '0 Bytes' when input is 0", () => {
Expand Down
12 changes: 12 additions & 0 deletions web/src/lib/config/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const getBackendHost = (): string => {
if (
typeof import.meta !== "undefined" &&
import.meta.env?.VITE_BACKEND_HOST
) {
return import.meta.env.VITE_BACKEND_HOST;
} else {
return "http://localhost:8080";
}
};

export const backendHost = getBackendHost();
26 changes: 10 additions & 16 deletions web/src/lib/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
export const backendDevPort = 8080;
// Read from environment variable or fallback to localhost
import { backendHost } from "../config/env";

export const redirectUriParam = "redirect_uri";

export const endpointMapping = Object.freeze({
getGoogleLoginUrl: new URL(
`http://localhost:${backendDevPort}/auth/google/login`,
),
getMeUrl: new URL(`http://localhost:${backendDevPort}/auth/me`),
postLogoutUrl: new URL(`http://localhost:${backendDevPort}/auth/logout`),
uploadStaticFireUrl: new URL(
`http://localhost:${backendDevPort}/api/staticfire/upload`,
),
getStaticFireMetadataUrl: new URL(
`http://localhost:${backendDevPort}/api/staticfire/metadata`,
),
postStaticFireColumnsUrl: new URL(
`http://localhost:${backendDevPort}/api/staticfire/columns`,
),
getUsageURL: new URL(`http://localhost:${backendDevPort}/api/usage`),
getGoogleLoginUrl: new URL(`${backendHost}/auth/google/login`),
getMeUrl: new URL(`${backendHost}/auth/me`),
postLogoutUrl: new URL(`${backendHost}/auth/logout`),
uploadStaticFireUrl: new URL(`${backendHost}/api/staticfire/upload`),
getStaticFireMetadataUrl: new URL(`${backendHost}/api/staticfire/metadata`),
postStaticFireColumnsUrl: new URL(`${backendHost}/api/staticfire/columns`),
getUsageURL: new URL(`${backendHost}/api/usage`),
});
14 changes: 14 additions & 0 deletions web/src/lib/utils/formatBytes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Function to Format Bytes
export function formatBytes(bytes: number, decimals: number = 2): string {
if (bytes === 0) return "0 Bytes";

const sizes = ["Bytes", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(1024));

if (i >= sizes.length) return `${bytes} Bytes`;

const value = bytes / Math.pow(1024, i);
return i === 0 || value < 1
? `${Math.floor(value)} ${sizes[i]}`
: `${value.toFixed(decimals)} ${sizes[i]}`;
}
15 changes: 0 additions & 15 deletions web/src/lib/utils/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,3 @@ export async function getStorageUsage(): Promise<StorageUsageResponse | null> {
return null;
}
}

// Function to Format Bytes
export function formatBytes(bytes: number, decimals: number = 2): string {
if (bytes === 0) return "0 Bytes";

const sizes = ["Bytes", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(1024));

if (i >= sizes.length) return `${bytes} Bytes`;

const value = bytes / Math.pow(1024, i);
return i === 0 || value < 1
? `${Math.floor(value)} ${sizes[i]}`
: `${value.toFixed(decimals)} ${sizes[i]}`;
}
1 change: 1 addition & 0 deletions web/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
7 changes: 5 additions & 2 deletions web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
"moduleResolution": "bundler",
"module": "esnext",
"target": "es2020",
},
"include": ["src"]
}
Loading