Compare commits

...

4 commits

Author SHA1 Message Date
Nathan Lamy
0164870be5 chore: add ci
Some checks are pending
Build and Publish Docker Image / docker (push) Waiting to run
2025-08-21 16:06:54 +02:00
Nathan Lamy
f39a0be9f0 feat: add bearer auth 2025-08-21 13:45:17 +02:00
Nathan Lamy
e8ca067ae8 feat: add relatives 2025-08-21 13:34:56 +02:00
Nathan Lamy
9061da4f09 chore: reverse last commit 2025-08-21 12:27:38 +02:00
9 changed files with 138 additions and 8260 deletions

View file

@ -0,0 +1,42 @@
name: Build and Publish Docker Image
on:
push:
branches: ["main"]
jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

30
Dockerfile Normal file
View file

@ -0,0 +1,30 @@
ARG NODE_IMAGE=node:22-slim
###### First Stage - Creating base ######
FROM $NODE_IMAGE as base
RUN mkdir -p /home/node/app && chown node:node /home/node/app
RUN npm install --global pnpm
WORKDIR /home/node/app
USER node
RUN mkdir tmp
###### Second Stage - Installing dependencies ######
FROM base AS dependencies
COPY --chown=node:node ./package*.json ./
RUN pnpm install
COPY --chown=node:node . .
###### Third Stage - Building Stage ######
FROM dependencies AS build
RUN node ace build
###### Final Stage - Production ######
FROM base as production
ENV NODE_ENV=production
ENV PORT=3333
ENV HOST=0.0.0.0
COPY --chown=node:node ./package.json ./
COPY --chown=node:node ./pnpm-lock.yaml ./
RUN pnpm install --prod
COPY --chown=node:node --from=build /home/node/app/build .
EXPOSE 3333

View file

@ -53,12 +53,22 @@ export default class CollesController {
return response.notFound({ message: 'Colle not found' })
}
const relatives = await Colle.query()
.where('subjectId', colle.subjectId)
.where('date', colle.date.toISO()!)
.where('id', '!=', colle.id) // Exclude the current colle
.whereHas('student', (query) => {
query.where('className', auth.user!.className)
})
.preload('student')
return {
success: true,
data: {
...colle.serialize(),
bjid: colle.bjid,
bjsecret: colle.bjsecret,
relatives,
},
}
}

View file

@ -0,0 +1,47 @@
import env from '#start/env'
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
export default class BearerTokenAuthMiddleware {
async handle({ response, request }: HttpContext, next: NextFn) {
// Get the authorization header
const authHeader = request.header('authorization')
if (!authHeader) {
return response.status(401).json({
error: 'Authorization header is required',
})
}
// Check if it's a Bearer token
if (!authHeader.startsWith('Bearer ')) {
return response.status(401).json({
error: 'Authorization header must be a Bearer token',
})
}
// Extract the token
const token = authHeader.substring(7) // Remove 'Bearer ' prefix
if (!token) {
return response.status(401).json({
error: 'Token is required',
})
}
// Get the valid token from environment
const validToken = env.get('API_BEARER_TOKEN')
if (!validToken) {
return response.status(500).json({
error: 'Server configuration error: API token not configured',
})
}
// Validate the token
if (token !== validToken) {
return response.status(401).json({
error: 'Invalid token',
})
}
await next()
}
}

View file

@ -5,7 +5,7 @@ import { DateTime } from 'luxon'
import { UAParser } from 'ua-parser-js'
import webpush, { PushSubscription } from 'web-push'
const MAX_FAILED_ATTEMPTS = 1
const MAX_FAILED_ATTEMPTS = 5
export const EVENTS = {
SYSTEM: 1 << 0,
@ -41,7 +41,6 @@ export class NotificationService {
}
private pushNotification(subscription: PushSubscription, payload: Record<string, any>) {
console.log(payload)
return webpush.sendNotification(subscription, JSON.stringify(payload))
}

8239
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -71,4 +71,6 @@ export default await Env.create(new URL('../', import.meta.url), {
VAPID_DETAILS: Env.schema.string(),
VAPID_PUBLIC_KEY: Env.schema.string(),
VAPID_PRIVATE_KEY: Env.schema.string(),
API_BEARER_TOKEN: Env.schema.string(),
})

View file

@ -36,5 +36,6 @@ router.use([() => import('@adonisjs/core/bodyparser_middleware'), () => import('
*/
export const middleware = router.named({
guest: () => import('#middleware/guest_middleware'),
auth: () => import('#middleware/auth_middleware')
auth: () => import('#middleware/auth_middleware'),
internal: () => import('#middleware/bearer_token_auth_middleware'),
})

View file

@ -12,7 +12,6 @@ import router from '@adonisjs/core/services/router'
import { authThrottle } from './limiters.js'
import { throttle } from './limiter.js'
import { middleware } from './kernel.js'
import redis from '@adonisjs/redis/services/main'
// TODO: Magic link login
// transmit.registerRoutes()
@ -40,9 +39,8 @@ router.get('/subjects', [SubjectsController, 'index']).use(middleware.auth())
const CollesController = () => import('#controllers/colles_controller')
router
.group(() => {
// TODO: PRIVATE ROUTES
router.post('/', [CollesController, 'create'])
router.post('/upcoming', [CollesController, 'createUpcoming'])
router.post('/', [CollesController, 'create']).use(middleware.internal())
router.post('/upcoming', [CollesController, 'createUpcoming']).use(middleware.internal())
router.post('/:colleId/refresh', [CollesController, 'refresh']).use(middleware.auth())
router.get('/', [CollesController, 'index']).use(middleware.auth())
router.get('/:colleId', [CollesController, 'show']).use(middleware.auth())
@ -56,7 +54,7 @@ router
router.post('/', [NotificationsController, 'subscribe'])
router.post('/:id/unsubscribe', [NotificationsController, 'unsubscribe'])
router.post('/:id', [NotificationsController, 'update'])
router.post('/:id/test', [NotificationsController, 'test']).use(middleware.auth())
router.post('/:id/test', [NotificationsController, 'test'])
})
.prefix('/notifications')
.use(middleware.auth())
@ -71,16 +69,4 @@ router
router.post('/back-fetch', [InternalsController, 'backFetch'])
})
.prefix('/internals')
// TODO: Token authentication
router.get('/', async () => {
await redis.publish(
'jobs_queue',
JSON.stringify({
type: 1, // Fetch day colles
// Format DD/MM/YYYY
date: '06/06/2025',
class_name: 'MPSI 2',
})
)
})
.use(middleware.internal())