From 923cd02a6d4b055a70e3285814a7cfc975629682 Mon Sep 17 00:00:00 2001 From: Bahati308 Date: Fri, 9 Jan 2026 12:07:31 +0300 Subject: [PATCH 1/4] Add code format --- .github/CICD.md | 158 +- .github/workflows/ci.yml | 31 +- .github/workflows/synkronus-cli.yml | 53 + .github/workflows/synkronus-docker.yml | 27 + .github/workflows/synkronus-portal-docker.yml | 23 + synkronus-portal/package.json | 3 + synkronus-portal/src/App.tsx | 12 +- synkronus-portal/src/components/Login.css | 24 +- synkronus-portal/src/components/Login.tsx | 47 +- .../src/components/ProtectedRoute.tsx | 29 +- synkronus-portal/src/contexts/AuthContext.tsx | 121 +- synkronus-portal/src/main.tsx | 12 +- synkronus-portal/src/pages/Dashboard.css | 179 ++- synkronus-portal/src/pages/Dashboard.tsx | 1404 ++++++++++------- synkronus-portal/src/services/api.ts | 163 +- synkronus-portal/src/types/auth.ts | 25 +- 16 files changed, 1500 insertions(+), 811 deletions(-) diff --git a/.github/CICD.md b/.github/CICD.md index 7e56f59f1..841499ddb 100644 --- a/.github/CICD.md +++ b/.github/CICD.md @@ -15,7 +15,7 @@ The ODE monorepo uses GitHub Actions for continuous integration and deployment. #### Triggers - **Push to `main`**: Builds and publishes release images -- **Push to `develop`**: Builds and publishes pre-release images +- **Push to `dev`**: Builds and publishes pre-release images - **Push to feature branches**: Builds and publishes branch-specific images - **Pull Requests**: Builds but does not publish (validation only) - **Manual Dispatch**: Allows manual triggering with optional version tag @@ -37,7 +37,7 @@ Images are published to **GitHub Container Registry (GHCR)**: | Branch/Event | Tags Generated | Description | |--------------|----------------|-------------| | `main` | `latest`, `main-{sha}` | Latest stable release | -| `develop` | `develop`, `develop-{sha}` | Development pre-release | +| `dev` | `dev`, `dev-{sha}` | Development pre-release | | Feature branches | `{branch-name}`, `{branch-name}-{sha}` | Feature-specific builds | | Pull Requests | `pr-{number}` | PR validation builds (not pushed) | | Manual with version | `v{version}`, `v{major}.{minor}`, `latest` | Versioned release | @@ -76,7 +76,7 @@ docker pull ghcr.io/opendataensemble/synkronus:v1.0.0 ### Pull Development Build ```bash -docker pull ghcr.io/opendataensemble/synkronus:develop +docker pull ghcr.io/opendataensemble/synkronus:dev ``` ### Pull Feature Branch Build @@ -129,6 +129,155 @@ echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin 3. Select **synkronus** 4. View all published tags and their details +## Code Formatting & Linting + +All workflows enforce formatting and linting checks before builds. Ensure your code is properly formatted before pushing to avoid CI failures. + +### Go Projects (synkronus, synkronus-cli) + +#### Format Go Code + +Format all Go files in a project: + +```bash +# For synkronus +cd synkronus +go fmt ./... + +# Or using gofmt directly +gofmt -s -w . + +# For synkronus-cli +cd synkronus-cli +go fmt ./... +``` + +#### Check Go Formatting (without modifying files) + +```bash +# Check if files are formatted +gofmt -s -l . + +# View what would change +gofmt -s -d . +``` + +#### Run Linting + +```bash +# Install golangci-lint (if not already installed) +# macOS: brew install golangci-lint +# Linux: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.2 + +# Run linting for synkronus +cd synkronus +golangci-lint run + +# Run linting for synkronus-cli +cd synkronus-cli +golangci-lint run +``` + +### TypeScript/JavaScript Projects + +#### Synkronus Portal + +```bash +cd synkronus-portal + +# Format code +npm run format + +# Check formatting (no writes) +npm run format:check + +# Run linting +npm run lint +``` + +#### Formulus (React Native) + +```bash +cd formulus + +# Format code +npm run format + +# Check formatting (no writes) +npm run format:check + +# Run linting +npm run lint + +# Run linting with auto-fix +npm run lint:fix +``` + +#### Formulus Formplayer (React Web) + +```bash +cd formulus-formplayer + +# Format code +npm run format + +# Check formatting (no writes) +npm run format:check + +# Run linting +npm run lint + +# Run linting with auto-fix +npm run lint:fix +``` + +### What CI Enforces + +#### Go Projects + +- **gofmt**: All Go files must be formatted. CI will fail if any files are unformatted. +- **golangci-lint**: Runs comprehensive linting checks using the `.golangci.yml` configuration. + +#### TypeScript/JavaScript Projects + +- **Prettier**: All source files must be formatted according to Prettier rules. +- **ESLint**: Code must pass ESLint checks with no errors. + +### Pre-commit Checklist + +Before pushing your code, run these commands: + +```bash +# For Go projects +cd synkronus # or synkronus-cli +go fmt ./... +golangci-lint run + +# For TypeScript/JavaScript projects +cd synkronus-portal # or formulus, or formulus-formplayer +npm run format:check +npm run lint +``` + +### Auto-formatting with Git Hooks (Optional) + +You can set up a pre-commit hook to automatically format code: + +```bash +# Create .git/hooks/pre-commit +cat > .git/hooks/pre-commit << 'EOF' +#!/bin/bash +# Format Go files +find . -name "*.go" -not -path "./vendor/*" -exec gofmt -s -w {} \; +# Format TypeScript/JavaScript files +cd synkronus-portal && npm run format +cd ../formulus && npm run format +cd ../formulus-formplayer && npm run format +EOF + +chmod +x .git/hooks/pre-commit +``` + ## Troubleshooting ### Build Fails on Push @@ -163,7 +312,7 @@ echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin ### For Deployments 1. **Pin versions in production**: Use specific version tags, not `latest` -2. **Test pre-releases**: Use `develop` tag for staging environments +2. **Test pre-releases**: Use `dev` tag for staging environments 3. **Monitor image sizes**: Keep images lean for faster deployments 4. **Use health checks**: Always configure health checks in deployments @@ -175,7 +324,6 @@ Potential improvements to the CI/CD pipeline: - [ ] Implement security scanning (Trivy, Snyk) - [ ] Add deployment to staging environment - [ ] Create release notes automation -- [ ] Add Slack/Discord notifications - [ ] Implement rollback mechanisms - [ ] Add performance benchmarking diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc5818051..48da1dc04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -169,28 +169,27 @@ jobs: - name: Download dependencies run: go mod download - - name: Run gofmt (warning) + - name: Run gofmt run: | UNFORMATTED_FILES=$(git ls-files '*.go' | xargs gofmt -s -l) if [ -n "$UNFORMATTED_FILES" ]; then - echo "⚠️ Code is not formatted. Consider running 'go fmt ./...' or 'gofmt -s -w .'" + echo "❌ Code is not formatted. Please run 'go fmt ./...' or 'gofmt -s -w .'" echo "Unformatted files:" echo "$UNFORMATTED_FILES" echo "Diffs:" echo "$UNFORMATTED_FILES" | xargs gofmt -s -d + exit 1 else echo "✅ All Go files are formatted." fi - - name: Run golangci-lint (if available) - run: | - if command -v golangci-lint &> /dev/null; then - golangci-lint run - else - echo "golangci-lint not installed, skipping..." - fi - continue-on-error: true + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + working-directory: ./synkronus + args: --timeout=5m - name: Run tests run: go test -v -race -coverprofile=coverage.out ./... @@ -226,20 +225,28 @@ jobs: - name: Download dependencies run: go mod download - - name: Run gofmt (warning) + - name: Run gofmt run: | UNFORMATTED_FILES=$(git ls-files '*.go' | xargs gofmt -s -l) if [ -n "$UNFORMATTED_FILES" ]; then - echo "⚠️ Code is not formatted. Consider running 'go fmt ./...' or 'gofmt -s -w .'" + echo "❌ Code is not formatted. Please run 'go fmt ./...' or 'gofmt -s -w .'" echo "Unformatted files:" echo "$UNFORMATTED_FILES" echo "Diffs:" echo "$UNFORMATTED_FILES" | xargs gofmt -s -d + exit 1 else echo "✅ All Go files are formatted." fi + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + working-directory: ./synkronus-cli + args: --timeout=5m + - name: Run tests run: go test -v -race -coverprofile=coverage.out ./... diff --git a/.github/workflows/synkronus-cli.yml b/.github/workflows/synkronus-cli.yml index fc1e38e35..3cf471961 100644 --- a/.github/workflows/synkronus-cli.yml +++ b/.github/workflows/synkronus-cli.yml @@ -16,9 +16,62 @@ on: types: [published] jobs: + format-check: + name: Check Go formatting + if: github.event_name != 'release' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24.x' + cache-dependency-path: synkronus-cli/go.sum + + - name: Check formatting + working-directory: synkronus-cli + run: | + UNFORMATTED_FILES=$(git ls-files '*.go' | xargs gofmt -s -l) + + if [ -n "$UNFORMATTED_FILES" ]; then + echo "❌ Code is not formatted. Please run 'go fmt ./...' or 'gofmt -s -w .'" + echo "Unformatted files:" + echo "$UNFORMATTED_FILES" + echo "" + echo "Diffs:" + echo "$UNFORMATTED_FILES" | xargs gofmt -s -d + exit 1 + else + echo "✅ All Go files are formatted." + fi + + lint: + name: Lint with golangci-lint + if: github.event_name != 'release' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24.x' + cache-dependency-path: synkronus-cli/go.sum + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + working-directory: synkronus-cli + args: --timeout=5m + build-cli: name: Build synkronus-cli (CI) if: github.event_name != 'release' + needs: [format-check, lint] runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/synkronus-docker.yml b/.github/workflows/synkronus-docker.yml index c4dec91b3..a7e698b13 100644 --- a/.github/workflows/synkronus-docker.yml +++ b/.github/workflows/synkronus-docker.yml @@ -38,6 +38,33 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + cache-dependency-path: synkronus/go.sum + + - name: Check Go formatting + working-directory: ./synkronus + run: | + UNFORMATTED_FILES=$(git ls-files '*.go' | xargs gofmt -s -l) + + if [ -n "$UNFORMATTED_FILES" ]; then + echo "❌ Code is not formatted. Please run 'go fmt ./...' or 'gofmt -s -w .'" + echo "Unformatted files:" + echo "$UNFORMATTED_FILES" + exit 1 + else + echo "✅ All Go files are formatted." + fi + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + working-directory: ./synkronus + args: --timeout=5m + - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: diff --git a/.github/workflows/synkronus-portal-docker.yml b/.github/workflows/synkronus-portal-docker.yml index 0f820c4c3..5096adf59 100644 --- a/.github/workflows/synkronus-portal-docker.yml +++ b/.github/workflows/synkronus-portal-docker.yml @@ -38,6 +38,29 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: | + synkronus-portal/package-lock.json + packages/tokens/package-lock.json + + - name: Install dependencies + run: | + cd packages/tokens && npm ci + cd ../components && npm install || true + cd ../../synkronus-portal && npm ci + + - name: Run ESLint + working-directory: synkronus-portal + run: npm run lint + + - name: Check Prettier formatting + working-directory: synkronus-portal + run: npm run format:check + - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: diff --git a/synkronus-portal/package.json b/synkronus-portal/package.json index c6f270c11..c20849234 100644 --- a/synkronus-portal/package.json +++ b/synkronus-portal/package.json @@ -7,6 +7,8 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", + "format": "prettier \"src/**/*.{ts,tsx,json,css,md}\" --write", + "format:check": "prettier \"src/**/*.{ts,tsx,json,css,md}\" --check", "preview": "vite preview" }, "dependencies": { @@ -29,6 +31,7 @@ "globals": "^16.5.0", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", + "prettier": "^3.0.0", "vite": "^7.2.4" } } diff --git a/synkronus-portal/src/App.tsx b/synkronus-portal/src/App.tsx index 21ff768d8..eca85a905 100644 --- a/synkronus-portal/src/App.tsx +++ b/synkronus-portal/src/App.tsx @@ -1,7 +1,7 @@ -import { AuthProvider } from './contexts/AuthContext' -import { ProtectedRoute } from './components/ProtectedRoute' -import { Dashboard } from './pages/Dashboard' -import './App.css' +import { AuthProvider } from './contexts/AuthContext'; +import { ProtectedRoute } from './components/ProtectedRoute'; +import { Dashboard } from './pages/Dashboard'; +import './App.css'; function App() { return ( @@ -10,7 +10,7 @@ function App() { - ) + ); } -export default App +export default App; diff --git a/synkronus-portal/src/components/Login.css b/synkronus-portal/src/components/Login.css index 02b53fa12..c35d4a577 100644 --- a/synkronus-portal/src/components/Login.css +++ b/synkronus-portal/src/components/Login.css @@ -19,20 +19,25 @@ left: -50%; width: 200%; height: 200%; - background: + background: radial-gradient(circle at 25% 35%, var(--color-brand-primary-500, #4f7f4e) 0%, transparent 35%), - radial-gradient(circle at 75% 65%, var(--color-brand-secondary-500, #e9b85b) 0%, transparent 35%); + radial-gradient( + circle at 75% 65%, + var(--color-brand-secondary-500, #e9b85b) 0%, + transparent 35% + ); opacity: var(--opacity-6, 0.06); animation: backgroundPulse 25s ease-in-out infinite; pointer-events: none; } @keyframes backgroundPulse { - 0%, 100% { + 0%, + 100% { transform: translate(0, 0) scale(1); opacity: var(--opacity-6, 0.06); } - 50% { + 50% { transform: translate(3%, 3%) scale(1.05); opacity: var(--opacity-8, 0.08); } @@ -46,13 +51,14 @@ padding: var(--spacing-8, 32px); width: 100%; max-width: 420px; - box-shadow: + box-shadow: 0 8px 32px rgba(0, 0, 0, var(--opacity-40, 0.4)), inset 0 1px 0 rgba(255, 255, 255, var(--opacity-5, 0.05)); border: var(--border-width-thin, 1px) solid rgba(79, 127, 78, var(--opacity-20, 0.2)); position: relative; z-index: 1; - animation: slideUpFade var(--duration-slower, 500ms) var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1)); + animation: slideUpFade var(--duration-slower, 500ms) + var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1)); } @keyframes slideUpFade { @@ -79,7 +85,8 @@ height: var(--logo-sm, 80px); object-fit: contain; filter: drop-shadow(0 4px 12px rgba(79, 127, 78, var(--opacity-30, 0.3))); - transition: filter var(--duration-normal, 250ms) var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1)); + transition: filter var(--duration-normal, 250ms) + var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1)); } .login-logo:hover { @@ -195,7 +202,8 @@ font-weight: var(--font-weight-medium, 500); font-size: var(--font-size-sm, 14px); backdrop-filter: blur(10px); - animation: slideDown var(--duration-normal, 250ms) var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1)); + animation: slideDown var(--duration-normal, 250ms) + var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1)); } @keyframes slideDown { diff --git a/synkronus-portal/src/components/Login.tsx b/synkronus-portal/src/components/Login.tsx index 21483aeaf..f0f5cfaa5 100644 --- a/synkronus-portal/src/components/Login.tsx +++ b/synkronus-portal/src/components/Login.tsx @@ -1,33 +1,33 @@ -import { useState } from 'react' -import type { FormEvent } from 'react' -import { useAuth } from '../contexts/AuthContext' -import { Button, Input } from "@ode/components/react-web"; +import { useState } from 'react'; +import type { FormEvent } from 'react'; +import { useAuth } from '../contexts/AuthContext'; +import { Button, Input } from '@ode/components/react-web'; -import odeLogo from '../assets/ode_logo.png' -import './Login.css' +import odeLogo from '../assets/ode_logo.png'; +import './Login.css'; export function Login() { - const [username, setUsername] = useState('') - const [password, setPassword] = useState('') - const [error, setError] = useState(null) - const [loading, setLoading] = useState(false) - const { login } = useAuth() + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + const { login } = useAuth(); const handleSubmit = async (e?: FormEvent) => { if (e) { - e.preventDefault() + e.preventDefault(); } - setError(null) - setLoading(true) + setError(null); + setLoading(true); try { - await login({ username, password }) + await login({ username, password }); } catch (err) { - setError(err instanceof Error ? err.message : 'Login failed') + setError(err instanceof Error ? err.message : 'Login failed'); } finally { - setLoading(false) + setLoading(false); } - } + }; return (
@@ -37,9 +37,9 @@ export function Login() {

Synkronus Portal

Sign In

- + {error &&
{error}
} - +
- +
- +
+ - +