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
|
||||
# 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
|
||||
node_modules
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.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.*
|
||||
|
||||
.env
|
||||
.env.local
|
||||
|
|
|
|||
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