Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:

jobs:
build:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
50 changes: 46 additions & 4 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ concurrency:
cancel-in-progress: true

jobs:
lint:
name: ESLint
lint-backend:
name: ESLint backend
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand All @@ -35,8 +35,50 @@ jobs:
run: npx eslint .
working-directory: apps/backend

node-tests:
name: Node.js tests
lint-frontend:
name: ESLint frontend
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
cache-dependency-path: apps/frontend/package-lock.json

- name: Install deps
run: npm ci
working-directory: apps/frontend

- name: Run ESLint
run: npx eslint .
working-directory: apps/frontend

lint-landing:
name: ESLint landing
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
cache-dependency-path: apps/landing/package-lock.json

- name: Install deps
run: npm ci
working-directory: apps/landing

- name: Run ESLint
run: npx eslint .
working-directory: apps/landing

tests-backend:
name: Backend tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand Down
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Unlike generic flashcard apps, Lingput adapts to your vocabulary and provides **

---

## 🚀 Architectural & Technical Highlights
## Architectural & Technical Highlights

This project was built to production-grade standards, demonstrating expertise in full-stack development, system design, and scalability. Here are the key technical features:

Expand All @@ -29,6 +29,38 @@ This project was built to production-grade standards, demonstrating expertise in

---

## CI/CD

This repo ships with a simple, reliable pipeline built around **Docker**, **GitHub Actions**, and **CapRover** on a DigitalOcean droplet.

### Branch strategy & protections

- `main` is **protected**: direct pushes are blocked; changes land via Pull Requests.
- Status checks (tests + ESLint) are **required** to merge.

### Continuous Integration — `pr-tests.yml`

On every **Pull Request** and on **pushes to `main`**, GitHub Actions runs:

- **ESLint** for the codebase.
- **Unit/Integration tests**.
- Dependency caching to keep CI fast.

### Continuous Delivery — `deploy.yml` (CapRover on DigitalOcean)

- **Trigger:** Runs when the PR Tests workflow completes on commits to `main`.
- **Docker images:** Services are built via Docker and tagged (with the commit SHA).
- **CapRover release:** The workflow updates CapRover apps using the new image tags.
- **What gets built and deployed:**
- Backend API (`lingput-backend`)
- Worker (BullMQ worker) (`lingput-worker`)
- Frontend app (`lingput-frontend`)
- Marketing/landing site (`lingput-landing`)
- API/docs site (`lingput-docs`)
- **NGINX:** Not deployed by this workflow. On CapRover, NGINX is provided by the platform (you configure routes/SSL there). The `lingput-nginx` image in compose is only for self-hosted Docker setups.

---

<p align="center" id="video-demo">
<img src="docs/lingput-demo.gif" width="960" height="540"/>
</p>
Expand Down
45 changes: 30 additions & 15 deletions apps/frontend/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
import { defineConfig } from "eslint/config";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
baseDirectory: __dirname,
});

const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];

export default eslintConfig;
export default defineConfig([
{
ignores: ["**/*.test.{js,mjs,cjs,ts,mts,cts}", "**/*.config.{js,mjs,cjs,ts,mts,cts}", ".next/"],
},
{
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
languageOptions: {
globals: { ...globals.browser, ...globals.node },
ecmaVersion: "latest",
sourceType: "module",
parserOptions: { ecmaFeatures: { jsx: true } },
},
settings: { react: { version: "detect" } },
},
js.configs.recommended,
...tseslint.configs.recommended,
react.configs.flat.recommended,
{
plugins: { react },
rules: {
"react/react-in-jsx-scope": "off",
"react/jsx-uses-react": "off",
},
},
]);
Loading