diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f55afd4..c98bf42 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,64 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: +# Dependabot configuration for Branch Newspaper # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - - package-ecosystem: "" # See documentation for possible values - directory: "/" # Location of package manifests + # Elixir/Mix dependencies + - package-ecosystem: "mix" + directory: "/" schedule: - interval: "daily" + interval: "weekly" + day: "monday" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "elixir" + commit-message: + prefix: "deps(mix):" + groups: + phoenix: + patterns: + - "phoenix*" + - "plug*" + ecto: + patterns: + - "ecto*" + dev-deps: + patterns: + - "credo" + - "dialyxir" + - "sobelow" + - "ex_doc" + update-types: + - "minor" + - "patch" + + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "ci(actions):" + + # npm dependencies (if assets use npm) + - package-ecosystem: "npm" + directory: "/assets" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "javascript" + commit-message: + prefix: "deps(npm):" + ignore: + # Tailwind and esbuild are managed by Mix + - dependency-name: "tailwindcss" + - dependency-name: "esbuild" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0efbfb7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,167 @@ +name: CI + +on: + push: + branches: [main, master, develop] + tags: ['v*'] + pull_request: + branches: [main, master] + workflow_dispatch: + +env: + MIX_ENV: test + ELIXIR_VERSION: '1.15.7' + OTP_VERSION: '26.2' + +jobs: + lint: + name: Lint & Format + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.ELIXIR_VERSION }} + otp-version: ${{ env.OTP_VERSION }} + + - name: Cache deps + uses: actions/cache@v4 + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}- + + - name: Install dependencies + run: | + mix local.hex --force + mix local.rebar --force + mix deps.get + + - name: Run lint checks + run: ./ci-scripts/lint.sh + + test: + name: Test (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + # Primary supported version + - elixir: '1.15.7' + otp: '26.2' + # Latest versions + - elixir: '1.16.0' + otp: '26.2' + # Minimum supported version + - elixir: '1.15.0' + otp: '25.3' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + + - name: Cache deps + uses: actions/cache@v4 + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}- + + - name: Install dependencies + run: | + mix local.hex --force + mix local.rebar --force + mix deps.get + + - name: Setup test environment + run: ./ci-scripts/setup.sh + + - name: Run tests + run: ./ci-scripts/test.sh + + build: + name: Build Release + runs-on: ubuntu-latest + needs: [lint, test] + if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') + env: + MIX_ENV: prod + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.ELIXIR_VERSION }} + otp-version: ${{ env.OTP_VERSION }} + + - name: Cache deps + uses: actions/cache@v4 + with: + path: | + deps + _build + key: ${{ runner.os }}-mix-prod-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-prod-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}- + + - name: Build release + run: ./ci-scripts/build.sh + + - name: Upload release artifact + uses: actions/upload-artifact@v4 + with: + name: branch-newspaper-release + path: _build/prod/rel/branch_newspaper/ + if-no-files-found: warn + + mirror-to-gitlab: + name: Mirror to GitLab + runs-on: ubuntu-latest + needs: [test] + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup SSH for GitLab + env: + GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }} + run: | + mkdir -p ~/.ssh + echo "$GITLAB_SSH_KEY" > ~/.ssh/gitlab_key + chmod 600 ~/.ssh/gitlab_key + ssh-keyscan gitlab.com >> ~/.ssh/known_hosts + cat >> ~/.ssh/config << EOF + Host gitlab.com + HostName gitlab.com + User git + IdentityFile ~/.ssh/gitlab_key + StrictHostKeyChecking no + EOF + + - name: Push to GitLab + env: + GITLAB_URL: git@gitlab.com:maa-framework/3-applications/branch-newspaper.git + run: ./ci-scripts/mirror-push.sh diff --git a/.github/workflows/mirror-sync.yml b/.github/workflows/mirror-sync.yml new file mode 100644 index 0000000..ac974fb --- /dev/null +++ b/.github/workflows/mirror-sync.yml @@ -0,0 +1,152 @@ +name: Mirror Sync + +on: + push: + branches: ['**'] + tags: ['**'] + delete: + # Trigger on branch/tag deletion to sync deletions + workflow_dispatch: + inputs: + full_sync: + description: 'Perform full mirror sync (all branches and tags)' + required: false + default: 'false' + type: boolean + +jobs: + mirror-push: + name: Push to GitLab Mirror + runs-on: ubuntu-latest + # Only run on the main repo, not forks + if: github.repository == 'hyperpolymath/branch-newspaper' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup SSH for GitLab + env: + GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }} + run: | + if [ -z "$GITLAB_SSH_KEY" ]; then + echo "ERROR: GITLAB_SSH_KEY secret is not set" + echo "Please add a deploy key to GitLab and store the private key as a GitHub secret" + exit 1 + fi + + mkdir -p ~/.ssh + echo "$GITLAB_SSH_KEY" > ~/.ssh/gitlab_key + chmod 600 ~/.ssh/gitlab_key + ssh-keyscan gitlab.com >> ~/.ssh/known_hosts + + cat >> ~/.ssh/config << EOF + Host gitlab.com + HostName gitlab.com + User git + IdentityFile ~/.ssh/gitlab_key + StrictHostKeyChecking no + EOF + + - name: Configure Git + run: | + git config --global user.email "github-actions@github.com" + git config --global user.name "GitHub Actions" + + - name: Add GitLab remote + run: | + git remote add gitlab git@gitlab.com:maa-framework/3-applications/branch-newspaper.git || true + git remote set-url gitlab git@gitlab.com:maa-framework/3-applications/branch-newspaper.git + + - name: Push current ref to GitLab + if: github.event_name == 'push' + run: | + REF="${GITHUB_REF}" + echo "Pushing ref: $REF" + + # Retry logic with exponential backoff + MAX_RETRIES=4 + RETRY_DELAY=2 + + for i in $(seq 1 $MAX_RETRIES); do + if git push gitlab "$REF" --force-with-lease; then + echo "Successfully pushed $REF" + exit 0 + fi + if [ $i -lt $MAX_RETRIES ]; then + echo "Push failed, retrying in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY + RETRY_DELAY=$((RETRY_DELAY * 2)) + fi + done + + echo "Failed to push after $MAX_RETRIES attempts" + exit 1 + + - name: Full mirror sync + if: github.event.inputs.full_sync == 'true' || github.event_name == 'workflow_dispatch' + run: | + echo "Performing full mirror sync..." + + # Push all branches + git push gitlab --all --force-with-lease + + # Push all tags + git push gitlab --tags --force + + echo "Full sync complete!" + + - name: Handle deletion + if: github.event_name == 'delete' + run: | + REF_TYPE="${{ github.event.ref_type }}" + REF_NAME="${{ github.event.ref }}" + + echo "Handling deletion of $REF_TYPE: $REF_NAME" + + if [ "$REF_TYPE" = "branch" ]; then + git push gitlab --delete "$REF_NAME" || echo "Branch may already be deleted" + elif [ "$REF_TYPE" = "tag" ]; then + git push gitlab --delete "refs/tags/$REF_NAME" || echo "Tag may already be deleted" + fi + + verify-mirror: + name: Verify Mirror Sync + runs-on: ubuntu-latest + needs: mirror-push + if: always() && needs.mirror-push.result == 'success' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup SSH for GitLab + env: + GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }} + run: | + mkdir -p ~/.ssh + echo "$GITLAB_SSH_KEY" > ~/.ssh/gitlab_key + chmod 600 ~/.ssh/gitlab_key + ssh-keyscan gitlab.com >> ~/.ssh/known_hosts + + cat >> ~/.ssh/config << EOF + Host gitlab.com + HostName gitlab.com + User git + IdentityFile ~/.ssh/gitlab_key + StrictHostKeyChecking no + EOF + + - name: Add GitLab remote + run: | + git remote add gitlab git@gitlab.com:maa-framework/3-applications/branch-newspaper.git || true + + - name: Verify mirror sync + env: + SOURCE_REMOTE: origin + DEST_REMOTE: gitlab + run: ./ci-scripts/verify-mirror.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..20a8bd4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,151 @@ +# GitLab CI Configuration for Branch Newspaper +# This pipeline calls shared scripts in ci-scripts/ for consistency with GitHub Actions + +stages: + - lint + - test + - build + - deploy + +variables: + MIX_ENV: test + ELIXIR_VERSION: "1.15.7" + OTP_VERSION: "26.2" + +# Cache dependencies between jobs +.cache_template: &cache_template + cache: + key: + files: + - mix.lock + paths: + - deps/ + - _build/ + +# Base template for Elixir jobs +.elixir_template: &elixir_template + image: elixir:${ELIXIR_VERSION} + before_script: + - mix local.hex --force + - mix local.rebar --force + - mix deps.get + +# Lint job +lint: + <<: *elixir_template + <<: *cache_template + stage: lint + script: + - chmod +x ./ci-scripts/*.sh + - ./ci-scripts/lint.sh + allow_failure: false + +# Test job - Primary version +test: + <<: *elixir_template + <<: *cache_template + stage: test + script: + - chmod +x ./ci-scripts/*.sh + - ./ci-scripts/setup.sh + - ./ci-scripts/test.sh + artifacts: + when: always + reports: + junit: _build/test/lib/branch_newspaper/test-junit-report.xml + expire_in: 1 week + +# Test job - Matrix for additional Elixir versions +test:elixir-1.16: + <<: *elixir_template + <<: *cache_template + stage: test + image: elixir:1.16.0 + script: + - chmod +x ./ci-scripts/*.sh + - ./ci-scripts/setup.sh + - ./ci-scripts/test.sh + allow_failure: true + +test:elixir-1.15-minimum: + <<: *elixir_template + <<: *cache_template + stage: test + image: elixir:1.15.0 + script: + - chmod +x ./ci-scripts/*.sh + - ./ci-scripts/setup.sh + - ./ci-scripts/test.sh + allow_failure: true + +# Build release +build: + <<: *elixir_template + stage: build + variables: + MIX_ENV: prod + cache: + key: + files: + - mix.lock + prefix: prod + paths: + - deps/ + - _build/ + script: + - chmod +x ./ci-scripts/*.sh + - ./ci-scripts/build.sh + artifacts: + paths: + - _build/prod/rel/branch_newspaper/ + expire_in: 1 week + only: + - main + - master + - tags + +# Deploy to staging (manual trigger) +deploy:staging: + stage: deploy + image: alpine:latest + before_script: + - apk add --no-cache openssh-client rsync + script: + - echo "Deploying to staging environment..." + - echo "This is a placeholder - configure actual deployment" + environment: + name: staging + url: https://staging.branch-newspaper.example.com + when: manual + only: + - main + - develop + +# Deploy to production (manual trigger, only on tags) +deploy:production: + stage: deploy + image: alpine:latest + before_script: + - apk add --no-cache openssh-client rsync + script: + - echo "Deploying to production environment..." + - echo "This is a placeholder - configure actual deployment" + environment: + name: production + url: https://branch-newspaper.example.com + when: manual + only: + - tags + +# Mirror verification (runs after pushes from GitHub) +verify-mirror: + stage: test + image: alpine/git:latest + script: + - chmod +x ./ci-scripts/*.sh + - apk add --no-cache bash + - ./ci-scripts/verify-mirror.sh || true + only: + - main + - master + allow_failure: true diff --git a/README.md b/README.md index 6aacdd6..8715746 100644 --- a/README.md +++ b/README.md @@ -1 +1,168 @@ -# branch-newspaper +# Branch Newspaper + +[![CI](https://github.com/hyperpolymath/branch-newspaper/actions/workflows/ci.yml/badge.svg)](https://github.com/hyperpolymath/branch-newspaper/actions/workflows/ci.yml) +[![Mirror Sync](https://github.com/hyperpolymath/branch-newspaper/actions/workflows/mirror-sync.yml/badge.svg)](https://github.com/hyperpolymath/branch-newspaper/actions/workflows/mirror-sync.yml) +[![CodeQL](https://github.com/hyperpolymath/branch-newspaper/actions/workflows/codeql.yml/badge.svg)](https://github.com/hyperpolymath/branch-newspaper/actions/workflows/codeql.yml) + +A Phoenix LiveView application for managing and distributing meeting minutes with IPFS integration for decentralized content storage. + +## Features + +- **Meeting Minutes Management** - Create, edit, and organize meeting minutes +- **IPFS Integration** - Store content on IPFS for decentralized, immutable storage +- **Real-time UI** - Phoenix LiveView for instant updates without page reloads +- **Tag Organization** - Categorize minutes with tags for easy discovery + +## Tech Stack + +| Component | Technology | +|-----------|------------| +| Language | Elixir ~> 1.15 | +| Framework | Phoenix 1.8.1 | +| Real-time | Phoenix LiveView 1.1.0 | +| Database | SQLite3 (dev) / PostgreSQL (prod) | +| Storage | IPFS (Kubo) | +| CSS | Tailwind CSS v4 | + +## Prerequisites + +- Elixir 1.15+ and Erlang/OTP 25+ +- Node.js 18+ (for asset compilation) +- IPFS node (Kubo) running locally or accessible + +## Getting Started + +### Development Setup + +1. **Clone the repository** + ```bash + git clone https://github.com/hyperpolymath/branch-newspaper.git + cd branch-newspaper + ``` + +2. **Install dependencies and setup database** + ```bash + mix setup + ``` + +3. **Start the IPFS daemon** (in a separate terminal) + ```bash + ipfs daemon + ``` + +4. **Start the Phoenix server** + ```bash + mix phx.server + # Or with interactive Elixir shell: + iex -S mix phx.server + ``` + +5. **Visit the application** + + Open [http://localhost:4000](http://localhost:4000) in your browser. + +### Running Tests + +```bash +# Run all tests +mix test + +# Run with coverage +mix test --cover + +# Run the precommit checks (format, compile warnings, tests) +mix precommit +``` + +### Code Quality + +```bash +# Check formatting +mix format --check-formatted + +# Run all lint checks +./ci-scripts/lint.sh +``` + +## Configuration + +### Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `SECRET_KEY_BASE` | Phoenix secret key | (generated) | +| `DATABASE_URL` | Database connection string | SQLite file | +| `PHX_HOST` | Production hostname | localhost | +| `PORT` | HTTP port | 4000 | +| `IPFS_API_URL` | IPFS API endpoint | http://localhost:5001/api/v0 | + +See [SECRETS.md](SECRETS.md) for complete secrets documentation. + +## Project Structure + +``` +branch-newspaper/ +├── assets/ # Frontend assets (JS, CSS) +├── ci-scripts/ # Shared CI/CD scripts +├── config/ # Application configuration +├── lib/ +│ ├── branch_newspaper/ # Business logic +│ │ ├── content/ # Content domain +│ │ └── services/ # External services (IPFS) +│ └── branch_newspaper_web/ # Web interface +│ ├── components/ # UI components +│ ├── controllers/ # HTTP controllers +│ └── live/ # LiveView modules +├── priv/ # Private application files +└── test/ # Test files +``` + +## Documentation + +- [ROADMAP.adoc](ROADMAP.adoc) - Development roadmap and MVP plan +- [TODO.md](TODO.md) - Task backlog and improvements +- [SECRETS.md](SECRETS.md) - Secrets and configuration guide +- [AGENTS.md](AGENTS.md) - AI coding guidelines + +## CI/CD + +This project uses unified CI/CD that runs on both GitHub Actions and GitLab CI: + +- **Lint** - Code formatting and static analysis +- **Test** - ExUnit tests across multiple Elixir versions +- **Build** - Release compilation for deployment +- **Mirror** - Automatic sync between GitHub and GitLab + +### Test Matrix + +| Elixir | OTP | Status | +|--------|-----|--------| +| 1.15.0 | 25.3 | Minimum supported | +| 1.15.7 | 26.2 | Primary | +| 1.16.0 | 26.2 | Latest | + +## Contributing + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Run the precommit checks (`mix precommit`) +4. Commit your changes (`git commit -m 'Add amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a Pull Request + +## Repository Mirrors + +- **Primary (GitHub)**: https://github.com/hyperpolymath/branch-newspaper +- **Mirror (GitLab)**: https://gitlab.com/maa-framework/3-applications/branch-newspaper + +Changes pushed to GitHub are automatically mirrored to GitLab. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- [Phoenix Framework](https://phoenixframework.org/) +- [IPFS](https://ipfs.tech/) +- Part of the [MAA Framework](https://gitlab.com/maa-framework) project diff --git a/ROADMAP.adoc b/ROADMAP.adoc new file mode 100644 index 0000000..956e113 --- /dev/null +++ b/ROADMAP.adoc @@ -0,0 +1,361 @@ += Branch Newspaper Roadmap +:toc: left +:toclevels: 3 +:icons: font +:source-highlighter: highlight.js + +== Overview + +This document outlines the development roadmap for Branch Newspaper, from the current state to MVP v1.0 deployment and beyond. + +Branch Newspaper is an Elixir/Phoenix application for managing and distributing meeting minutes with IPFS integration for decentralized content storage. + +== Current State + +=== Completed Infrastructure +* [x] Phoenix 1.8.1 application scaffold +* [x] SQLite3 database with Ecto +* [x] Phoenix LiveView for real-time UI +* [x] IPFS client integration (Kubo) +* [x] Basic CRUD for Minutes +* [x] CI/CD pipelines (GitHub Actions + GitLab CI) +* [x] Repository mirroring setup +* [x] Dependabot configuration +* [x] CodeQL security scanning + +=== Technical Stack +|=== +| Component | Technology | Version + +| Language | Elixir | ~> 1.15 +| Framework | Phoenix | ~> 1.8.1 +| Real-time | Phoenix LiveView | ~> 1.1.0 +| Database | SQLite3/Ecto | ~> 3.12 +| Storage | IPFS (Kubo) | v0.24.0 +| CSS | Tailwind CSS | v4.1 +| Build | Mix, esbuild | Latest +|=== + +== Route to MVP v1.0 + +=== Phase 1: Foundation Hardening (Week 1-2) + +==== Security & Quality +* [ ] Remove sensitive files from repository +** SQLite database files +** Binary tarballs (Kubo) +* [ ] Add security analysis tools +** Sobelow for Phoenix security +** Credo for code quality +* [ ] Implement proper secret management +** Externalize all configuration +** Document required environment variables + +==== Testing +* [ ] Add missing test coverage +** IPFS client service tests +** Integration tests for content flow +* [ ] Set up coverage reporting (ExCoveralls) +* [ ] Achieve 80%+ code coverage + +==== Documentation +* [ ] Comprehensive README.md +** Setup instructions +** Development workflow +** Configuration guide +* [ ] API documentation +* [ ] Architecture overview + +=== Phase 2: Feature Completion (Week 3-4) + +==== Core Features +* [ ] Complete Minutes management +** List with pagination +** Search/filter functionality +** Tag-based organization +* [ ] IPFS content lifecycle +** Upload with verification +** Pinning management +** Content retrieval with caching + +==== User Experience +* [ ] Loading states and feedback +* [ ] Error handling and messaging +* [ ] Responsive design polish +* [ ] Accessibility audit (WCAG 2.1 AA) + +=== Phase 3: Production Readiness (Week 5-6) + +==== Infrastructure +* [ ] Docker containerization +[source,dockerfile] +---- +# Multi-stage Dockerfile +FROM elixir:1.15-alpine AS builder +# ... build steps ... + +FROM alpine:3.19 AS runner +# ... runtime configuration ... +---- + +* [ ] Health check endpoints +** `/health/live` - liveness probe +** `/health/ready` - readiness probe +* [ ] Structured logging (JSON format) +* [ ] Prometheus metrics export + +==== Deployment +* [ ] Staging environment setup +** Fly.io or similar PaaS +** Separate IPFS node +* [ ] Database migration strategy +** PostgreSQL for production +** Migration scripts +* [ ] SSL/TLS configuration +* [ ] CDN for static assets + +=== Phase 4: MVP v1.0 Launch (Week 7-8) + +==== Pre-Launch Checklist +* [ ] Security audit completed +* [ ] Performance testing passed +* [ ] Backup and recovery tested +* [ ] Monitoring and alerting configured +* [ ] Documentation reviewed + +==== Launch Tasks +* [ ] Production environment provisioning +* [ ] DNS configuration +* [ ] SSL certificate setup +* [ ] Initial data migration +* [ ] Launch announcement + +==== Post-Launch +* [ ] Monitor error rates +* [ ] Track performance metrics +* [ ] Gather user feedback +* [ ] Hot-fix process documented + +== MVP v1.0 Features + +=== Included in MVP + +[cols="1,2,1"] +|=== +| Feature | Description | Priority + +| Minutes CRUD +| Create, read, update, delete meeting minutes +| P0 + +| IPFS Storage +| Store minutes content on IPFS +| P0 + +| LiveView UI +| Real-time updates without page reloads +| P0 + +| Basic Search +| Search minutes by title and content +| P1 + +| Tags +| Organize minutes with tags +| P1 + +| Date Filtering +| Filter minutes by meeting date +| P2 +|=== + +=== Deferred to v1.1+ + +* User authentication and authorization +* Team/organization support +* Rich text editor for content +* File attachments +* Email notifications +* API for external integrations +* Mobile app + +== Deployment Architecture + +=== MVP v1.0 Architecture + +[source] +---- +┌─────────────────────────────────────────────────────────┐ +│ Load Balancer │ +│ (Fly.io/Cloudflare) │ +└─────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Phoenix Application │ +│ ┌─────────────────────────────┐ │ +│ │ Phoenix LiveView │ │ +│ │ - Real-time UI │ │ +│ │ - WebSocket connections │ │ +│ └─────────────────────────────┘ │ +│ ┌─────────────────────────────┐ │ +│ │ Business Logic │ │ +│ │ - Minutes management │ │ +│ │ - IPFS integration │ │ +│ └─────────────────────────────┘ │ +└─────────────────────┬───────────────────────────────────┘ + │ + ┌───────────┴───────────┐ + ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ +│ PostgreSQL │ │ IPFS Node │ +│ (Fly Postgres)│ │ (Kubo) │ +└─────────────────┘ └─────────────────┘ +---- + +=== Recommended Hosting Options + +[cols="1,2,2,1"] +|=== +| Provider | Pros | Cons | Cost + +| Fly.io +| Easy deployment, built-in PostgreSQL, global edge +| Learning curve +| ~$10-30/mo + +| Railway +| Simple setup, managed databases +| Less control +| ~$5-20/mo + +| Render +| Free tier, managed PostgreSQL +| Cold starts on free tier +| $0-25/mo + +| Self-hosted (VPS) +| Full control, predictable costs +| More maintenance +| ~$5-20/mo +|=== + +== Risk Mitigation + +=== Technical Risks + +[cols="2,1,2,2"] +|=== +| Risk | Impact | Mitigation | Status + +| IPFS node instability +| High +| Implement retry logic, consider pinning service backup +| Planned + +| Database corruption +| Critical +| Automated backups, point-in-time recovery +| Not started + +| Security vulnerability +| Critical +| Regular dependency updates, security scanning +| In progress +|=== + +=== Process Risks + +[cols="2,1,2"] +|=== +| Risk | Impact | Mitigation + +| Scope creep +| Medium +| Strict MVP feature list, defer nice-to-haves + +| Technical debt +| Medium +| Code reviews, regular refactoring sprints + +| Knowledge silos +| Medium +| Documentation, code comments +|=== + +== Success Metrics + +=== MVP Launch Criteria + +* All P0 features implemented and tested +* 80%+ test coverage +* No critical/high security issues +* Documentation complete +* Staging environment validated +* Monitoring and alerting operational + +=== Post-Launch KPIs + +[cols="1,2,1"] +|=== +| Metric | Target | Measurement + +| Uptime +| 99.5% +| Monitoring alerts + +| Response Time (p95) +| < 200ms +| APM metrics + +| Error Rate +| < 1% +| Error tracking + +| IPFS Success Rate +| > 99% +| Custom metrics +|=== + +== Future Roadmap (Post-MVP) + +=== v1.1 - User Management +* User authentication (OAuth2/OIDC) +* Role-based access control +* Audit logging + +=== v1.2 - Collaboration +* Real-time collaborative editing +* Comments and annotations +* Email notifications + +=== v1.3 - Integration +* REST API +* Webhooks +* Export formats (PDF, DOCX) + +=== v2.0 - Enterprise +* Multi-tenancy +* Custom branding +* Advanced analytics +* SLA guarantees + +== Resources + +=== Documentation +* link:README.md[README] - Getting started +* link:TODO.md[TODO] - Task backlog +* link:SECRETS.md[SECRETS] - Configuration secrets +* link:AGENTS.md[AGENTS] - AI coding guidelines + +=== External Links +* https://hexdocs.pm/phoenix/overview.html[Phoenix Framework Documentation] +* https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html[Phoenix LiveView Guide] +* https://docs.ipfs.tech/[IPFS Documentation] +* https://www.erlang.org/doc/[Erlang/OTP Documentation] + +--- + +_Document Version: 1.0.0_ +_Last Updated: 2025-12-08_ +_Status: Active Development_ diff --git a/SECRETS.md b/SECRETS.md new file mode 100644 index 0000000..6221182 --- /dev/null +++ b/SECRETS.md @@ -0,0 +1,161 @@ +# Secrets Configuration + +This document lists all secrets required for the Branch Newspaper application and CI/CD pipelines. + +## GitHub Secrets + +Configure these in: **Settings → Secrets and variables → Actions** + +### Required Secrets + +| Secret Name | Description | Used By | How to Obtain | +|-------------|-------------|---------|---------------| +| `GITLAB_SSH_KEY` | SSH private key for GitLab push access | `mirror-sync.yml`, `ci.yml` | See [GitLab SSH Key Setup](#gitlab-ssh-key-setup) | + +### Optional Secrets (for enhanced features) + +| Secret Name | Description | Used By | How to Obtain | +|-------------|-------------|---------|---------------| +| `CODECOV_TOKEN` | Token for uploading coverage reports | `ci.yml` (if coverage enabled) | [codecov.io](https://codecov.io) → Settings | +| `DOCKERHUB_USERNAME` | Docker Hub username for image publishing | Deployment workflows | [hub.docker.com](https://hub.docker.com) account | +| `DOCKERHUB_TOKEN` | Docker Hub access token | Deployment workflows | Docker Hub → Account Settings → Security | + +--- + +## GitLab CI/CD Variables + +Configure these in: **Settings → CI/CD → Variables** + +### Required Variables + +| Variable Name | Description | Protected | Masked | +|---------------|-------------|-----------|--------| +| `GITHUB_MIRROR_TOKEN` | GitHub PAT for pull mirroring (if used) | Yes | Yes | + +### Optional Variables + +| Variable Name | Description | Protected | Masked | +|---------------|-------------|-----------|--------| +| `DEPLOY_SSH_KEY` | SSH key for deployment servers | Yes | Yes | +| `STAGING_HOST` | Staging server hostname | No | No | +| `PRODUCTION_HOST` | Production server hostname | Yes | No | + +--- + +## Application Secrets + +These should be set as environment variables in your deployment environment. + +### Required for Production + +| Environment Variable | Description | Example | +|---------------------|-------------|---------| +| `SECRET_KEY_BASE` | Phoenix secret key (64+ chars) | Generate with `mix phx.gen.secret` | +| `DATABASE_URL` | Database connection string (if using external DB) | `ecto://user:pass@host/db` | +| `PHX_HOST` | Production hostname | `branch-newspaper.example.com` | +| `PORT` | HTTP port to listen on | `4000` | + +### IPFS Configuration + +| Environment Variable | Description | Default | +|---------------------|-------------|---------| +| `IPFS_API_URL` | IPFS node API endpoint | `http://localhost:5001/api/v0` | +| `IPFS_GATEWAY_URL` | IPFS gateway for content retrieval | `http://localhost:8080/ipfs` | + +--- + +## Setup Instructions + +### GitLab SSH Key Setup + +This is required for the GitHub → GitLab mirror sync to work. + +1. **Generate a new SSH key pair** (or use an existing one): + ```bash + ssh-keygen -t ed25519 -C "github-to-gitlab-mirror" -f gitlab_deploy_key + ``` + +2. **Add the PUBLIC key to GitLab**: + - Go to: [gitlab.com/maa-framework/3-applications/branch-newspaper](https://gitlab.com/maa-framework/3-applications/branch-newspaper) + - Navigate to: Settings → Repository → Deploy keys + - Click "Add deploy key" + - Paste the contents of `gitlab_deploy_key.pub` + - **Enable "Grant write permissions to this key"** + +3. **Add the PRIVATE key to GitHub**: + - Go to: [github.com/hyperpolymath/branch-newspaper](https://github.com/hyperpolymath/branch-newspaper) + - Navigate to: Settings → Secrets and variables → Actions + - Click "New repository secret" + - Name: `GITLAB_SSH_KEY` + - Value: Paste the entire contents of `gitlab_deploy_key` (the private key file) + +4. **Verify the setup**: + - Go to Actions tab in GitHub + - Manually trigger "Mirror Sync" workflow + - Check the workflow logs + +### Generating Phoenix Secret Key + +```bash +# In a terminal with Elixir installed: +mix phx.gen.secret + +# Or using OpenSSL: +openssl rand -base64 64 +``` + +### Database URL Format + +For different database backends: + +```bash +# SQLite (default for development) +# No DATABASE_URL needed - uses local file + +# PostgreSQL +DATABASE_URL=ecto://username:password@hostname:5432/database_name + +# MySQL +DATABASE_URL=ecto://username:password@hostname:3306/database_name +``` + +--- + +## Security Best Practices + +1. **Never commit secrets to version control** + - Use `.env` files locally (add to `.gitignore`) + - Use CI/CD secret management features + +2. **Rotate secrets regularly** + - `SECRET_KEY_BASE`: Rotate quarterly or after personnel changes + - `GITLAB_SSH_KEY`: Rotate annually or after suspected compromise + +3. **Use the principle of least privilege** + - Deploy keys should have minimal required permissions + - API tokens should be scoped appropriately + +4. **Monitor for exposed secrets** + - Enable GitHub secret scanning + - Use pre-commit hooks to prevent accidental commits + +--- + +## Troubleshooting + +### Mirror sync fails with "Permission denied" +- Verify `GITLAB_SSH_KEY` is set correctly +- Ensure the deploy key has write permission on GitLab +- Check that the key format is correct (full private key including headers) + +### CI fails to fetch dependencies +- Check if `HEX_API_KEY` is needed for private packages +- Verify network access from CI runners + +### Deployment fails with "SECRET_KEY_BASE not set" +- Ensure all required environment variables are configured +- Check deployment platform's secret management + +--- + +*Last updated: 2025-12-08* diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..971b659 --- /dev/null +++ b/TODO.md @@ -0,0 +1,221 @@ +# Branch Newspaper - TODO List + +This document outlines improvements, missing tests, documentation gaps, and technical debt identified during the repository migration and analysis. + +## Priority Legend +- **P0** - Critical: Blocks deployment or has security implications +- **P1** - High: Important for production readiness +- **P2** - Medium: Improves quality and maintainability +- **P3** - Low: Nice to have improvements + +--- + +## Security Issues (P0) + +### Database Files in Repository +- [ ] **Remove SQLite database files from version control** + - Files: `branch_newspaper_dev.db`, `branch_newspaper_dev.db-shm`, `branch_newspaper_dev.db-wal` + - Impact: May contain sensitive test data, increases repository size + - Fix: Add to `.gitignore`, remove from history using `git filter-branch` or BFG + +### Binary Files in Repository +- [ ] **Remove Kubo binary tarball from repository** + - File: `kubo_v0.24.0_linux-amd64.tar.gz` + - Impact: Large binary, version-specific, increases clone time + - Fix: Document download instructions, add to `.gitignore`, use CI to fetch + +### Secrets Configuration +- [ ] **Configure production secrets properly** + - Ensure `SECRET_KEY_BASE` is set via environment variable + - Verify IPFS endpoint configuration is externalized + - Add secrets documentation for deployment + +--- + +## Missing Tests (P1) + +### Services Layer +- [ ] **Add tests for `BranchNewspaper.Services.IpfsClient`** + - File: `lib/branch_newspaper/services/ipfs_client.ex` + - Coverage needed: + - `add_content/1` - success and error cases + - `get_content/1` - content retrieval and not found + - `pin_content/1` - pinning behavior + - Consider: Mock Tesla adapter for unit tests + +### Web Layer +- [ ] **Add tests for remaining controllers** + - Review coverage in `test/branch_newspaper_web/controllers/` + - Ensure API endpoints have integration tests + +### Application Layer +- [ ] **Add tests for `BranchNewspaper.Application`** + - Verify supervision tree starts correctly + - Test graceful shutdown behavior + +--- + +## Missing Documentation (P1) + +### User Documentation +- [ ] **Update README.md with comprehensive setup instructions** + - Current README is minimal boilerplate + - Add: Prerequisites, environment setup, configuration + - Add: IPFS/Kubo setup instructions + - Add: Development workflow + +### API Documentation +- [ ] **Document IPFS integration** + - Expected IPFS node configuration + - Content addressing scheme + - Pin management strategy + +### Architecture Documentation +- [ ] **Create ARCHITECTURE.md** + - System components overview + - Data flow diagrams + - IPFS integration patterns + - Phoenix LiveView patterns used + +--- + +## Code Quality Improvements (P2) + +### Static Analysis +- [ ] **Add Credo for static code analysis** + ```elixir + # Add to mix.exs deps + {:credo, "~> 1.7", only: [:dev, :test], runtime: false} + ``` + +- [ ] **Add Dialyxir for type checking** + ```elixir + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} + ``` + +- [ ] **Add Sobelow for security scanning** + ```elixir + {:sobelow, "~> 0.13", only: [:dev, :test], runtime: false} + ``` + +### Code Coverage +- [ ] **Add ExCoveralls for coverage reporting** + ```elixir + {:excoveralls, "~> 0.18", only: :test} + ``` + +### Error Handling +- [ ] **Improve IPFS client error handling** + - Add retry logic for transient failures + - Add timeout configuration + - Implement circuit breaker pattern + +--- + +## CI/CD Enhancements (P2) + +### GitHub Actions +- [ ] **Enable coverage reporting to Codecov/Coveralls** +- [ ] **Add Dialyzer to CI pipeline** (after adding dialyxir) +- [ ] **Add release publishing workflow** for tagged releases +- [ ] **Configure branch protection rules** + - Require CI to pass before merge + - Require code review + +### Performance +- [ ] **Optimize CI caching strategy** + - Cache PLT files for Dialyzer + - Consider using GitHub Actions cache v4 features + +### Security Scanning +- [ ] **Enable GitHub Advanced Security features** + - CodeQL is configured but verify it runs correctly + - Add secret scanning alerts + +--- + +## Feature Improvements (P3) + +### IPFS Integration +- [ ] **Add content verification after IPFS upload** +- [ ] **Implement content deduplication** +- [ ] **Add IPFS cluster support for redundancy** + +### UI/UX +- [ ] **Add loading states for IPFS operations** +- [ ] **Implement optimistic updates in LiveView** +- [ ] **Add pagination for minutes listing** + +### API +- [ ] **Add REST API for external integrations** +- [ ] **Implement GraphQL endpoint** (optional) +- [ ] **Add rate limiting** + +--- + +## Infrastructure (P3) + +### Deployment +- [ ] **Create Docker configuration** + - Multi-stage Dockerfile for minimal image + - docker-compose for local development with IPFS + +- [ ] **Add Kubernetes manifests** (if needed) + - Deployment, Service, ConfigMap, Secret templates + +### Monitoring +- [ ] **Add Prometheus metrics export** +- [ ] **Configure structured logging** +- [ ] **Add health check endpoints** + +--- + +## Repository Housekeeping (P3) + +### Git History +- [ ] **Clean up large binary files from history** + - Use BFG Repo-Cleaner or git filter-repo + - Affects: kubo tarball, SQLite files + +### Documentation Files +- [ ] **Review and update CODE_OF_CONDUCT.md** +- [ ] **Update SECURITY.md with vulnerability reporting process** +- [ ] **Add CONTRIBUTING.md** + +### Workflows +- [ ] **Remove unused workflow files** + - Review `jekyll-gh-pages.yml` necessity + +--- + +## Completed Items + +- [x] Set up GitHub Actions CI workflow +- [x] Set up GitLab CI configuration +- [x] Configure Dependabot for dependency updates +- [x] Create shared CI scripts +- [x] Set up push mirroring workflow +- [x] Add mirror verification script +- [x] Configure CodeQL security scanning + +--- + +## Notes + +### GitLab Migration Status +The original repository is at `gitlab.com/maa-framework/3-applications/branch-newspaper`. The migration to GitHub is configured for event-driven push mirroring. + +**Important**: GitLab currently blocks HTTP git access. To complete the code migration: +1. Configure SSH deploy key on GitLab +2. Run manual sync using `ci-scripts/mirror-push.sh` +3. Verify with `ci-scripts/verify-mirror.sh` + +### Elixir Version Compatibility +- **Minimum**: Elixir 1.15.0 / OTP 25.3 +- **Primary**: Elixir 1.15.7 / OTP 26.2 +- **Latest tested**: Elixir 1.16.0 / OTP 26.2 + +--- + +*Generated during repository migration on $(date +%Y-%m-%d)* +*Last updated: 2025-12-08* diff --git a/ci-scripts/build.sh b/ci-scripts/build.sh new file mode 100755 index 0000000..08b88c9 --- /dev/null +++ b/ci-scripts/build.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# CI Build Script - Shared between GitHub Actions and GitLab CI +# This script builds the application for deployment + +set -euo pipefail + +echo "==> Building Branch Newspaper" + +# Set environment +export MIX_ENV="${MIX_ENV:-prod}" + +echo "==> Building in $MIX_ENV environment" + +# Get dependencies +echo "==> Getting dependencies..." +mix deps.get --only $MIX_ENV + +# Compile +echo "==> Compiling..." +mix compile + +# Build assets +echo "==> Building assets..." +if [ -f "assets/package.json" ]; then + cd assets + npm ci + cd .. +fi + +# Deploy assets (esbuild + tailwind) +mix assets.deploy + +# Create release +if grep -q "releases:" mix.exs 2>/dev/null; then + echo "==> Creating release..." + mix release --overwrite + + # Show release info + echo "" + echo "==> Release created successfully!" + ls -la _build/$MIX_ENV/rel/branch_newspaper/ 2>/dev/null || true +else + echo "==> Skipping release (not configured in mix.exs)" +fi + +echo "==> Build complete!" diff --git a/ci-scripts/lint.sh b/ci-scripts/lint.sh new file mode 100755 index 0000000..d356559 --- /dev/null +++ b/ci-scripts/lint.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# CI Lint Script - Shared between GitHub Actions and GitLab CI +# This script runs code quality checks + +set -euo pipefail + +echo "==> Running Branch Newspaper Linting & Code Quality Checks" + +EXIT_CODE=0 + +# Check formatting +echo "==> Checking code formatting..." +if ! mix format --check-formatted; then + echo "ERROR: Code is not properly formatted. Run 'mix format' to fix." + EXIT_CODE=1 +fi + +# Check for unused dependencies +echo "==> Checking for unused dependencies..." +if mix deps.unlock --check-unused 2>/dev/null; then + echo "OK: No unused dependencies found." +else + echo "WARNING: Unused dependencies detected." + # Don't fail on unused deps, just warn +fi + +# Compile with warnings as errors +echo "==> Compiling with warnings as errors..." +if ! mix compile --warnings-as-errors --force; then + echo "ERROR: Compilation warnings found." + EXIT_CODE=1 +fi + +# Run Credo for static code analysis (if available) +if grep -q "credo" mix.exs 2>/dev/null; then + echo "==> Running Credo static analysis..." + if ! mix credo --strict; then + echo "WARNING: Credo found issues." + # Make Credo failures advisory for now + fi +fi + +# Run Dialyzer for type checking (if PLT exists or --dialyzer flag passed) +if [ "${RUN_DIALYZER:-false}" = "true" ]; then + echo "==> Running Dialyzer type analysis..." + if grep -q "dialyxir" mix.exs 2>/dev/null; then + mix dialyzer --format github || echo "WARNING: Dialyzer found issues." + else + echo "SKIP: Dialyxir not configured." + fi +fi + +# Check for security vulnerabilities in dependencies +if grep -q "sobelow" mix.exs 2>/dev/null; then + echo "==> Running Sobelow security scan..." + mix sobelow --config || echo "WARNING: Sobelow found potential issues." +fi + +# Check for outdated dependencies +echo "==> Checking for outdated dependencies..." +mix hex.outdated || true + +echo "==> Lint checks complete with exit code: $EXIT_CODE" +exit $EXIT_CODE diff --git a/ci-scripts/mirror-push.sh b/ci-scripts/mirror-push.sh new file mode 100755 index 0000000..c97edc1 --- /dev/null +++ b/ci-scripts/mirror-push.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Mirror Push Script - Pushes changes from GitHub to GitLab +# This is triggered by GitHub Actions on push events (event-driven, not polling) + +set -euo pipefail + +echo "==> Pushing to GitLab Mirror" + +# Configuration from environment +GITLAB_URL="${GITLAB_URL:-git@gitlab.com:maa-framework/3-applications/branch-newspaper.git}" +GITLAB_REMOTE_NAME="gitlab-mirror" + +# Setup GitLab remote if not exists +if ! git remote get-url "$GITLAB_REMOTE_NAME" &>/dev/null; then + echo "==> Adding GitLab mirror remote..." + git remote add "$GITLAB_REMOTE_NAME" "$GITLAB_URL" +fi + +# Get current branch/ref +CURRENT_REF="${GITHUB_REF:-$(git symbolic-ref HEAD 2>/dev/null || git rev-parse HEAD)}" +CURRENT_BRANCH="${CURRENT_REF#refs/heads/}" + +echo "==> Current ref: $CURRENT_REF" +echo "==> Current branch: $CURRENT_BRANCH" + +# Push with retry logic +MAX_RETRIES=4 +RETRY_DELAY=2 + +push_with_retry() { + local target=$1 + local retries=0 + + while [ $retries -lt $MAX_RETRIES ]; do + echo "==> Pushing $target to GitLab (attempt $((retries + 1))/$MAX_RETRIES)..." + + if git push "$GITLAB_REMOTE_NAME" "$target" --force-with-lease 2>&1; then + echo "==> Successfully pushed $target" + return 0 + fi + + retries=$((retries + 1)) + if [ $retries -lt $MAX_RETRIES ]; then + echo "==> Push failed, retrying in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY + RETRY_DELAY=$((RETRY_DELAY * 2)) + fi + done + + echo "==> Failed to push $target after $MAX_RETRIES attempts" + return 1 +} + +# Push branches +echo "==> Pushing branches..." +push_with_retry "$CURRENT_BRANCH" + +# Push tags if this is a tag push +if [[ "$CURRENT_REF" == refs/tags/* ]]; then + TAG_NAME="${CURRENT_REF#refs/tags/}" + echo "==> Pushing tag: $TAG_NAME" + push_with_retry "refs/tags/$TAG_NAME" +fi + +# Optionally push all tags +if [ "${PUSH_ALL_TAGS:-false}" = "true" ]; then + echo "==> Pushing all tags..." + git push "$GITLAB_REMOTE_NAME" --tags --force-with-lease || true +fi + +echo "==> Mirror push complete!" diff --git a/ci-scripts/setup.sh b/ci-scripts/setup.sh new file mode 100755 index 0000000..1a9ca85 --- /dev/null +++ b/ci-scripts/setup.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# CI Setup Script - Shared between GitHub Actions and GitLab CI +# This script installs dependencies and prepares the environment + +set -euo pipefail + +echo "==> Setting up Branch Newspaper CI environment" + +# Detect OS and architecture +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m) +case $ARCH in + x86_64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; +esac + +echo "==> Detected OS: $OS, Architecture: $ARCH" + +# Install Elixir dependencies +echo "==> Installing Mix dependencies..." +mix local.hex --force +mix local.rebar --force +mix deps.get + +# Compile in the appropriate environment +MIX_ENV="${MIX_ENV:-test}" +echo "==> Compiling in $MIX_ENV environment..." +mix compile --warnings-as-errors + +# Setup database +echo "==> Setting up database..." +mix ecto.create || true +mix ecto.migrate + +# Install Node.js dependencies for assets (if needed) +if [ -f "assets/package.json" ]; then + echo "==> Installing Node.js dependencies..." + cd assets && npm ci && cd .. +fi + +# Setup Kubo/IPFS if needed for tests +if [ -d "kubo" ] && [ "${SETUP_IPFS:-false}" = "true" ]; then + echo "==> Setting up IPFS (Kubo)..." + if [ ! -f "kubo/ipfs" ]; then + KUBO_VERSION="${KUBO_VERSION:-v0.24.0}" + KUBO_TARBALL="kubo_${KUBO_VERSION}_${OS}-${ARCH}.tar.gz" + + if [ -f "$KUBO_TARBALL" ]; then + tar -xzf "$KUBO_TARBALL" -C kubo --strip-components=1 + else + echo "Warning: Kubo tarball not found, skipping IPFS setup" + fi + fi +fi + +echo "==> Setup complete!" diff --git a/ci-scripts/test.sh b/ci-scripts/test.sh new file mode 100755 index 0000000..069c643 --- /dev/null +++ b/ci-scripts/test.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# CI Test Script - Shared between GitHub Actions and GitLab CI +# This script runs all tests and generates coverage reports + +set -euo pipefail + +echo "==> Running Branch Newspaper Tests" + +# Set environment +export MIX_ENV=test + +# Parse arguments +COVERAGE="${COVERAGE:-false}" +FORMAT="${FORMAT:-default}" + +while [[ $# -gt 0 ]]; do + case $1 in + --coverage) + COVERAGE=true + shift + ;; + --format) + FORMAT="$2" + shift 2 + ;; + *) + shift + ;; + esac +done + +# Ensure dependencies are compiled +echo "==> Ensuring test dependencies are compiled..." +mix deps.compile + +# Run database migrations +echo "==> Running database migrations..." +mix ecto.create --quiet || true +mix ecto.migrate --quiet + +# Run tests +echo "==> Running ExUnit tests..." + +TEST_OPTS="" + +if [ "$COVERAGE" = "true" ]; then + echo "==> Coverage reporting enabled" + # If using excoveralls + if grep -q "excoveralls" mix.exs 2>/dev/null; then + case $FORMAT in + github) + mix coveralls.github + ;; + html) + mix coveralls.html + ;; + *) + mix coveralls + ;; + esac + else + mix test --cover + fi +else + mix test $TEST_OPTS +fi + +TEST_EXIT_CODE=$? + +echo "==> Test run complete with exit code: $TEST_EXIT_CODE" +exit $TEST_EXIT_CODE diff --git a/ci-scripts/verify-mirror.sh b/ci-scripts/verify-mirror.sh new file mode 100755 index 0000000..fad605f --- /dev/null +++ b/ci-scripts/verify-mirror.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +# Mirror Verification Script +# Confirms that all branches and tags have been transferred between repositories + +set -euo pipefail + +echo "==> Branch Newspaper Mirror Verification" + +# Configuration +SOURCE_REMOTE="${SOURCE_REMOTE:-gitlab}" +DEST_REMOTE="${DEST_REMOTE:-origin}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +ERRORS=0 +WARNINGS=0 + +# Function to print status +print_status() { + local status=$1 + local message=$2 + case $status in + ok) + echo -e "${GREEN}[OK]${NC} $message" + ;; + error) + echo -e "${RED}[ERROR]${NC} $message" + ((ERRORS++)) + ;; + warn) + echo -e "${YELLOW}[WARN]${NC} $message" + ((WARNINGS++)) + ;; + esac +} + +# Fetch latest from both remotes +echo "==> Fetching from remotes..." +git fetch "$SOURCE_REMOTE" --tags --prune 2>/dev/null || print_status warn "Could not fetch from $SOURCE_REMOTE" +git fetch "$DEST_REMOTE" --tags --prune 2>/dev/null || print_status warn "Could not fetch from $DEST_REMOTE" + +# Verify branches +echo "" +echo "==> Verifying branches..." + +SOURCE_BRANCHES=$(git branch -r | grep "^ $SOURCE_REMOTE/" | sed "s| $SOURCE_REMOTE/||" | grep -v "HEAD" || true) +DEST_BRANCHES=$(git branch -r | grep "^ $DEST_REMOTE/" | sed "s| $DEST_REMOTE/||" | grep -v "HEAD" || true) + +if [ -z "$SOURCE_BRANCHES" ]; then + print_status warn "No branches found on $SOURCE_REMOTE" +else + for branch in $SOURCE_BRANCHES; do + if echo "$DEST_BRANCHES" | grep -q "^$branch$"; then + # Check if commits match + SOURCE_COMMIT=$(git rev-parse "$SOURCE_REMOTE/$branch" 2>/dev/null || echo "") + DEST_COMMIT=$(git rev-parse "$DEST_REMOTE/$branch" 2>/dev/null || echo "") + + if [ "$SOURCE_COMMIT" = "$DEST_COMMIT" ]; then + print_status ok "Branch '$branch' synced (${SOURCE_COMMIT:0:7})" + else + print_status error "Branch '$branch' out of sync: $SOURCE_REMOTE=${SOURCE_COMMIT:0:7} vs $DEST_REMOTE=${DEST_COMMIT:0:7}" + fi + else + print_status error "Branch '$branch' missing on $DEST_REMOTE" + fi + done +fi + +# Verify tags +echo "" +echo "==> Verifying tags..." + +SOURCE_TAGS=$(git tag -l | sort) +DEST_TAGS=$(git ls-remote --tags "$DEST_REMOTE" 2>/dev/null | awk '{print $2}' | sed 's|refs/tags/||' | grep -v '\^{}' | sort || true) + +if [ -z "$SOURCE_TAGS" ]; then + print_status warn "No tags found in repository" +else + for tag in $SOURCE_TAGS; do + if echo "$DEST_TAGS" | grep -q "^$tag$"; then + print_status ok "Tag '$tag' present on $DEST_REMOTE" + else + print_status error "Tag '$tag' missing on $DEST_REMOTE" + fi + done +fi + +# Verify commit history integrity +echo "" +echo "==> Verifying commit history..." + +# Get the main/master branch +MAIN_BRANCH="" +for branch in main master; do + if git rev-parse --verify "$DEST_REMOTE/$branch" &>/dev/null; then + MAIN_BRANCH=$branch + break + fi +done + +if [ -n "$MAIN_BRANCH" ]; then + COMMIT_COUNT=$(git rev-list --count "$DEST_REMOTE/$MAIN_BRANCH" 2>/dev/null || echo "0") + print_status ok "Commit history intact: $COMMIT_COUNT commits on $MAIN_BRANCH" + + # Show recent commits + echo "" + echo "==> Recent commits on $MAIN_BRANCH:" + git log --oneline -5 "$DEST_REMOTE/$MAIN_BRANCH" 2>/dev/null || true +else + print_status warn "Could not find main/master branch" +fi + +# Summary +echo "" +echo "==> Verification Summary" +echo "========================" +echo "Errors: $ERRORS" +echo "Warnings: $WARNINGS" + +if [ $ERRORS -gt 0 ]; then + echo "" + echo -e "${RED}Mirror verification FAILED${NC}" + exit 1 +else + echo "" + echo -e "${GREEN}Mirror verification PASSED${NC}" + exit 0 +fi