Compare commits

...

10 Commits

30 changed files with 7549 additions and 261 deletions

View File

@@ -1,118 +0,0 @@
name: CVE Security Check - Trivy + OWASP
on:
schedule:
- cron: '0 2 * * *'
push:
paths:
- 'package.json'
- 'pnpm-lock.yaml'
- '.gitea/workflows/cve-check.yml'
pull_request:
paths:
- 'package.json'
- 'pnpm-lock.yaml'
jobs:
trivy-scan:
name: Trivy Vulnerability Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'HIGH,CRITICAL'
- name: Display Trivy results
run: |
echo "## 🔍 Trivy Scan Results" >> $GITHUB_STEP_SUMMARY
if [ -f trivy-results.sarif ]; then
echo "✅ Scan complété" >> $GITHUB_STEP_SUMMARY
fi
- name: Upload Trivy SARIF to artifact
uses: actions/upload-artifact@v3
if: always()
with:
name: trivy-results
path: trivy-results.sarif
owasp-dependency-check:
name: OWASP Dependency-Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run OWASP Dependency-Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'memegoat'
path: '.'
format: 'JSON'
args: >
--enableExperimental
--suppression ./dependency-check-suppressions.xml
- name: Generate OWASP HTML Report
run: |
if [ -d "reports" ]; then
echo "📊 Rapport OWASP généré"
fi
- name: Upload OWASP reports
uses: actions/upload-artifact@v3
if: always()
with:
name: dependency-check-reports
path: reports/
- name: Parse OWASP results
run: |
if [ -f reports/dependency-check-report.json ]; then
echo "## 📋 OWASP Dependency-Check Results" >> $GITHUB_STEP_SUMMARY
CRITICAL=$(jq '[.reportSchema.vulnerabilities[] | select(.severity=="CRITICAL")] | length' reports/dependency-check-report.json || echo 0)
HIGH=$(jq '[.reportSchema.vulnerabilities[] | select(.severity=="HIGH")] | length' reports/dependency-check-report.json || echo 0)
MEDIUM=$(jq '[.reportSchema.vulnerabilities[] | select(.severity=="MEDIUM")] | length' reports/dependency-check-report.json || echo 0)
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| 🔴 CRITICAL | $CRITICAL |" >> $GITHUB_STEP_SUMMARY
echo "| 🟠 HIGH | $HIGH |" >> $GITHUB_STEP_SUMMARY
echo "| 🟡 MEDIUM | $MEDIUM |" >> $GITHUB_STEP_SUMMARY
if [ "$CRITICAL" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "⚠️ **Vulnérabilités CRITICAL détectées !**" >> $GITHUB_STEP_SUMMARY
exit 1
fi
fi
security-summary:
name: Security Summary
runs-on: ubuntu-latest
needs: [trivy-scan, owasp-dependency-check]
if: always()
steps:
- uses: actions/download-artifact@v3
- name: Generate final report
run: |
echo "## 🔐 Security Audit Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Trivy scan - Completed" >> $GITHUB_STEP_SUMMARY
echo "✅ OWASP Dependency-Check - Completed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "📁 [Télécharger les rapports détaillés](artifacts)" >> $GITHUB_STEP_SUMMARY
- name: Fail if critical vulnerabilities found
if: failure()
run: |
echo "🚨 Des vulnérabilités CRITICAL ont été détectées"
exit 1

View File

@@ -0,0 +1,25 @@
name: Backend Lint
on:
push:
paths:
- 'backend/**'
pull_request:
paths:
- 'backend/**'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Run lint
run: pnpm -F @memegoat/backend lint

View File

@@ -0,0 +1,25 @@
name: Frontend Lint
on:
push:
paths:
- 'frontend/**'
pull_request:
paths:
- 'frontend/**'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Run lint
run: pnpm -F @memegoat/frontend lint

50
.gitignore vendored Normal file
View File

@@ -0,0 +1,50 @@
# Dependencies
node_modules/
jspm_packages/
# Environment variables
.env
.env.local
.env.*.local
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
.DS_Store
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Build outputs
dist/
build/
out/
*.tsbuildinfo
# Testing
coverage/
.nyc_output/
*.lcov
# Cache
.cache/
.parcel-cache/
.eslintcache
.stylelintcache
# OS
.DS_Store
Thumbs.db
# Package manager
.pnpm-debug.log
.yarn/

303
.utlis/gitea_issues.ps1 Normal file
View File

@@ -0,0 +1,303 @@
<#
Gitea Issues Utility (PowerShell)
Prérequis:
- PowerShell 5+ (ou 7+)
- Fichier .env à la racine du dépôt avec:
GITEA_BASE_URL=https://git.example.com/api/v1
GITEA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
GITEA_DEFAULT_OWNER=owner
GITEA_DEFAULT_REPO=repo
Utilisation rapide:
.\gitea_issues.ps1 create -Title "Titre" \
[-Body "CorpsMarkdown" | -BodyFromStdin | -BodyFile chemin\\fichier.md | -BodyBase64 "..." ] \
-Labels "bug,help wanted" -Assignees "user1,user2" [-Owner o] [-Repo r]
.\gitea_issues.ps1 list [-State open|closed|all] [-Page 1] [-Limit 30] [-Owner o] [-Repo r]
.\gitea_issues.ps1 get -Index 12 [-Owner o] [-Repo r]
.\gitea_issues.ps1 update -Index 12 [-Title x] \
[-Body y | -BodyFromStdin | -BodyFile chemin\\fichier.md | -BodyBase64 "..."] \
[-Labels csv] [-Assignees csv] [-State open|closed] [-Owner o] [-Repo r]
.\gitea_issues.ps1 close -Index 12 [-Owner o] [-Repo r]
.\gitea_issues.ps1 open -Index 12 [-Owner o] [-Repo r]
.\gitea_issues.ps1 comment -Index 12 \
[-Message "TexteMarkdown" | -MessageFromStdin | -MessageFile chemin\\commentaire.md | -MessageBase64 "..."] \
[-Owner o] [-Repo r]
Priorité des sources de contenu (inline prioritaire):
Body: -Body > -BodyFromStdin > -BodyFile > -BodyBase64
Message: -Message > -MessageFromStdin > -MessageFile > -MessageBase64
Astuce: pour éviter les problèmes d'échappement avec les ``` en PowerShell, utilisez un here-string
ou passez le contenu via un pipe et -BodyFromStdin / -MessageFromStdin.
#>
[CmdletBinding(DefaultParameterSetName='help')]
param(
[Parameter(ParameterSetName='create')][switch]$create,
[Parameter(ParameterSetName='list')][switch]$list,
[Parameter(ParameterSetName='get')][switch]$get,
[Parameter(ParameterSetName='update')][switch]$update,
[Parameter(ParameterSetName='close')][switch]$close,
[Parameter(ParameterSetName='open')][switch]$open,
[Parameter(ParameterSetName='comment')][switch]$comment,
# communs
[string]$Owner,
[string]$Repo,
# create/update
[string]$Title,
[string]$Body,
[switch]$BodyFromStdin,
[string]$BodyFile,
[string]$BodyBase64,
[string]$Labels,
[string]$Assignees,
[ValidateSet('open','closed')][string]$State,
# list
[ValidateSet('open','closed','all')][string]$StateList='open',
[int]$Page=1,
[int]$Limit=30,
# get/update/close/open/comment
[int]$Index,
# comment
[string]$Message,
[switch]$MessageFromStdin,
[string]$MessageFile,
[string]$MessageBase64
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Capture éventuellement l'entrée du pipeline vers le script
# (permet d'utiliser: Get-Content -Raw file | .\gitea_issues.ps1 ... -BodyFromStdin)
try {
$script:__pipedInput = $null
# $input est un énumérateur; si le script reçoit un pipe, on le matérialise en texte
$script:__pipedInput = ($input | Out-String)
if ([string]::IsNullOrWhiteSpace($script:__pipedInput)) { $script:__pipedInput = $null }
} catch { $script:__pipedInput = $null }
function Load-Env {
# Détection robuste du dossier du script (compatibilité pwsh/host)
$scriptDir = $null
if ($PSScriptRoot) {
$scriptDir = $PSScriptRoot
} elseif ($PSCommandPath) {
$scriptDir = Split-Path -Parent $PSCommandPath
} elseif ($MyInvocation -and $MyInvocation.MyCommand -and $MyInvocation.MyCommand.Path) {
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
} else {
$scriptDir = (Get-Location).Path
}
$repoRoot = Resolve-Path (Join-Path $scriptDir '..')
$envFile = Join-Path $repoRoot '.env'
if (Test-Path $envFile) {
Get-Content $envFile | ForEach-Object {
if ($_ -match '^[A-Za-z_][A-Za-z0-9_]*=') {
$kv = $_ -replace '\r',''
$name,$value = $kv.Split('=',2)
if ($null -ne $value) {
$value = $value.Trim()
if ($value.StartsWith('"') -and $value.EndsWith('"') -and $value.Length -ge 2) {
$value = $value.Substring(1, $value.Length - 2)
}
Set-Item -Path ("Env:{0}" -f $name) -Value $value
}
}
}
}
}
function Ensure-UTF8 {
try {
# Forcer l'encodage console en UTF-8 (sans BOM)
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[Console]::OutputEncoding = $utf8NoBom
[Console]::InputEncoding = $utf8NoBom
$script:OutputEncoding = $utf8NoBom
$global:OutputEncoding = $utf8NoBom
# Sur Windows, fixer la page de code à 65001 si possible (silencieux)
if ($IsWindows) { cmd /c chcp 65001 > $null 2>&1 }
} catch { }
}
function Ensure-Env {
if (-not $env:GITEA_BASE_URL) { throw 'GITEA_BASE_URL manquant dans .env' }
if (-not $env:GITEA_TOKEN) { throw 'GITEA_TOKEN manquant dans .env' }
}
function Decode-Base64Utf8 {
param([string]$b64)
if (-not $b64) { return $null }
try {
$bytes = [Convert]::FromBase64String($b64)
return [System.Text.Encoding]::UTF8.GetString($bytes)
} catch {
throw 'Chaîne Base64 invalide'
}
}
function Read-AllStdin {
try {
# 1) Si l'entrée provient du pipeline (capturée au niveau script)
if ($script:__pipedInput) { return $script:__pipedInput }
# 2) Sinon, lecture directe de STDIN (pipe vers PowerShell)
$content = $null
try { $content = [Console]::In.ReadToEnd() } catch { $content = $null }
return $content
} catch {
return $null
}
}
function Resolve-Content {
param(
[string]$Inline,
[switch]$FromStdin,
[string]$FromFile,
[string]$FromBase64,
[string]$Kind # 'Body' | 'Message'
)
# Avertissements de priorité si plusieurs sources sont présentes
$provided = @()
if ($Inline) { $provided += 'Inline' }
if ($FromStdin) { $provided += 'Stdin' }
if ($FromFile) { $provided += 'File' }
if ($FromBase64) { $provided += 'Base64' }
if ($provided.Count -gt 1) {
Write-Warning ("Plusieurs sources pour {0} détectées ({1}); la priorité est Inline > Stdin > File > Base64." -f $Kind, ($provided -join ', '))
}
if ($Inline) { return $Inline }
if ($FromStdin) {
$stdin = Read-AllStdin
if ($stdin) { return $stdin }
Write-Warning ("{0} demandé depuis stdin mais aucun contenu lu; on continue sur la source suivante." -f $Kind)
}
if ($FromFile) {
if (-not (Test-Path -Path $FromFile -PathType Leaf)) { throw "Fichier introuvable: $FromFile" }
return Get-Content -LiteralPath $FromFile -Raw -Encoding UTF8
}
if ($FromBase64) { return Decode-Base64Utf8 $FromBase64 }
return $null
}
function Resolve-Repo {
param([string]$OwnerIn,[string]$RepoIn)
$o = if ($OwnerIn) { $OwnerIn } elseif ($env:OWNER) { $env:OWNER } elseif ($env:GITEA_DEFAULT_OWNER) { $env:GITEA_DEFAULT_OWNER } else { '' }
$r = if ($RepoIn) { $RepoIn } elseif ($env:REPO) { $env:REPO } elseif ($env:GITEA_DEFAULT_REPO) { $env:GITEA_DEFAULT_REPO } else { '' }
if (-not $o -or -not $r) { throw 'OWNER/REPO non définis (utilisez .env ou paramètres -Owner/-Repo)' }
return @($o,$r)
}
function Invoke-Gitea {
param(
[ValidateSet('GET','POST','PATCH','PUT','DELETE')][string]$Method,
[string]$Path,
$BodyObj
)
$base = $env:GITEA_BASE_URL.TrimEnd('/')
$uri = "$base$Path"
$headers = @{ Authorization = "token $($env:GITEA_TOKEN)" }
if ($PSBoundParameters.ContainsKey('BodyObj') -and $BodyObj -ne $null) {
$json = $BodyObj | ConvertTo-Json -Depth 10
# Encodage explicite UTF-8 pour le corps JSON
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
$headers['Content-Type'] = 'application/json; charset=utf-8'
return Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -Body $bytes
} else {
return Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers
}
}
function Write-Help {
Get-Content -TotalCount 40 $MyInvocation.MyCommand.Path | ForEach-Object { $_ }
}
try {
Ensure-UTF8
Load-Env
Ensure-Env
if ($create) {
if (-not $Title) { throw 'Paramètre -Title requis' }
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
$Body = Resolve-Content -Inline $Body -FromStdin:$BodyFromStdin -FromFile $BodyFile -FromBase64 $BodyBase64 -Kind 'Body'
$bodyObj = @{ title = $Title }
if ($Body) { $bodyObj.body = $Body }
if ($Labels) { $bodyObj.labels = ($Labels -split ',') | Where-Object { $_ -ne '' } }
if ($Assignees) { $bodyObj.assignees = ($Assignees -split ',') | Where-Object { $_ -ne '' } }
$res = Invoke-Gitea -Method POST -Path "/repos/$owner/$repo/issues" -BodyObj $bodyObj
$res | ConvertTo-Json -Depth 10 | Write-Output
return
}
if ($list) {
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
$res = Invoke-Gitea -Method GET -Path "/repos/$owner/$repo/issues?state=$StateList&page=$Page&limit=$Limit"
$res | ForEach-Object { [PSCustomObject]@{ number = $_.number; title = $_.title; state = $_.state; labels = ($_.labels | ForEach-Object { $_.name }) -join ',' } } | Format-Table -AutoSize
return
}
if ($get) {
if (-not $Index) { throw 'Paramètre -Index requis' }
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
$res = Invoke-Gitea -Method GET -Path "/repos/$owner/$repo/issues/$Index"
$res | ConvertTo-Json -Depth 10 | Write-Output
return
}
if ($update) {
if (-not $Index) { throw 'Paramètre -Index requis' }
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
$Body = Resolve-Content -Inline $Body -FromStdin:$BodyFromStdin -FromFile $BodyFile -FromBase64 $BodyBase64 -Kind 'Body'
$bodyObj = @{}
if ($Title) { $bodyObj.title = $Title }
if ($Body) { $bodyObj.body = $Body }
if ($Labels) { $bodyObj.labels = ($Labels -split ',') | Where-Object { $_ -ne '' } }
if ($Assignees) { $bodyObj.assignees = ($Assignees -split ',') | Where-Object { $_ -ne '' } }
if ($State) { $bodyObj.state = $State }
if ($bodyObj.Keys.Count -eq 0) { throw 'Aucun champ à mettre à jour' }
$res = Invoke-Gitea -Method PATCH -Path "/repos/$owner/$repo/issues/$Index" -BodyObj $bodyObj
$res | ConvertTo-Json -Depth 10 | Write-Output
return
}
if ($close) {
if (-not $Index) { throw 'Paramètre -Index requis' }
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
$res = Invoke-Gitea -Method PATCH -Path "/repos/$owner/$repo/issues/$Index" -BodyObj @{ state = 'closed' }
$res | ConvertTo-Json -Depth 10 | Write-Output
return
}
if ($open) {
if (-not $Index) { throw 'Paramètre -Index requis' }
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
$res = Invoke-Gitea -Method PATCH -Path "/repos/$owner/$repo/issues/$Index" -BodyObj @{ state = 'open' }
$res | ConvertTo-Json -Depth 10 | Write-Output
return
}
if ($comment) {
if (-not $Index) { throw 'Paramètre -Index requis' }
$Message = Resolve-Content -Inline $Message -FromStdin:$MessageFromStdin -FromFile $MessageFile -FromBase64 $MessageBase64 -Kind 'Message'
if (-not $Message) { throw 'Paramètre -Message (ou -MessageFromStdin / -MessageFile / -MessageBase64) requis' }
$owner,$repo = Resolve-Repo -OwnerIn $Owner -RepoIn $Repo
$res = Invoke-Gitea -Method POST -Path "/repos/$owner/$repo/issues/$Index/comments" -BodyObj @{ body = $Message }
$res | ConvertTo-Json -Depth 10 | Write-Output
return
}
Write-Help
}
catch {
Write-Error $_
exit 1
}

339
.utlis/gitea_issues.sh Normal file
View File

@@ -0,0 +1,339 @@
#!/usr/bin/env bash
# Gitea Issues Utility (Bash)
#
# Prérequis:
# - curl, jq
# - Fichier .env à la racine du projet contenant au minimum:
# GITEA_BASE_URL=https://git.example.com/api/v1
# GITEA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# GITEA_DEFAULT_OWNER=owner
# GITEA_DEFAULT_REPO=repo
#
# Utilisation rapide:
# ./gitea_issues.sh create -t "Titre" -b "Corps (Markdown)" [-F chemin/fichier.md] [-l label1,label2] [-a user1,user2]
# ./gitea_issues.sh list [-s open|closed|all] [-p page] [-P limit]
# ./gitea_issues.sh get <index>
# ./gitea_issues.sh update <index> [-t titre] [-b corps (Markdown) | -F chemin/fichier.md] [-l labels] [-a assignees]
# ./gitea_issues.sh close <index>
# ./gitea_issues.sh open <index>
# ./gitea_issues.sh comment <index> -m "message (Markdown)" [-M chemin/commentaire.md]
#
# Notes:
# - Les scripts lisent ../.env (depuis ./.utlis/) automatiquement.
# - Vous pouvez surcharger owner/repo via variables d'env OWNER/REPO ou flags --owner/--repo.
set -euo pipefail
# Assurer un environnement UTF-8 pour les E/S et curl/jq
ensure_utf8_locale() {
# Si LANG/LC_ALL ne sont pas déjà en UTF-8, essaye de basculer
local want="UTF-8"
if [[ "${LANG:-}" != *"$want"* || "${LC_ALL:-}" != *"$want"* ]]; then
local chosen=""
if command -v locale >/dev/null 2>&1; then
if locale -a 2>/dev/null | grep -qi '^C\.utf8\|C\.UTF-8$'; then
chosen="C.UTF-8"
elif locale -a 2>/dev/null | grep -qi '^en_US\.utf8\|en_US\.UTF-8$'; then
chosen="en_US.UTF-8"
elif locale -a 2>/dev/null | grep -qi '^fr_FR\.utf8\|fr_FR\.UTF-8$'; then
chosen="fr_FR.UTF-8"
fi
fi
if [[ -z "$chosen" ]]; then
# Fallback raisonnable si locale -a n'est pas dispo
chosen="C.UTF-8"
fi
export LANG="$chosen"
export LC_ALL="$chosen"
fi
}
ensure_utf8_locale
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Charger .env si présent
ENV_FILE="$REPO_ROOT/.env"
if [[ -f "$ENV_FILE" ]]; then
# shellcheck disable=SC2046
export $(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$ENV_FILE" | sed 's/#.*//')
fi
BASE_URL="${GITEA_BASE_URL:-}"
TOKEN="${GITEA_TOKEN:-}"
DEFAULT_OWNER="${GITEA_DEFAULT_OWNER:-}"
DEFAULT_REPO="${GITEA_DEFAULT_REPO:-}"
OWNER_OVERRIDE=""
REPO_OVERRIDE=""
require_tools() {
for t in curl jq; do
command -v "$t" >/dev/null 2>&1 || { echo "Erreur: outil requis introuvable: $t" >&2; exit 1; }
done
}
usage() {
sed -n '1,60p' "$0"
}
fail() { echo "Erreur: $*" >&2; exit 1; }
ensure_env() {
[[ -n "$BASE_URL" ]] || fail "GITEA_BASE_URL manquant (.env)"
[[ -n "$TOKEN" ]] || fail "GITEA_TOKEN manquant (.env)"
}
resolve_repo() {
local owner repo
owner="${OWNER_OVERRIDE:-${OWNER:-$DEFAULT_OWNER}}"
repo="${REPO_OVERRIDE:-${REPO:-$DEFAULT_REPO}}"
[[ -n "$owner" && -n "$repo" ]] || fail "OWNER/REPO non définis (utilisez .env ou --owner/--repo)"
echo "$owner" "$repo"
}
api() {
local method="$1" path="$2"; shift 2
local url="$BASE_URL$path"
curl -sS -X "$method" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json; charset=utf-8" \
-H "Accept-Charset: utf-8" \
"$url" "$@"
}
api_jq_or_status() {
# Lit la sortie JSON et vérifie erreurs Gitea
local http_code json
# Utilise curl avec -w pour capturer le code
json=$(curl -sS -w '\n%{http_code}' \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json; charset=utf-8" \
-H "Accept-Charset: utf-8" \
"$@")
http_code="${json##*$'\n'}"
json="${json%$'\n'*}"
if [[ "$http_code" -ge 400 ]]; then
echo "$json" | jq . 2>/dev/null || true
fail "HTTP $http_code"
else
echo "$json" | jq .
fi
}
parse_common_flags() {
while [[ $# -gt 0 ]]; do
case "$1" in
--owner) OWNER_OVERRIDE="$2"; shift 2;;
--repo) REPO_OVERRIDE="$2"; shift 2;;
-h|--help) usage; exit 0;;
*) break;;
esac
done
echo "$@"
}
cmd_create() {
local title="" body="" body_file="" labels="" assignees=""
while [[ $# -gt 0 ]]; do
case "$1" in
-t|--title) title="$2"; shift 2;;
-b|--body) body="$2"; shift 2;;
-F|--body-file) body_file="$2"; shift 2;;
-l|--labels) labels="$2"; shift 2;;
-a|--assignees) assignees="$2"; shift 2;;
*) break;;
esac
done
[[ -n "$title" ]] || fail "Titre requis (-t)"
read -r owner repo < <(resolve_repo)
local data
if [[ -n "$body" ]]; then
# Inline prioritaire
data=$(jq -n \
--arg title "$title" \
--arg body "$body" \
--arg labels_csv "$labels" \
--arg assignees_csv "$assignees" '
{
title: $title,
body: $body
}
+ ( ($labels_csv|length) > 0
then { labels: ($labels_csv | split(",") | map(select(. != ""))) } else {} end)
+ ( ($assignees_csv|length) > 0
then { assignees: ($assignees_csv | split(",") | map(select(. != ""))) } else {} end)
')
if [[ -n "$body_file" ]]; then
echo "Avertissement: -b et -F fournis; -b (inline) sera prioritaire" >&2
fi
elif [[ -n "$body_file" ]]; then
[[ -f "$body_file" ]] || fail "Fichier introuvable: $body_file"
data=$(jq -n \
--arg title "$title" \
--arg labels_csv "$labels" \
--arg assignees_csv "$assignees" \
--rawfile body "$body_file" '
{
title: $title,
body: $body
}
+ ( ($labels_csv|length) > 0
then { labels: ($labels_csv | split(",") | map(select(. != ""))) } else {} end)
+ ( ($assignees_csv|length) > 0
then { assignees: ($assignees_csv | split(",") | map(select(. != ""))) } else {} end)
')
else
data=$(jq -n \
--arg title "$title" '
{ title: $title }
')
fi
api_jq_or_status -X POST "$BASE_URL/repos/$owner/$repo/issues" --data-binary "$data"
}
cmd_list() {
local state="open" page="1" limit="30"
while [[ $# -gt 0 ]]; do
case "$1" in
-s|--state) state="$2"; shift 2;;
-p|--page) page="$2"; shift 2;;
-P|--limit) limit="$2"; shift 2;;
*) break;;
esac
done
read -r owner repo < <(resolve_repo)
api GET "/repos/$owner/$repo/issues?state=$state&page=$page&limit=$limit" | jq '.[].number as $n | {number: $n, title: .title, state: .state, labels: [.labels[].name]}'
}
cmd_get() {
local index="$1"; [[ -n "$index" ]] || fail "Index requis"
read -r owner repo < <(resolve_repo)
api GET "/repos/$owner/$repo/issues/$index" | jq '{number, title, state, body, labels: [.labels[].name], assignees: [.assignees[].login], created_at, updated_at}'
}
cmd_update() {
local index title="" body="" body_file="" labels="" assignees="" state=""
index="$1"; shift || true
[[ -n "$index" ]] || fail "Index requis"
while [[ $# -gt 0 ]]; do
case "$1" in
-t|--title) title="$2"; shift 2;;
-b|--body) body="$2"; shift 2;;
-F|--body-file) body_file="$2"; shift 2;;
-l|--labels) labels="$2"; shift 2;;
-a|--assignees) assignees="$2"; shift 2;;
-s|--state) state="$2"; shift 2;;
*) break;;
esac
done
read -r owner repo < <(resolve_repo)
local data
if [[ -n "$body" ]]; then
data=$(jq -n \
--arg title "$title" \
--arg body "$body" \
--arg labels_csv "$labels" \
--arg assignees_csv "$assignees" \
--arg state "$state" '
{}
+ ( ($title|length)>0 then {title:$title} else {} end )
+ {body:$body}
+ ( ($labels_csv|length)>0 then {labels: ($labels_csv|split(",")|map(select(.!="")))} else {} end )
+ ( ($assignees_csv|length)>0 then {assignees: ($assignees_csv|split(",")|map(select(.!="")))} else {} end )
+ ( ($state|length)>0 then {state:$state} else {} end )
')
if [[ -n "$body_file" ]]; then
echo "Avertissement: -b et -F fournis; -b (inline) sera prioritaire" >&2
fi
elif [[ -n "$body_file" ]]; then
[[ -f "$body_file" ]] || fail "Fichier introuvable: $body_file"
data=$(jq -n \
--arg title "$title" \
--rawfile body "$body_file" \
--arg labels_csv "$labels" \
--arg assignees_csv "$assignees" \
--arg state "$state" '
{}
+ ( ($title|length)>0 then {title:$title} else {} end )
+ {body:$body}
+ ( ($labels_csv|length)>0 then {labels: ($labels_csv|split(",")|map(select(.!="")))} else {} end )
+ ( ($assignees_csv|length)>0 then {assignees: ($assignees_csv|split(",")|map(select(.!="")))} else {} end )
+ ( ($state|length)>0 then {state:$state} else {} end )
')
else
data=$(jq -n \
--arg title "$title" '
{}
+ ( ($title|length)>0 then {title:$title} else {} end )
')
fi
api_jq_or_status -X PATCH "$BASE_URL/repos/$owner/$repo/issues/$index" --data-binary "$data"
}
cmd_close() {
local index="$1"; [[ -n "$index" ]] || fail "Index requis"
cmd_update "$index" -s closed
}
cmd_open() {
local index="$1"; [[ -n "$index" ]] || fail "Index requis"
cmd_update "$index" -s open
}
cmd_comment() {
local index message="" message_file=""
index="$1"; shift || true
[[ -n "$index" ]] || fail "Index requis"
while [[ $# -gt 0 ]]; do
case "$1" in
-m|--message) message="$2"; shift 2;;
-M|--message-file) message_file="$2"; shift 2;;
*) break;;
esac
done
# Inline prioritaire
if [[ -n "$message" ]]; then
read -r owner repo < <(resolve_repo)
local data
data=$(jq -n --arg body "$message" '{body:$body}')
if [[ -n "$message_file" ]]; then
echo "Avertissement: -m et -M fournis; -m (inline) sera prioritaire" >&2
fi
api_jq_or_status -X POST "$BASE_URL/repos/$owner/$repo/issues/$index/comments" --data-binary "$data"
return
elif [[ -n "$message_file" ]]; then
[[ -f "$message_file" ]] || fail "Fichier introuvable: $message_file"
read -r owner repo < <(resolve_repo)
local data
data=$(jq -n --rawfile body "$message_file" '{body:$body}')
api_jq_or_status -X POST "$BASE_URL/repos/$owner/$repo/issues/$index/comments" --data-binary "$data"
return
else
fail "Message requis (-m) ou fichier (-M)"
fi
}
main() {
require_tools
ensure_env
local args
args=( $(parse_common_flags "$@") ) || true
set +u
local cmd="${args[0]}"; shift || true
set -u
case "$cmd" in
create) shift; cmd_create "$@" ;;
list) shift; cmd_list "$@" ;;
get) shift; cmd_get "$1" ;;
update) shift; cmd_update "$@" ;;
close) shift; cmd_close "$1" ;;
open) shift; cmd_open "$1" ;;
comment) shift; cmd_comment "$@" ;;
-h|--help|help|""|*) usage; exit 1;;
esac
}
main "$@"

View File

@@ -70,9 +70,6 @@ lorem ipsum
We love contributions! Please read our [contributing guide](CONTRIBUTING.md) before submitting a pull request. If you'd like to self-host, refer to the [self-hosting guide](SELF_HOST.md).
_It is the sole responsibility of the end users to respect websites' policies when scraping, searching and crawling with Firecrawl. Users are advised to adhere to the applicable privacy policies and terms of use of the websites prior to initiating any scraping activities. By default, Firecrawl respects the directives specified in the websites' robots.txt files when crawling. By utilizing Firecrawl, you expressly agree to comply with these conditions._
## License Disclaimer
This project is primarily licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), as specified in the LICENSE file in the root directory of this repository. However, certain components of this project are licensed under the MIT License. Refer to the LICENSE files in these specific directories for details.

56
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

98
backend/README.md Normal file
View File

@@ -0,0 +1,98 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Project setup
```bash
$ pnpm install
```
## Compile and run the project
```bash
# development
$ pnpm run start
# watch mode
$ pnpm run start:dev
# production mode
$ pnpm run start:prod
```
## Run tests
```bash
# unit tests
$ pnpm run test
# e2e tests
$ pnpm run test:e2e
# test coverage
$ pnpm run test:cov
```
## Deployment
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
```bash
$ pnpm install -g @nestjs/mau
$ mau deploy
```
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
## Resources
Check out a few resources that may come in handy when working with NestJS:
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).

40
backend/biome.json Normal file
View File

@@ -0,0 +1,40 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true,
"includes": ["**", "!node_modules", "!dist", "!build"]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 1
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noUnknownAtRules": "off"
},
"style": {
"useImportType": "off"
}
},
"domains": {
"next": "recommended",
"react": "recommended"
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

8
backend/nest-cli.json Normal file
View File

@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

65
backend/package.json Normal file
View File

@@ -0,0 +1,65 @@
{
"name": "@memegoat/backend",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"lint": "biome check",
"format": "biome format --write",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^11.0.1",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^22.10.7",
"@types/supertest": "^6.0.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from "@nestjs/testing";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
describe("AppController", () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe("root", () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe("Hello World!");
});
});
});

View File

@@ -0,0 +1,12 @@
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}

10
backend/src/app.module.ts Normal file
View File

@@ -0,0 +1,10 @@
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@@ -0,0 +1,8 @@
import { Injectable } from "@nestjs/common";
@Injectable()
export class AppService {
getHello(): string {
return "Hello World!";
}
}

8
backend/src/main.ts Normal file
View File

@@ -0,0 +1,8 @@
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

View File

@@ -0,0 +1,25 @@
import { INestApplication } from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import request from "supertest";
import { App } from "supertest/types";
import { AppModule } from "../src/app.module";
describe("AppController (e2e)", () => {
let app: INestApplication<App>;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it("/ (GET)", () => {
return request(app.getHttpServer())
.get("/")
.expect(200)
.expect("Hello World!");
});
});

View File

@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

25
backend/tsconfig.json Normal file
View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"resolvePackageJsonExports": true,
"esModuleInterop": true,
"isolatedModules": true,
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2023",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false
}
}

View File

@@ -1,37 +1,42 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true,
"includes": ["**", "!node_modules", "!.next", "!dist", "!build"]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 2
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noUnknownAtRules": "off"
}
},
"domains": {
"next": "recommended",
"react": "recommended"
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true,
"includes": ["**", "!node_modules", "!.next", "!dist", "!build"]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 1
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noUnknownAtRules": "off"
}
},
"domains": {
"next": "recommended",
"react": "recommended"
}
},
"css": {
"parser": {
"tailwindDirectives": true
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

View File

@@ -1,8 +1,8 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
reactCompiler: true,
/* config options here */
reactCompiler: true,
};
export default nextConfig;

View File

@@ -1,27 +1,27 @@
{
"name": "@memegoat/frontend",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "biome check",
"format": "biome format --write"
},
"dependencies": {
"next": "16.1.1",
"react": "19.2.3",
"react-dom": "19.2.3"
},
"devDependencies": {
"@biomejs/biome": "2.2.0",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0",
"tailwindcss": "^4",
"typescript": "^5"
}
"name": "@memegoat/frontend",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "biome check",
"format": "biome format --write"
},
"dependencies": {
"next": "16.1.1",
"react": "19.2.3",
"react-dom": "19.2.3"
},
"devDependencies": {
"@biomejs/biome": "2.3.11",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0",
"tailwindcss": "^4",
"typescript": "^5"
}
}

View File

@@ -1,7 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;

View File

@@ -1,26 +1,26 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

View File

@@ -1,35 +1,35 @@
import type { Metadata } from "next";
import {Geist, Geist_Mono, Ubuntu_Mono, Ubuntu_Sans} from "next/font/google";
import { Ubuntu_Mono, Ubuntu_Sans } from "next/font/google";
import "./globals.css";
const ubuntuSans = Ubuntu_Sans({
variable: "--font-ubuntu-sans",
subsets: ["latin"],
variable: "--font-ubuntu-sans",
subsets: ["latin"],
});
const ubuntuMono = Ubuntu_Mono({
variable: "--font-geist-mono",
weight: ['400', '700'],
subsets: ["latin"],
variable: "--font-geist-mono",
weight: ["400", "700"],
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "MemeGoat",
icons: "/memegoat-color.svg"
title: "MemeGoat",
icons: "/memegoat-color.svg",
};
export default function RootLayout({
children,
children,
}: Readonly<{
children: React.ReactNode;
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${ubuntuSans.variable} ${ubuntuMono.variable} antialiased`}
>
{children}
</body>
</html>
);
return (
<html lang="en">
<body
className={`${ubuntuSans.variable} ${ubuntuMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}

View File

@@ -1,9 +1,9 @@
export default function Home() {
return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<p>Hello world !</p>
</main>
</div>
);
return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<p>Hello world !</p>
</main>
</div>
);
}

View File

@@ -1,34 +1,34 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
}

6280
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff