feat: initialisation projet Hono.js + TypeScript + Vitest
This commit is contained in:
parent
b06970c9ae
commit
708517edef
13 changed files with 3125 additions and 131 deletions
19
.env.example
Normal file
19
.env.example
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Supabase
|
||||||
|
SUPABASE_URL=https://xxx.supabase.co
|
||||||
|
SUPABASE_SERVICE_ROLE_KEY=xxx
|
||||||
|
|
||||||
|
# APIs
|
||||||
|
DEEPSEEK_API_KEY=xxx
|
||||||
|
GEMINI_API_KEY=xxx
|
||||||
|
|
||||||
|
# Stripe
|
||||||
|
STRIPE_SECRET_KEY=xxx
|
||||||
|
STRIPE_WEBHOOK_SECRET=xxx
|
||||||
|
STRIPE_PRICE_STANDARD=price_xxx
|
||||||
|
STRIPE_PRICE_PREMIUM=price_xxx
|
||||||
|
|
||||||
|
# Config
|
||||||
|
PORT=3000
|
||||||
|
APP_URL=https://expria.app
|
||||||
|
API_URL=https://api.expria.app
|
||||||
|
NODE_ENV=production
|
||||||
134
.gitignore
vendored
134
.gitignore
vendored
|
|
@ -1,132 +1,4 @@
|
||||||
# ---> Node
|
node_modules
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
dist
|
||||||
|
.env
|
||||||
# Gatsby files
|
.env.local
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# vuepress v2.x temp and cache directory
|
|
||||||
.temp
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
|
||||||
.docusaurus
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
|
|
|
||||||
2957
package-lock.json
generated
Normal file
2957
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
26
package.json
Normal file
26
package.json
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "expria-backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx watch src/index.ts",
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:coverage": "vitest run --coverage"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hono/node-server": "^1.13.7",
|
||||||
|
"@supabase/supabase-js": "^2.49.4",
|
||||||
|
"hono": "^4.7.7",
|
||||||
|
"stripe": "^17.7.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.15.3",
|
||||||
|
"@vitest/coverage-v8": "^3.1.2",
|
||||||
|
"tsx": "^4.19.3",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"vitest": "^3.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/controllers/.gitkeep
Normal file
0
src/controllers/.gitkeep
Normal file
14
src/index.ts
Normal file
14
src/index.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { serve } from '@hono/node-server'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
app.get('/', (c) => {
|
||||||
|
return c.json({ message: 'Expria API — OK' }, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
const port = Number(process.env.PORT) || 3000
|
||||||
|
|
||||||
|
serve({ fetch: app.fetch, port }, () => {
|
||||||
|
console.log(`Expria API listening on port ${port}`)
|
||||||
|
})
|
||||||
0
src/lib/__tests__/.gitkeep
Normal file
0
src/lib/__tests__/.gitkeep
Normal file
74
src/lib/access.ts
Normal file
74
src/lib/access.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
export type Plan = 'free' | 'standard' | 'premium'
|
||||||
|
|
||||||
|
export type Feature =
|
||||||
|
| 'oral_t2_live'
|
||||||
|
| 'detailed_report'
|
||||||
|
| 'tips'
|
||||||
|
| 'dashboard'
|
||||||
|
| 'exam_mode'
|
||||||
|
| 'pattern_analysis'
|
||||||
|
| 'preparation_index'
|
||||||
|
| 'basic_report'
|
||||||
|
|
||||||
|
export const PLANS = {
|
||||||
|
free: {
|
||||||
|
simulations_lifetime: 5,
|
||||||
|
oral_t2_live: false,
|
||||||
|
detailed_report: false,
|
||||||
|
tips: false,
|
||||||
|
dashboard: false,
|
||||||
|
exam_mode: false,
|
||||||
|
pattern_analysis: false,
|
||||||
|
preparation_index: false,
|
||||||
|
},
|
||||||
|
standard: {
|
||||||
|
simulations_lifetime: null,
|
||||||
|
oral_t2_live: false,
|
||||||
|
detailed_report: true,
|
||||||
|
tips: true,
|
||||||
|
dashboard: true,
|
||||||
|
exam_mode: false,
|
||||||
|
pattern_analysis: false,
|
||||||
|
preparation_index: false,
|
||||||
|
},
|
||||||
|
premium: {
|
||||||
|
simulations_lifetime: null,
|
||||||
|
oral_t2_live: true,
|
||||||
|
detailed_report: true,
|
||||||
|
tips: true,
|
||||||
|
dashboard: true,
|
||||||
|
exam_mode: true,
|
||||||
|
pattern_analysis: true,
|
||||||
|
preparation_index: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPlanPermissions(plan: Plan) {
|
||||||
|
const perms = PLANS[plan]
|
||||||
|
if (!perms) {
|
||||||
|
throw new Error(`Plan inconnu : ${plan}`)
|
||||||
|
}
|
||||||
|
return perms
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canUserSimulate(user: { plan: string; simulations_used: number }): {
|
||||||
|
allowed: boolean
|
||||||
|
reason?: string
|
||||||
|
} {
|
||||||
|
if (!(user.plan in PLANS)) {
|
||||||
|
return { allowed: false, reason: 'invalid_plan' }
|
||||||
|
}
|
||||||
|
const plan = user.plan as Plan
|
||||||
|
const perms = PLANS[plan]
|
||||||
|
if (perms.simulations_lifetime !== null && user.simulations_used >= perms.simulations_lifetime) {
|
||||||
|
return { allowed: false, reason: 'quota_reached' }
|
||||||
|
}
|
||||||
|
return { allowed: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkFeatureAccess(plan: Plan, feature: Feature): boolean {
|
||||||
|
if (feature === 'basic_report') return true
|
||||||
|
const perms = PLANS[plan]
|
||||||
|
if (!perms) return false
|
||||||
|
return perms[feature as keyof typeof perms] === true
|
||||||
|
}
|
||||||
0
src/middleware/.gitkeep
Normal file
0
src/middleware/.gitkeep
Normal file
0
src/routes/.gitkeep
Normal file
0
src/routes/.gitkeep
Normal file
0
src/types/.gitkeep
Normal file
0
src/types/.gitkeep
Normal file
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
12
vitest.config.ts
Normal file
12
vitest.config.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'node',
|
||||||
|
globals: true,
|
||||||
|
coverage: {
|
||||||
|
reporter: ['text', 'html'],
|
||||||
|
include: ['src/lib/**', 'src/controllers/**'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue