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
26 changes: 10 additions & 16 deletions .github/workflows/ci.yml → .github/workflows/ci-api.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
name: CI
name: CI API

on:
pull_request:
paths:
- "apps/api/**"
- "packages/api-client/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/ci-api.yml"

jobs:
quality:
api-quality:
runs-on: ubuntu-latest

services:
Expand Down Expand Up @@ -39,17 +45,5 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Prettier
run: npm run format:check

- name: ESLint
run: npm run lint

- name: OpenAPI (Redocly)
run: npm run docs:lint

- name: Typecheck
run: npm run build

- name: Tests
run: npm test
- name: API quality
run: npm run api:ci
28 changes: 28 additions & 0 deletions .github/workflows/ci-web.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CI Web

on:
pull_request:
paths:
- "apps/web/**"
- "packages/api-client/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/ci-web.yml"

jobs:
web-quality:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm

- name: Install dependencies
run: npm ci

- name: Web quality
run: npm run web:ci
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ dist
coverage
.prisma
postman/
.cursor/
.cursor/
apps/web/.next
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ dist
node_modules
coverage
package-lock.json
src/db/migrations
apps/api/src/db/migrations
apps/web/.next
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ The platform is being developed with a clear MVP focus around:
- homework tracking
- tutor dashboard workflows

This repository contains the **public backend API** for Studiqo.
This repository is a **monorepo** containing Studiqo applications and shared packages.

**Frontend repository:** _TODO: add private frontend details if needed_
- `apps/api` - public backend API (Express + TypeScript)
- `apps/web` - Next.js frontend app
- `packages/api-client` - generated API types/client package

**Frontend repository:** now in `apps/web`
**Live demo:** _TODO_
**API docs:** OpenAPI 3.1 spec at [`docs/openapi/openapi.yaml`](docs/openapi/openapi.yaml) (served path prefix `/api/v1`).
**API docs:** OpenAPI 3.1 spec at [`apps/api/docs/openapi/openapi.yaml`](apps/api/docs/openapi/openapi.yaml) (served path prefix `/api/v1`).

---

Expand All @@ -48,7 +52,23 @@ From a development point of view, Studiqo is also being built to demonstrate:

## Quick Start

_TODO_
Install dependencies:

```bash
npm install
```

Run the API in development:

```bash
npm run dev:api
```

Run the web app in development:

```bash
npm run dev:web
```

---

Expand Down Expand Up @@ -78,7 +98,7 @@ The MVP is intended to allow a tutor to:

**API examples:** _TODO_
**Postman collection:** _TODO_
**OpenAPI:** [`docs/openapi/openapi.yaml`](docs/openapi/openapi.yaml). Validate with `npm run docs:lint`. Preview in the browser with `npm run docs:preview` (uses [redocly.yaml](redocly.yaml); default port 4000). To emit a static HTML file: `npx redocly build-docs docs/openapi/openapi.yaml -o api-docs.html`. For the frontend, point [openapi-typescript](https://github.com/drwpow/openapi-typescript) or [Orval](https://orval.dev/) at that file (or a hosted copy) to generate types or clients.
**OpenAPI:** [`apps/api/docs/openapi/openapi.yaml`](apps/api/docs/openapi/openapi.yaml). Validate with `npm run docs:lint`. Preview in the browser with `npm run docs:preview` (uses `apps/api/redocly.yaml`; default port 4000). To emit a static HTML file: `npx redocly build-docs apps/api/docs/openapi/openapi.yaml -o api-docs.html`. For the frontend, point [openapi-typescript](https://github.com/drwpow/openapi-typescript) or [Orval](https://orval.dev/) at that file (or a hosted copy) to generate types or clients.

---

Expand Down
5 changes: 5 additions & 0 deletions apps/api/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist
node_modules
coverage
package-lock.json
src/db/migrations
File renamed without changes.
File renamed without changes.
File renamed without changes.
66 changes: 66 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "studiqo-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"generate": "drizzle-kit generate",
"migrate": "drizzle-kit migrate",
"migrate:test": "ENV_FILE=.env.test drizzle-kit migrate",
"test": "vitest run",
"test:watch": "vitest",
"test:integration": "vitest run tests/integration",
"test:unit": "vitest run tests/unit",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"docs:lint": "redocly lint",
"docs:preview": "redocly preview",
"format": "prettier --write .",
"format:check": "prettier --check .",
"ci": "npm run format:check && npm run lint && npm run docs:lint && npm run build && npm test"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"dependencies": {
"@sentry/node": "^10.47.0",
"@types/jsonwebtoken": "^9.0.10",
"argon2": "^0.44.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"dotenv": "^17.3.1",
"drizzle-orm": "^0.45.1",
"express": "^5.2.1",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.3",
"morgan": "^1.10.1",
"postgres": "^3.4.8",
"resend": "^6.10.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@redocly/cli": "^2.25.2",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/morgan": "^1.9.10",
"@types/node": "^25.5.0",
"@types/supertest": "^6.0.2",
"drizzle-kit": "^0.31.10",
"eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"globals": "^17.4.0",
"prettier": "^3.8.1",
"supertest": "^7.1.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.57.2",
"vitest": "^3.2.4",
"yaml": "^2.8.3"
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
24 changes: 23 additions & 1 deletion src/config/config.ts → apps/api/src/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
import dotenv from "dotenv";
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { MigrationConfig } from "drizzle-orm/migrator";

dotenv.config({ path: process.env.ENV_FILE ?? ".env" });
function resolveEnvFilePath(): string {
const envFile = process.env.ENV_FILE;

if (envFile) {
const workspacePath = resolve(process.cwd(), envFile);
if (existsSync(workspacePath)) {
return workspacePath;
}
const repoRootPath = resolve(process.cwd(), "../../", envFile);
return repoRootPath;
}

const workspaceDefaultPath = resolve(process.cwd(), ".env");
if (existsSync(workspaceDefaultPath)) {
return workspaceDefaultPath;
}

return resolve(process.cwd(), "../../.env");
}

dotenv.config({ path: resolveEnvFilePath() });

type Config = {
api: APIConfig;
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
22 changes: 22 additions & 0 deletions apps/api/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { config as loadDotenv } from "dotenv";
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { defineConfig } from "vitest/config";

function resolveTestEnvPath(): string {
const workspacePath = resolve(process.cwd(), ".env.test");
if (existsSync(workspacePath)) {
return workspacePath;
}
return resolve(process.cwd(), "../../.env.test");
}

loadDotenv({ path: resolveTestEnvPath(), override: true });

export default defineConfig({
test: {
environment: "node",
include: ["tests/**/*.test.ts"],
fileParallelism: false,
},
});
2 changes: 2 additions & 0 deletions apps/web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.next
node_modules
15 changes: 15 additions & 0 deletions apps/web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Studiqo Web

Next.js frontend workspace for Studiqo.

## Development

```bash
npm run dev -w apps/web
```

## Build

```bash
npm run build -w apps/web
```
9 changes: 9 additions & 0 deletions apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ReactNode } from "react";

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
8 changes: 8 additions & 0 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function Page() {
return (
<main>
<h1>Studiqo Frontend</h1>
<p>Next.js workspace scaffold complete.</p>
</main>
);
}
21 changes: 21 additions & 0 deletions apps/web/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import eslint from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import globals from "globals";
import tseslint from "typescript-eslint";

export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
},
eslintConfigPrettier,
{
ignores: [".next/**", "node_modules/**"],
},
);
6 changes: 6 additions & 0 deletions apps/web/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
5 changes: 5 additions & 0 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {};

export default nextConfig;
28 changes: 28 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@studiqo/web",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint ."
},
"dependencies": {
"next": "16.2.2",
"react": "19.1.0",
"react-dom": "19.1.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"typescript": "^5.9.3",
"@types/node": "^25.5.0",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"eslint": "^10.1.0",
"eslint-config-next": "16.2.2",
"eslint-config-prettier": "^10.1.8",
"globals": "^17.4.0",
"typescript-eslint": "^8.57.2"
}
}
Loading
Loading