This commit is contained in:
Vakula Uladimir
2025-10-17 11:27:52 +03:00
commit 12f005e335
72 changed files with 14402 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
/*
|--------------------------------------------------------------------------
| Environment variables service
|--------------------------------------------------------------------------
|
| The `Env.create` method creates an instance of the Env service. The
| service validates the environment variables and also cast values
| to JavaScript data types.
|
*/
import { Env } from '@adonisjs/core/env'
export default await Env.create(new URL('../', import.meta.url), {
NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
HOST: Env.schema.string({ format: 'host' }),
LOG_LEVEL: Env.schema.string(),
/*
|----------------------------------------------------------
| Variables for configuring session package
|----------------------------------------------------------
*/
SESSION_DRIVER: Env.schema.enum(['cookie', 'memory'] as const),
/*
|----------------------------------------------------------
| Variables for configuring database connection
|----------------------------------------------------------
*/
DB_HOST: Env.schema.string({ format: 'host' }),
DB_PORT: Env.schema.number(),
DB_USER: Env.schema.string(),
DB_PASSWORD: Env.schema.string.optional(),
DB_DATABASE: Env.schema.string(),
/*
|----------------------------------------------------------
| Variables for Telegram bot
|----------------------------------------------------------
*/
TELEGRAM_BOT_TOKEN: Env.schema.string(),
})
+49
View File
@@ -0,0 +1,49 @@
/*
|--------------------------------------------------------------------------
| HTTP kernel file
|--------------------------------------------------------------------------
|
| The HTTP kernel file is used to register the middleware with the server
| or the router.
|
*/
import router from '@adonisjs/core/services/router'
import server from '@adonisjs/core/services/server'
/**
* The error handler is used to convert an exception
* to an HTTP response.
*/
server.errorHandler(() => import('#exceptions/handler'))
/**
* The server middleware stack runs middleware on all the HTTP
* requests, even if there is no route registered for
* the request URL.
*/
server.use([
() => import('#middleware/container_bindings_middleware'),
() => import('@adonisjs/static/static_middleware'),
() => import('@adonisjs/vite/vite_middleware'),
])
/**
* The router middleware stack runs middleware on all the HTTP
* requests with a registered route.
*/
router.use([
() => import('@adonisjs/core/bodyparser_middleware'),
() => import('@adonisjs/session/session_middleware'),
() => import('@adonisjs/shield/shield_middleware'),
() => import('@adonisjs/auth/initialize_auth_middleware')
])
/**
* Named middleware collection must be explicitly assigned to
* the routes or the routes group.
*/
export const middleware = router.named({
guest: () => import('#middleware/guest_middleware'),
auth: () => import('#middleware/auth_middleware')
})
+51
View File
@@ -0,0 +1,51 @@
/*
|--------------------------------------------------------------------------
| Routes file
|--------------------------------------------------------------------------
|
| The routes file is used for defining the HTTP routes.
|
*/
import router from '@adonisjs/core/services/router'
const KeywordsController = () => import('#controllers/keywords_controller')
const AuctionsController = () => import('#controllers/auctions_controller')
const ParseLogsController = () => import('#controllers/parse_logs_controller')
router.on('/').render('pages/home')
// Health check endpoint for Docker
router.get('/health', async ({ response }) => {
return response.ok({ status: 'ok', timestamp: new Date().toISOString() })
})
// List recent auctions (last 3 days)
router.get('/list', [AuctionsController, 'list'])
// HTML view of recent auctions (last 3 days)
router.get('/list-view', [AuctionsController, 'listView'])
// Trigger auction parsing
router.post('/trigger-parse', [AuctionsController, 'triggerParse'])
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
*/
router.group(() => {
// Keywords
router.get('/keywords', [KeywordsController, 'index'])
router.post('/keywords', [KeywordsController, 'store'])
router.delete('/keywords/:id', [KeywordsController, 'destroy'])
// Auctions
router.get('/auctions/search', [AuctionsController, 'search'])
router.get('/auctions/:id', [AuctionsController, 'show'])
router.get('/auctions', [AuctionsController, 'index'])
// Parse Logs
router.get('/parse-logs', [ParseLogsController, 'index'])
}).prefix('/api')
+56
View File
@@ -0,0 +1,56 @@
/**
* Scheduler configuration
*
* This file defines all scheduled tasks for the application.
* The scheduler runs in a separate process using: node ace scheduler:work
*
* Learn more: https://github.com/kabbouchi/adonisjs-scheduler
*/
import scheduler from 'adonisjs-scheduler/services/main'
import ParseAuctions from '../commands/parse_auctions.js'
/**
* Schedule auction parsing every 6 hours
*
* This task will:
* 1. Scrape auctions from icetrade.by
* 2. Upsert them to the database
* 3. Create notifications for keyword matches
*
* Configuration:
* - Runs every 6 hours
* - Prevents overlapping executions
* - Scrapes 1 page by default (can be adjusted)
*/
scheduler
.command(ParseAuctions, ['--pages=1'])
.everySixHours()
.withoutOverlapping()
/**
* Alternative schedule options (uncomment to use):
*
* Run every hour:
*/
// scheduler.command(ParseAuctions).everyHour().withoutOverlapping()
/**
* Run every 3 hours using cron:
*/
// scheduler.command(ParseAuctions).cron('0 *\/3 * * *').withoutOverlapping()
/**
* Run at specific times (6am, 12pm, 6pm, 12am):
*/
// scheduler.command(ParseAuctions).cron('0 6,12,18,0 * * *').withoutOverlapping()
/**
* Run every 30 minutes:
*/
// scheduler.command(ParseAuctions).everyThirtyMinutes().withoutOverlapping()
/**
* Run daily at 3am:
*/
// scheduler.command(ParseAuctions).dailyAt('3:00').withoutOverlapping()