Initial commit: PC Builder AdonisJS project
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import { storeComponentValidator } from '#validators/Components/store'
|
||||
import { updateComponentValidator } from '#validators/Components/update'
|
||||
import Component from '#models/component'
|
||||
import ComponentType from '#models/component_type'
|
||||
|
||||
export default class ComponentsController {
|
||||
async index({ inertia, auth }: HttpContext) {
|
||||
const user = await auth.authenticate()
|
||||
const components = await Component.query()
|
||||
.preload('type')
|
||||
.where('userId', user.id)
|
||||
return inertia.render('components/index', { components })
|
||||
}
|
||||
|
||||
async create({ inertia }: HttpContext) {
|
||||
const types = await ComponentType.all()
|
||||
return inertia.render('components/create', { types })
|
||||
}
|
||||
|
||||
async store({ request, response, auth }: HttpContext) {
|
||||
const data = request.all()
|
||||
const user = await auth.authenticate()
|
||||
const { quantity, ...componentData } = await storeComponentValidator.validate(data)
|
||||
await ComponentType.findOrFail(componentData.typeId)
|
||||
for (let i = 0; i < quantity; i++) {
|
||||
await Component.create({
|
||||
...componentData,
|
||||
userId: user.id,
|
||||
})
|
||||
}
|
||||
response.redirect('/components')
|
||||
}
|
||||
|
||||
async show({ params, auth, inertia }: HttpContext) {
|
||||
const user = await auth.authenticate()
|
||||
const componentId = params.id
|
||||
const component = await Component.query()
|
||||
.preload('type')
|
||||
.where('userId', user.id)
|
||||
.andWhere('id', componentId)
|
||||
.firstOrFail()
|
||||
return inertia.render('components/show', { component })
|
||||
}
|
||||
|
||||
async edit({ params, auth, inertia }: HttpContext) {
|
||||
const user = await auth.authenticate()
|
||||
const types = await ComponentType.all()
|
||||
const componentId = params.id
|
||||
const component = await Component.query()
|
||||
.preload('type')
|
||||
.where('userId', user.id)
|
||||
.andWhere('id', componentId)
|
||||
.firstOrFail()
|
||||
return inertia.render('components/edit', { component, types })
|
||||
}
|
||||
|
||||
async update({ params, auth, request, response }: HttpContext) {
|
||||
const data = request.all()
|
||||
const componentData = await updateComponentValidator.validate(data)
|
||||
const user = await auth.authenticate()
|
||||
const component = await Component.query()
|
||||
.where('userId', user.id)
|
||||
.andWhere('id', params.id)
|
||||
.firstOrFail()
|
||||
component.merge(componentData)
|
||||
await component.save()
|
||||
return response.redirect('/components')
|
||||
}
|
||||
|
||||
async destroy({ params, response }: HttpContext) {
|
||||
const componentId = params.id
|
||||
const component = await Component.findOrFail(componentId)
|
||||
await component.delete()
|
||||
return response.redirect('/components')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import Computer from '#models/computer'
|
||||
import ComputerState from '#models/computer_state'
|
||||
import Component from '#models/component'
|
||||
import { storeComputerValidator } from '#validators/Computers/store'
|
||||
import { handleComponents } from '#services/component_service'
|
||||
import { handlePictures } from '#services/picture_service'
|
||||
import type { Image } from '#types/UItypes'
|
||||
import { updateComputerValidator } from '#validators/Computers/update'
|
||||
|
||||
export default class ComputersController {
|
||||
async index({ auth, inertia }: HttpContext) {
|
||||
const user = await auth.authenticate()
|
||||
const computers = await Computer.query()
|
||||
.where('user_id', user.id)
|
||||
.preload('state')
|
||||
.preload('components')
|
||||
.preload('pictures', (picturesQuery) => {
|
||||
picturesQuery.where('order', 1)
|
||||
})
|
||||
|
||||
return inertia.render('computers/index', { computers })
|
||||
}
|
||||
|
||||
async create({ auth, inertia }: HttpContext) {
|
||||
const user = await auth.authenticate()
|
||||
const states = await ComputerState.all()
|
||||
const components = await Component.query()
|
||||
.where('userId', user.id)
|
||||
.whereNull('computerId')
|
||||
.preload('type')
|
||||
return inertia.render('computers/create', { components, states })
|
||||
}
|
||||
|
||||
async store({ auth, request, response }: HttpContext) {
|
||||
const data = request.all()
|
||||
const user = await auth.authenticate()
|
||||
const { pictures, componentsId, ...computerData } = await storeComputerValidator.validate(data)
|
||||
await ComputerState.findOrFail(computerData.stateId)
|
||||
const computer = await Computer.create({
|
||||
...computerData,
|
||||
userId: user.id,
|
||||
})
|
||||
await handleComponents(componentsId as number[], computer)
|
||||
await handlePictures(pictures as Image[], request, computer)
|
||||
return response.redirect('computers/')
|
||||
}
|
||||
|
||||
async show({ auth, inertia, params }: HttpContext) {
|
||||
const user = await auth.authenticate()
|
||||
const computerId = params.id
|
||||
const computer = await Computer.query()
|
||||
.preload('state')
|
||||
.preload('components', (componentsQuery) => {
|
||||
componentsQuery.preload('type')
|
||||
})
|
||||
.preload('pictures', (picturesQuery) => {
|
||||
picturesQuery.orderBy('order')
|
||||
})
|
||||
.where('user_id', user.id)
|
||||
.andWhere('id', computerId)
|
||||
.firstOrFail()
|
||||
return inertia.render('computers/show', { computer })
|
||||
}
|
||||
|
||||
async edit({ params, inertia, auth }: HttpContext) {
|
||||
const user = await auth.authenticate()
|
||||
const states = await ComputerState.all()
|
||||
const computerId = params.id
|
||||
|
||||
const computer = await Computer.query()
|
||||
.preload('state')
|
||||
.preload('components', (componentsQuery) => {
|
||||
componentsQuery.preload('type')
|
||||
})
|
||||
.preload('pictures')
|
||||
.where('user_id', user.id)
|
||||
.andWhere('id', computerId)
|
||||
.firstOrFail()
|
||||
|
||||
const availableComponents = await Component.query()
|
||||
.preload('type')
|
||||
.where('user_id', user.id)
|
||||
.whereNull('computer_id')
|
||||
.whereNotIn(
|
||||
'id',
|
||||
computer.components.map((c) => c.id)
|
||||
)
|
||||
return inertia.render('computers/edit', { computer, availableComponents, states })
|
||||
}
|
||||
|
||||
async update({ params, request, response }: HttpContext) {
|
||||
const computerId = params.id
|
||||
const { pictures, componentsId, ...computerData } = await updateComputerValidator.validate(
|
||||
request.all()
|
||||
)
|
||||
const computer = await Computer.findOrFail(computerId)
|
||||
computer.merge(computerData)
|
||||
await computer.save()
|
||||
await handleComponents(componentsId as number[], computer)
|
||||
await handlePictures(pictures as Image[], request, computer)
|
||||
return response.redirect(`${computer.id}`)
|
||||
}
|
||||
|
||||
async destroy({ params, response }: HttpContext) {
|
||||
const computerId = params.id
|
||||
const computer = await Computer.findOrFail(computerId)
|
||||
await computer.delete()
|
||||
return response.redirect('/computers')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import User from '#models/user'
|
||||
import { signupValidator } from '#validators/user'
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
|
||||
export default class NewAccountController {
|
||||
async create({ inertia }: HttpContext) {
|
||||
return inertia.render('auth/signup', {})
|
||||
}
|
||||
|
||||
async store({ request, response, auth }: HttpContext) {
|
||||
const payload = await request.validateUsing(signupValidator)
|
||||
const user = await User.create({ ...payload })
|
||||
|
||||
await auth.use('web').login(user)
|
||||
response.redirect().toRoute('home')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import { loginValidator } from '#validators/Auth/login'
|
||||
import User from '#models/user'
|
||||
|
||||
export default class SessionController {
|
||||
async create({ inertia }: HttpContext) {
|
||||
return inertia.render('auth/login', {})
|
||||
}
|
||||
|
||||
async store({ request, auth, response }: HttpContext) {
|
||||
const payload = await request.validateUsing(loginValidator)
|
||||
const user = await User.verifyCredentials(payload.email, payload.password)
|
||||
await auth.use('web').login(user)
|
||||
response.redirect('/computers')
|
||||
}
|
||||
|
||||
async destroy({ auth, response }: HttpContext) {
|
||||
await auth.use('web').logout()
|
||||
response.redirect('/auth/login')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import app from '@adonisjs/core/services/app'
|
||||
import { type HttpContext, ExceptionHandler } from '@adonisjs/core/http'
|
||||
import type { StatusPageRange, StatusPageRenderer } from '@adonisjs/core/types/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
|
||||
|
||||
/**
|
||||
* Status pages are used to display a custom HTML pages for certain error
|
||||
* codes. You might want to enable them in production only, but feel
|
||||
* free to enable them in development as well.
|
||||
*/
|
||||
protected renderStatusPages = app.inProduction
|
||||
|
||||
/**
|
||||
* Status pages is a collection of error code range and a callback
|
||||
* to return the HTML contents to send as a response.
|
||||
*/
|
||||
protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
|
||||
'404': (_, { inertia }) => inertia.render('errors/not_found', {}),
|
||||
'500..599': (_, { inertia }) => inertia.render('errors/server_error', {}),
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,16 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import type { NextFn } from '@adonisjs/core/types/http'
|
||||
import type { Authenticators } from '@adonisjs/auth/types'
|
||||
|
||||
export default class AuthMiddleware {
|
||||
redirectTo = '/auth/login'
|
||||
|
||||
async handle(
|
||||
ctx: HttpContext,
|
||||
next: NextFn,
|
||||
options: { guards?: (keyof Authenticators)[] } = {}
|
||||
) {
|
||||
await ctx.auth.authenticateUsing(options.guards, { loginRoute: this.redirectTo })
|
||||
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,32 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import type { NextFn } from '@adonisjs/core/types/http'
|
||||
import type { Authenticators } from '@adonisjs/auth/types'
|
||||
|
||||
/**
|
||||
* Guest middleware is used to deny access to routes that should
|
||||
* be accessed by unauthenticated users.
|
||||
*
|
||||
* For example, the login page should not be accessible if the user
|
||||
* is already logged-in
|
||||
*/
|
||||
export default class GuestMiddleware {
|
||||
/**
|
||||
* The URL to redirect to when user is logged-in
|
||||
*/
|
||||
redirectTo = '/computers'
|
||||
|
||||
async handle(
|
||||
ctx: HttpContext,
|
||||
next: NextFn,
|
||||
options: { guards?: (keyof Authenticators)[] } = {}
|
||||
) {
|
||||
for (let guard of options.guards || [ctx.auth.defaultGuard]) {
|
||||
if (await ctx.auth.use(guard).check()) {
|
||||
ctx.session.reflash()
|
||||
return ctx.response.redirect(this.redirectTo, true)
|
||||
}
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import type { NextFn } from '@adonisjs/core/types/http'
|
||||
import UserTransformer from '#transformers/user_transformer'
|
||||
import BaseInertiaMiddleware from '@adonisjs/inertia/inertia_middleware'
|
||||
|
||||
export default class InertiaMiddleware extends BaseInertiaMiddleware {
|
||||
share(ctx: HttpContext) {
|
||||
/**
|
||||
* The share method is called everytime an Inertia page is rendered. In
|
||||
* certain cases, a page may get rendered before the session middleware
|
||||
* or the auth middleware are executed. For example: During a 404 request.
|
||||
*
|
||||
* In that case, we must always assume that HttpContext is not fully hydrated
|
||||
* with all the properties
|
||||
*/
|
||||
const { session, auth } = ctx as Partial<HttpContext>
|
||||
|
||||
/**
|
||||
* Fetching the first error from the flash messages
|
||||
*/
|
||||
const error = session?.flashMessages.get('error') as string
|
||||
const success = session?.flashMessages.get('success') as string
|
||||
|
||||
/**
|
||||
* Data shared with all Inertia pages. Make sure you are using
|
||||
* transformers for rich data-types like Models.
|
||||
*/
|
||||
return {
|
||||
errors: ctx.inertia.always(this.getValidationErrors(ctx)),
|
||||
flash: ctx.inertia.always({
|
||||
error,
|
||||
success,
|
||||
}),
|
||||
user: ctx.inertia.always(auth?.user ? UserTransformer.transform(auth.user) : undefined),
|
||||
}
|
||||
}
|
||||
|
||||
async handle(ctx: HttpContext, next: NextFn) {
|
||||
await this.init(ctx)
|
||||
|
||||
const output = await next()
|
||||
this.dispose(ctx)
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@adonisjs/inertia/types' {
|
||||
type MiddlewareSharedProps = InferSharedProps<InertiaMiddleware>
|
||||
export interface SharedProps extends MiddlewareSharedProps {}
|
||||
}
|
||||
@@ -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,38 @@
|
||||
import { DateTime } from 'luxon'
|
||||
import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm'
|
||||
import ComponentType from '#models/component_type'
|
||||
import type { BelongsTo } from '@adonisjs/lucid/types/relations'
|
||||
|
||||
export default class Component extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
declare id: number
|
||||
|
||||
@column()
|
||||
declare name: string
|
||||
|
||||
@column()
|
||||
declare price: number
|
||||
|
||||
@column({ serializeAs: null })
|
||||
declare typeId: number
|
||||
|
||||
@belongsTo(() => ComponentType, {
|
||||
foreignKey: 'typeId',
|
||||
})
|
||||
declare type: BelongsTo<typeof ComponentType>
|
||||
|
||||
@column()
|
||||
declare computerId: number | null
|
||||
|
||||
@column({ serializeAs: null })
|
||||
declare userId: number
|
||||
|
||||
@column.date()
|
||||
declare purchaseDate: DateTime
|
||||
|
||||
@column.dateTime({ autoCreate: true })
|
||||
declare createdAt: DateTime
|
||||
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||
declare updatedAt: DateTime | null
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { BaseModel, column } from '@adonisjs/lucid/orm'
|
||||
|
||||
export default class ComponentType extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
declare id: number
|
||||
|
||||
@column()
|
||||
declare name: string
|
||||
|
||||
@column()
|
||||
declare unique: boolean
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { DateTime } from 'luxon'
|
||||
import { BaseModel, belongsTo, column, computed, hasMany } from '@adonisjs/lucid/orm'
|
||||
import type { BelongsTo, HasMany } from '@adonisjs/lucid/types/relations'
|
||||
import ComputerState from '#models/computer_state'
|
||||
import ComputerPicture from '#models/computer_picture'
|
||||
import Component from '#models/component'
|
||||
|
||||
export default class Computer extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
declare id: number
|
||||
|
||||
@column()
|
||||
declare title: string
|
||||
|
||||
@column()
|
||||
declare sellPrice: number
|
||||
|
||||
@column()
|
||||
declare gpuScore: number
|
||||
|
||||
@column()
|
||||
declare cpuScore: number
|
||||
|
||||
@column()
|
||||
declare globalScore: number
|
||||
|
||||
@column({ serializeAs: null })
|
||||
declare stateId: number
|
||||
|
||||
@belongsTo(() => ComputerState, {
|
||||
foreignKey: 'stateId',
|
||||
})
|
||||
declare state: BelongsTo<typeof ComputerState>
|
||||
|
||||
@hasMany(() => Component)
|
||||
declare components: HasMany<typeof Component>
|
||||
|
||||
@hasMany(() => ComputerPicture, {
|
||||
foreignKey: 'computerId',
|
||||
})
|
||||
declare pictures: HasMany<typeof ComputerPicture>
|
||||
|
||||
@column({ serializeAs: null })
|
||||
declare userId: number
|
||||
|
||||
@column.dateTime({ autoCreate: true })
|
||||
declare createdAt: DateTime
|
||||
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||
declare updatedAt: DateTime | null
|
||||
|
||||
@computed()
|
||||
get cost(): number {
|
||||
if (!this.$preloaded.components) {
|
||||
console.warn('Components not preloaded, cost may be incorrect')
|
||||
return 0
|
||||
}
|
||||
return this.components.reduce((total, component) => total + Number(component.price), 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { DateTime } from 'luxon'
|
||||
import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm'
|
||||
import Computer from '#models/computer'
|
||||
import type { BelongsTo } from '@adonisjs/lucid/types/relations'
|
||||
|
||||
export default class ComputerPicture extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
declare id: number
|
||||
|
||||
@column()
|
||||
declare computerId: number
|
||||
|
||||
@column()
|
||||
declare link: string
|
||||
|
||||
@column()
|
||||
declare order: number
|
||||
|
||||
@belongsTo(() => Computer)
|
||||
declare computer: BelongsTo<typeof Computer>
|
||||
|
||||
@column.dateTime({ autoCreate: true })
|
||||
declare createdAt: DateTime
|
||||
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||
declare updatedAt: DateTime | null
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { BaseModel, column } from '@adonisjs/lucid/orm'
|
||||
|
||||
export default class ComputerState extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
declare id: number
|
||||
|
||||
@column()
|
||||
declare name: string
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { DateTime } from 'luxon'
|
||||
import hash from '@adonisjs/core/services/hash'
|
||||
import { compose } from '@adonisjs/core/helpers'
|
||||
import { BaseModel, column } from '@adonisjs/lucid/orm'
|
||||
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid'
|
||||
|
||||
const AuthFinder = withAuthFinder(() => hash.use('scrypt'), {
|
||||
uids: ['email'],
|
||||
passwordColumnName: 'password',
|
||||
})
|
||||
|
||||
export default class User extends compose(BaseModel, AuthFinder) {
|
||||
@column({ isPrimary: true })
|
||||
declare id: number
|
||||
|
||||
@column()
|
||||
declare name: string | null
|
||||
|
||||
@column()
|
||||
declare email: string
|
||||
|
||||
@column({ serializeAs: null })
|
||||
declare password: string
|
||||
|
||||
@column.dateTime({ autoCreate: true })
|
||||
declare createdAt: DateTime
|
||||
|
||||
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||
declare updatedAt: DateTime | null
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import Component from '#models/component'
|
||||
import Computer from '#models/computer'
|
||||
|
||||
async function handleComponents(componentsId: number[], computer: Computer) {
|
||||
const currentComponents = await computer.related('components').query()
|
||||
const currentComponentsIds = currentComponents.map((component) => component.id)
|
||||
const componentsToAdd = componentsId.filter((id) => !currentComponentsIds.includes(id))
|
||||
const componentsToRemove = currentComponentsIds.filter((id) => !componentsId.includes(id))
|
||||
await Promise.all(
|
||||
componentsToAdd.map(async (id: number) => {
|
||||
const component = await Component.findOrFail(id)
|
||||
component.computerId = computer.id
|
||||
await component.save()
|
||||
})
|
||||
)
|
||||
await Promise.all(
|
||||
componentsToRemove.map(async (id: number) => {
|
||||
const component = await Component.findOrFail(id)
|
||||
component.computerId = null
|
||||
await component.save()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export { handleComponents }
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import ComputerPicture from '#models/computer_picture'
|
||||
import Computer from '#models/computer'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import type { Image } from '#types/UItypes'
|
||||
import { existsSync, unlinkSync } from 'node:fs'
|
||||
|
||||
async function handlePictures(
|
||||
pictures: Image[],
|
||||
request: HttpContext['request'],
|
||||
computer: Computer
|
||||
) {
|
||||
const currentPictures = await computer.related('pictures').query()
|
||||
const currentPictureIds = currentPictures.map((picture) => picture.id)
|
||||
const imagesToRemove = currentPictureIds.filter(
|
||||
(id: number) => !pictures.some((picture) => 'id' in picture && picture.id === id)
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
pictures.map(async (picture: Image, index: number) => {
|
||||
if ('id' in picture) {
|
||||
const existingPicture = await ComputerPicture.findOrFail(picture.id)
|
||||
existingPicture.order = picture.order
|
||||
await existingPicture.save()
|
||||
} else {
|
||||
const image = request.file(`pictures[${index}][file]`, {
|
||||
size: '2mb',
|
||||
extnames: ['jpg', 'png', 'jpeg', 'svg'],
|
||||
})
|
||||
if (image) {
|
||||
const imageName = `img_${uuid()}.${image.extname}`
|
||||
await image.move(`uploads/computer_${computer.id}`, {
|
||||
name: imageName,
|
||||
})
|
||||
await ComputerPicture.create({
|
||||
computerId: computer.id,
|
||||
link: `/uploads/computer_${computer.id}/${imageName}`,
|
||||
order: picture.order,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
await Promise.all(
|
||||
imagesToRemove.map(async (id: number) => {
|
||||
const picture = await ComputerPicture.findOrFail(id)
|
||||
const filePath = `uploads/computer_${computer.id}/${picture.link.split('/').pop()}`
|
||||
if (existsSync(filePath)) {
|
||||
unlinkSync(filePath)
|
||||
}
|
||||
await picture.delete()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export { handlePictures }
|
||||
@@ -0,0 +1,13 @@
|
||||
import { BaseModel } from '@adonisjs/lucid/orm'
|
||||
|
||||
export default class UserTransformer {
|
||||
static exclude = ['password']
|
||||
|
||||
transform(user: BaseModel) {
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import vine from '@vinejs/vine'
|
||||
|
||||
export const loginValidator = vine.compile(
|
||||
vine.object({
|
||||
email: vine.string().email(),
|
||||
password: vine.string().minLength(8).maxLength(32),
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
import vine from '@vinejs/vine'
|
||||
|
||||
export const registerValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().minLength(3).maxLength(64),
|
||||
email: vine
|
||||
.string()
|
||||
.email()
|
||||
.unique(async (query, field) => {
|
||||
const user = await query.from('users').where('email', field).first()
|
||||
return !user
|
||||
}),
|
||||
password: vine.string().minLength(8).maxLength(32),
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
import vine from '@vinejs/vine'
|
||||
|
||||
export const storeComponentValidator = vine.compile(
|
||||
vine.object({
|
||||
name: vine.string().trim().minLength(1),
|
||||
price: vine.number().decimal([0, 2]).min(0),
|
||||
typeId: vine.number().min(1),
|
||||
purchaseDate: vine.string().regex(/^\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/),
|
||||
quantity: vine.number().min(1).withoutDecimals(),
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import vine from '@vinejs/vine'
|
||||
|
||||
const email = () => vine.string().email().maxLength(254)
|
||||
const password = () => vine.string().minLength(8).maxLength(32)
|
||||
|
||||
export const signupValidator = vine.create({
|
||||
fullName: vine.string().nullable(),
|
||||
email: email().unique({ table: 'users', column: 'email' }),
|
||||
password: password().confirmed({
|
||||
confirmationField: 'passwordConfirmation',
|
||||
}),
|
||||
})
|
||||
Reference in New Issue
Block a user