omgebouwd naar adonisjs, werkt nog niet echt goed
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import User from '#models/user'
|
||||
import { loginValidator } from '#validators/user'
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import UserTransformer from '#transformers/user_transformer'
|
||||
|
||||
export default class AccessTokensController {
|
||||
async store({ request, serialize }: HttpContext) {
|
||||
const { email, password } = await request.validateUsing(loginValidator)
|
||||
|
||||
const user = await User.verifyCredentials(email, password)
|
||||
const token = await User.accessTokens.create(user)
|
||||
|
||||
return serialize({
|
||||
user: UserTransformer.transform(user),
|
||||
token: token.value!.release(),
|
||||
})
|
||||
}
|
||||
|
||||
async destroy({ auth }: HttpContext) {
|
||||
const user = auth.getUserOrFail()
|
||||
if (user.currentAccessToken) {
|
||||
await User.accessTokens.delete(user, user.currentAccessToken.identifier)
|
||||
}
|
||||
|
||||
return {
|
||||
message: 'Logged out successfully',
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import User from '#models/user'
|
||||
import { signupValidator } from '#validators/user'
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import UserTransformer from '#transformers/user_transformer'
|
||||
|
||||
export default class NewAccountController {
|
||||
async store({ request, serialize }: HttpContext) {
|
||||
const { fullName, email, password } = await request.validateUsing(signupValidator)
|
||||
|
||||
const user = await User.create({ fullName, email, password })
|
||||
const token = await User.accessTokens.create(user)
|
||||
|
||||
return serialize({
|
||||
user: UserTransformer.transform(user),
|
||||
token: token.value!.release(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import UserTransformer from '#transformers/user_transformer'
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
|
||||
export default class ProfileController {
|
||||
async show({ auth, serialize }: HttpContext) {
|
||||
return serialize(UserTransformer.transform(auth.getUserOrFail()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import app from '@adonisjs/core/services/app'
|
||||
import { type HttpContext, ExceptionHandler } from '@adonisjs/core/http'
|
||||
|
||||
export default class HttpExceptionHandler extends ExceptionHandler {
|
||||
/**
|
||||
* In debug mode, the exception handler will display verbose errors
|
||||
* with pretty printed stack traces.
|
||||
*/
|
||||
protected debug = !app.inProduction
|
||||
|
||||
/**
|
||||
* The method is used for handling errors and returning
|
||||
* response to the client
|
||||
*/
|
||||
async handle(error: unknown, ctx: HttpContext) {
|
||||
return super.handle(error, ctx)
|
||||
}
|
||||
|
||||
/**
|
||||
* The method is used to report error to the logging service or
|
||||
* the a third party error monitoring service.
|
||||
*
|
||||
* @note You should not attempt to send a response from this method.
|
||||
*/
|
||||
async report(error: unknown, ctx: HttpContext) {
|
||||
return super.report(error, ctx)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import type { NextFn } from '@adonisjs/core/types/http'
|
||||
import type { Authenticators } from '@adonisjs/auth/types'
|
||||
|
||||
/**
|
||||
* Auth middleware is used authenticate HTTP requests and deny
|
||||
* access to unauthenticated users.
|
||||
*/
|
||||
export default class AuthMiddleware {
|
||||
async handle(
|
||||
ctx: HttpContext,
|
||||
next: NextFn,
|
||||
options: {
|
||||
guards?: (keyof Authenticators)[]
|
||||
} = {}
|
||||
) {
|
||||
await ctx.auth.authenticateUsing(options.guards)
|
||||
return next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Logger } from '@adonisjs/core/logger'
|
||||
import { HttpContext } from '@adonisjs/core/http'
|
||||
import { type NextFn } from '@adonisjs/core/types/http'
|
||||
|
||||
/**
|
||||
* The container bindings middleware binds classes to their request
|
||||
* specific value using the container resolver.
|
||||
*
|
||||
* - We bind "HttpContext" class to the "ctx" object
|
||||
* - And bind "Logger" class to the "ctx.logger" object
|
||||
*/
|
||||
export default class ContainerBindingsMiddleware {
|
||||
handle(ctx: HttpContext, next: NextFn) {
|
||||
ctx.containerResolver.bindValue(HttpContext, ctx)
|
||||
ctx.containerResolver.bindValue(Logger, ctx.logger)
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import type { NextFn } from '@adonisjs/core/types/http'
|
||||
|
||||
export default class ForceJsonResponseMiddleware {
|
||||
handle(ctx: HttpContext, next: NextFn) {
|
||||
ctx.request.request.headers.accept = 'application/json'
|
||||
return next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import type { NextFn } from '@adonisjs/core/types/http'
|
||||
|
||||
/**
|
||||
* Silent auth middleware can be used as a global middleware to silent check
|
||||
* if the user is logged-in or not.
|
||||
*
|
||||
* The request continues as usual, even when the user is not logged-in.
|
||||
*/
|
||||
export default class SilentAuthMiddleware {
|
||||
async handle(ctx: HttpContext, next: NextFn) {
|
||||
await ctx.auth.check()
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { BaseModel as AdonisBaseModel } from '@adonisjs/lucid/orm'
|
||||
|
||||
export default class BaseModel extends AdonisBaseModel {
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { DateTime } from 'luxon'
|
||||
import { column } from '@adonisjs/lucid/orm'
|
||||
import BaseModel from '#models/base_model'
|
||||
|
||||
export default class File extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
declare id: number
|
||||
|
||||
@column()
|
||||
declare filename: string
|
||||
|
||||
@column()
|
||||
declare poster: string
|
||||
|
||||
@column()
|
||||
declare date: number
|
||||
|
||||
@column()
|
||||
declare parts: number
|
||||
|
||||
@column({ columnName: 'message_ids' })
|
||||
declare messageIds: Record<string, any>
|
||||
|
||||
@column()
|
||||
declare groups: string[]
|
||||
|
||||
@column.dateTime({ autoCreate: true })
|
||||
declare createdAt: DateTime
|
||||
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||
declare updatedAt: DateTime
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { DateTime } from 'luxon'
|
||||
import { column } from '@adonisjs/lucid/orm'
|
||||
import BaseModel from '#models/base_model'
|
||||
|
||||
export default class Group extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
declare id: number
|
||||
|
||||
@column()
|
||||
declare name: string
|
||||
|
||||
@column()
|
||||
declare active: boolean
|
||||
|
||||
@column({ columnName: 'last_indexed_id' })
|
||||
declare lastIndexedId: bigint
|
||||
|
||||
@column.dateTime({ autoCreate: true })
|
||||
declare createdAt: DateTime
|
||||
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||
declare updatedAt: DateTime
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { UserSchema } from '#database/schema'
|
||||
import hash from '@adonisjs/core/services/hash'
|
||||
import { compose } from '@adonisjs/core/helpers'
|
||||
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid'
|
||||
import { type AccessToken, DbAccessTokensProvider } from '@adonisjs/auth/access_tokens'
|
||||
|
||||
export default class User extends compose(UserSchema, withAuthFinder(hash)) {
|
||||
static accessTokens = DbAccessTokensProvider.forModel(User)
|
||||
declare currentAccessToken?: AccessToken
|
||||
|
||||
get initials() {
|
||||
const [first, last] = this.fullName ? this.fullName.split(' ') : this.email.split('@')
|
||||
if (first && last) {
|
||||
return `${first.charAt(0)}${last.charAt(0)}`.toUpperCase()
|
||||
}
|
||||
return `${first.slice(0, 2)}`.toUpperCase()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import nntpConfig from '#config/nntp'
|
||||
import { NNTP } from "nntp-js";
|
||||
|
||||
class NntpService {
|
||||
private readonly poolSize: number;
|
||||
private allConnections: Set<any>;
|
||||
private idleConnections: any[];
|
||||
private waiters: ((conn: any) => void)[];
|
||||
private createdCount: number;
|
||||
|
||||
constructor(poolSize = 10) {
|
||||
this.poolSize = poolSize;
|
||||
this.allConnections = new Set();
|
||||
this.idleConnections = [];
|
||||
this.waiters = [];
|
||||
this.createdCount = 0;
|
||||
console.log(`NNTP Pool initialized with size ${this.poolSize}`)
|
||||
}
|
||||
|
||||
private async _createConnection() {
|
||||
// This connection logic is based on the older, working pool implementation.
|
||||
const conn = new NNTP(nntpConfig.host, nntpConfig.port);
|
||||
await conn.connect();
|
||||
if (nntpConfig.user) {
|
||||
await conn.login(nntpConfig.user, nntpConfig.password?.release());
|
||||
}
|
||||
this.allConnections.add(conn);
|
||||
return conn;
|
||||
}
|
||||
|
||||
public async acquire() {
|
||||
if (this.idleConnections.length > 0) {
|
||||
console.log('Reusing existing connection from pool.');
|
||||
return this.idleConnections.pop();
|
||||
}
|
||||
|
||||
if (this.createdCount < this.poolSize) {
|
||||
this.createdCount++;
|
||||
console.log(`Creating new connection (${this.createdCount}/${this.poolSize}).`);
|
||||
return this._createConnection();
|
||||
}
|
||||
|
||||
console.log(`Pool maxed out at ${this.poolSize}. Waiting for a connection to become available.`);
|
||||
return new Promise(resolve => this.waiters.push(resolve));
|
||||
}
|
||||
|
||||
public release(conn: any) {
|
||||
if (this.waiters.length > 0) {
|
||||
console.log('Releasing connection directly to a waiting task.');
|
||||
const resolve = this.waiters.shift();
|
||||
if(resolve) resolve(conn);
|
||||
} else {
|
||||
console.log('Returning connection to the idle pool.');
|
||||
this.idleConnections.push(conn);
|
||||
}
|
||||
}
|
||||
|
||||
public async shutdown() {
|
||||
console.log('Shutting down all connections in the pool.');
|
||||
const shutdownPromises: Promise<any>[] = [];
|
||||
for (const conn of this.allConnections) {
|
||||
shutdownPromises.push(conn.quit());
|
||||
}
|
||||
await Promise.all(shutdownPromises);
|
||||
this.allConnections.clear();
|
||||
this.idleConnections.length = 0;
|
||||
this.waiters.length = 0;
|
||||
this.createdCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export default new NntpService();
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Queue } from 'bullmq'
|
||||
import queueConfig from '#config/queue'
|
||||
|
||||
class QueueService {
|
||||
public readonly nntpFetchQueue: Queue
|
||||
public readonly headerQueue: Queue
|
||||
public readonly fileQueue: Queue
|
||||
public readonly bodyQueue: Queue
|
||||
public readonly collectionQueue: Queue
|
||||
|
||||
constructor() {
|
||||
this.nntpFetchQueue = new Queue('nntp-fetch-queue', { connection: queueConfig.connection })
|
||||
this.headerQueue = new Queue('header-queue', { connection: queueConfig.connection })
|
||||
this.fileQueue = new Queue('file-queue', { connection: queueConfig.connection })
|
||||
this.bodyQueue = new Queue('body-queue', { connection: queueConfig.connection })
|
||||
this.collectionQueue = new Queue('collection-queue', { connection: queueConfig.connection })
|
||||
}
|
||||
|
||||
async closeAll() {
|
||||
await Promise.all([
|
||||
this.nntpFetchQueue.close(),
|
||||
this.headerQueue.close(),
|
||||
this.fileQueue.close(),
|
||||
this.bodyQueue.close(),
|
||||
this.collectionQueue.close(),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
export default new QueueService()
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createRequire } from 'module'
|
||||
import redisConfig from '#config/redis'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const IORedis = require('ioredis')
|
||||
|
||||
class RedisService {
|
||||
public readonly client: any
|
||||
|
||||
constructor() {
|
||||
this.client = new IORedis(redisConfig.connections.main)
|
||||
}
|
||||
}
|
||||
|
||||
export default new RedisService()
|
||||
@@ -0,0 +1,19 @@
|
||||
import { decode } from 'simple-yenc'
|
||||
|
||||
export class YencFile {
|
||||
private buffer: Buffer | null = null
|
||||
|
||||
public processPart(partBuffer: Buffer) {
|
||||
// This is a simplified implementation.
|
||||
// simple-yenc's decode function is synchronous and works on a full buffer.
|
||||
// A more complex implementation would handle multi-part decoding.
|
||||
this.buffer = decode(partBuffer)
|
||||
}
|
||||
|
||||
public getBuffer(): Buffer {
|
||||
if (!this.buffer) {
|
||||
throw new Error('No data has been processed yet.')
|
||||
}
|
||||
return this.buffer
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
export function parseYencMeta(buffer: Buffer): { header: Record<string, any>; crc32?: string } {
|
||||
const text = buffer.toString('latin1')
|
||||
const lines = text.split(/\\r?\\n/)
|
||||
|
||||
const header: Record<string, any> = {}
|
||||
let crc32: string | undefined
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('=ybegin')) {
|
||||
const parts = line.split(' ')
|
||||
parts.forEach((part) => {
|
||||
if (part.includes('=')) {
|
||||
const [key, value] = part.split('=')
|
||||
if (key === 'name') {
|
||||
header[key] = value.trim()
|
||||
} else {
|
||||
header[key] = parseInt(value, 10)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (line.startsWith('=ypart')) {
|
||||
const match = /begin=(\d+)/.exec(line)
|
||||
if (match) {
|
||||
header.partBegin = parseInt(match[1], 10)
|
||||
}
|
||||
} else if (line.startsWith('=yend')) {
|
||||
const match = /crc32=([a-fA-F0-9]+)/.exec(line)
|
||||
if (match) {
|
||||
crc32 = match[1]
|
||||
}
|
||||
break // End of yenc data
|
||||
}
|
||||
}
|
||||
|
||||
return { header, crc32 }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type User from '#models/user'
|
||||
import { BaseTransformer } from '@adonisjs/core/transformers'
|
||||
|
||||
export default class UserTransformer extends BaseTransformer<User> {
|
||||
toObject() {
|
||||
return this.pick(this.resource, [
|
||||
'id',
|
||||
'fullName',
|
||||
'email',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'initials',
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import vine from '@vinejs/vine'
|
||||
|
||||
/**
|
||||
* Shared rules for email and password.
|
||||
*/
|
||||
const email = () => vine.string().email().maxLength(254)
|
||||
const password = () => vine.string().minLength(8).maxLength(32)
|
||||
|
||||
/**
|
||||
* Validator to use when performing self-signup
|
||||
*/
|
||||
export const signupValidator = vine.create({
|
||||
fullName: vine.string().nullable(),
|
||||
email: email().unique({ table: 'users', column: 'email' }),
|
||||
password: password(),
|
||||
passwordConfirmation: password().sameAs('password'),
|
||||
})
|
||||
|
||||
/**
|
||||
* Validator to use before validating user credentials
|
||||
* during login
|
||||
*/
|
||||
export const loginValidator = vine.create({
|
||||
email: email(),
|
||||
password: vine.string(),
|
||||
})
|
||||
Reference in New Issue
Block a user