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
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SQL Server
MSSQL_SA_PASSWORD=your-strong-password-here
MSSQL_PID=Evaluation
MSSQL_PORT=1433

# MinIO
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=your-minio-password-here
MINIO_API_PORT=9000
MINIO_CONSOLE_PORT=9001

# App
APP_PORT=5000
5 changes: 5 additions & 0 deletions .github/workflows/main_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ jobs:
JwtOptions.Secret: ${{ secrets.JWT_SECRET}}
JwtOptions.ExpirationInMinutes: ${{ secrets.JWT_EXPIRATION_IN_MINUTES}}
ApplicationInsights.InstrumentationKey: ${{ secrets.AZURE_APPINSIGHTS_INSTRUMENTAL_KEY}}
Minio.Endpoint: ${{ secrets.MINIO_ENDPOINT}}
Minio.PublicEndpoint: ${{ secrets.MINIO_PUBLICENDPOINT}}
Minio.AccessKey: ${{ secrets.MINIO_ACCESSKEY}}
Minio.SecretKey: ${{ secrets.MINIO_SECRETKEY}}
Minio.BucketName: ${{ secrets.MINIO_BUCKETNAME}}

- name: Login to Docker Hub
uses: docker/login-action@v2
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## Environment files
.env

## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
Expand All @@ -6,11 +9,13 @@
# Generated css
src/app/wwwroot/app.css

# React admin build output
src/app/wwwroot/admin/

# secret files
secrets/
hangfire.db*
projects.db*
docker-compose.prod.yml

# dotenv files
.env
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"Lexend",
"linux",
"Logbessou",
"minio",
"Noumbo",
"Picsum",
"Postgre",
Expand Down
87 changes: 87 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# CLAUDE.md

## Project Overview

.NET Cameroon Community Website — official site for the .NET Cameroon community at [dotnet.cm](https://dotnet.cm).

## Architecture

- **Blazor SSR** for public pages (SEO-friendly)
- **React SPA** (`src/admin/`) for admin dashboard (migrating from Blazor WASM)
- **Clean layered architecture**: app → app.infrastructure → app.business → app.domain → app.shared
- **Dual database**: SQL Server (primary data), SQLite (projects cache)
- **EF Core 10** with domain events interceptor, complex properties for value objects

## Tech Stack

- .NET 10, ASP.NET Core, Blazor SSR
- React + Vite + TanStack Router/Query (admin)
- Tailwind CSS 4
- SQL Server 2022, SQLite
- Docker Compose for local dev
- OpenTelemetry + Aspire Dashboard

## Setup

```bash
# Start infrastructure
docker compose up -d

# Install JS dependencies
pnpm install

# Build Tailwind CSS
pnpm run tailwind

# Run the app
dotnet run --project ./src/app

# Dev mode (Tailwind + dotnet)
pnpm run dev
```

## Key Commands

```bash
# Run tests
dotnet test src

# Build Tailwind CSS
pnpm run tailwind

# Watch Tailwind
pnpm run tailwind-watch

# Admin dashboard dev server
pnpm run admin:dev

# Build admin dashboard
pnpm run admin:build
```

## Package Management

- Use **pnpm** (not npm) for all JS/TS package management

## Project Structure

```
src/
app/ # Main ASP.NET Core web app (Blazor SSR)
app.domain/ # Domain entities, aggregates, value objects
app.business/ # Service interfaces, repository contracts
app.infrastructure/ # EF Core, service implementations
app.client/ # Blazor WASM (being removed — migrating to React)
app.shared/ # Shared utilities, constants
admin/ # React admin dashboard (Vite + TanStack)
dotnet-ef-seeder/ # Custom DB seeding package
```

## Conventions

- Minimal API pattern for endpoints (see `src/app/Api/`)
- ErrorOr result pattern for service layer operations
- Domain-Driven Design: aggregates, value objects, domain events
- Localization: en-US, fr-FR via RESX files
- Auth: Cookie auth (UI/admin), JWT Bearer (external API)
- Feature branch workflow: `feature/<name>` from `main`
43 changes: 43 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
services:
dotnetcameroon.app:
image: djoufson/dotnetcameroon
restart: unless-stopped
container_name: dotnetcameroon
environment:
OTEL_EXPORTER_OTLP_ENDPOINT: http://localhost:18889
ports:
- "${APP_PORT:-5000}:8080"
dotnetcameroon.sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: dotnetcameroon.sqlserver
user: root
hostname: sqlserver
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "${MSSQL_SA_PASSWORD}"
MSSQL_PID: "${MSSQL_PID:-Evaluation}"
ports:
- "${MSSQL_PORT:-1433}:1433"
volumes:
- sqlserver-data:/var/opt/mssql
dotnetcameroon.minio:
image: minio/minio:latest
container_name: dotnetcameroon.minio
environment:
MINIO_ROOT_USER: "${MINIO_ROOT_USER}"
MINIO_ROOT_PASSWORD: "${MINIO_ROOT_PASSWORD}"
ports:
- "${MINIO_API_PORT:-9000}:9000"
- "${MINIO_CONSOLE_PORT:-9001}:9001"
volumes:
- minio-data:/data
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 30s
timeout: 10s
retries: 5
start_period: 10s
volumes:
sqlserver-data:
minio-data:
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ services:
timeout: 10s
retries: 5
start_period: 30s
dotnetcameroon.minio:
image: minio/minio:latest
container_name: dotnetcameroon.minio
environment:
MINIO_ROOT_USER: "minioadmin"
MINIO_ROOT_PASSWORD: "minioadmin"
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio-data:/data
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 30s
timeout: 10s
retries: 5
start_period: 10s
dotnetcameroon.dashboard:
image: mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest
container_name: dotnetcameroon.dashboard
Expand All @@ -31,3 +49,4 @@ services:
- 18888:18888
volumes:
sqlserver-data:
minio-data:
1 change: 0 additions & 1 deletion dotnetcameroon.slnx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/app.business/app.business.csproj" />
<Project Path="src/app.client/app.client.csproj" />
<Project Path="src/app.domain/app.domain.csproj" />
<Project Path="src/app.infrastructure/app.infrastructure.csproj" />
<Project Path="src/app.shared/app.shared.csproj" />
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"start": "dotnet run --project ./src/app",
"watch": "npm run tailwind && dotnet watch --project ./src/app",
"tailwind": "npx @tailwindcss/cli -i ./src/app/styles/input.css -o ./src/app/wwwroot/app.css",
"tailwind-watch": "npx @tailwindcss/cli -i ./src/app/styles/input.css -o ./src/app/wwwroot/app.css --watch"
"tailwind-watch": "npx @tailwindcss/cli -i ./src/app/styles/input.css -o ./src/app/wwwroot/app.css --watch",
"admin:dev": "cd src/admin && pnpm run dev",
"admin:build": "cd src/admin && pnpm run build"
},
"repository": {
"type": "git",
Expand Down
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<PackageVersion Include="Hangfire" Version="1.8.17" />
<PackageVersion Include="Hangfire.MemoryStorage" Version="1.8.1.1" />
<PackageVersion Include="MediatR" Version="12.4.0" />
<PackageVersion Include="Minio" Version="7.0.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.0" />
</ItemGroup>
Expand Down
24 changes: 24 additions & 0 deletions src/admin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
73 changes: 73 additions & 0 deletions src/admin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)

## React Compiler

The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...

// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,

// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```

You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
23 changes: 23 additions & 0 deletions src/admin/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])
13 changes: 13 additions & 0 deletions src/admin/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>admin</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading