initital commit

This commit is contained in:
2020-12-16 15:54:29 +01:00
commit a8ea2ef04a
75 changed files with 6664 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
import {HttpContextContract} from '@ioc:Adonis/Core/HttpContext'
import User from "App/Models/User";
import AuthValidator from "App/Validators/AuthValidator";
export default class AuthController {
public async loginWeb ({request, auth}: HttpContextContract) {
const data = await request.validate(AuthValidator)
const {email, password, remember_me } = data
try {
await auth.attempt(email, password, remember_me)
const user = await User.query()
.where('id', auth.user!.id)
.firstOrFail()
if (!remember_me) {
await user.merge({
rememberMeToken: ''
}).save()
}
return { user }
} catch (error) {
if (error.code === 'E_INVALID_AUTH_UID') return { error: "L'utilisateur n'a pas été trouvé" }
if (error.code === 'E_INVALID_AUTH_PASSWORD') return { error: "L'identifiant ou le mot de passe est incorrect" }
}
}
public async loginApi ({request, auth}: HttpContextContract) {
const email = request.input('email')
const password = request.input('password')
const token = await auth.use('api').attempt(email, password, {
expiresIn: '10 days'
})
return token.toJSON()
}
public async logoutWeb ({auth}: HttpContextContract) {
await auth.logout()
return { message: 'Vous avez été déconnecté' }
}
public async logoutApi ({auth}: HttpContextContract) {
await auth.use('api').logout()
return { message: 'Vous avez été déconnecté' }
}
public async user ({auth}: HttpContextContract) {
await auth.authenticate()
const user = await User.query()
.where('id', auth.user!.id)
.firstOrFail()
return { user }
}
}

View File

@@ -0,0 +1,40 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Application from "@ioc:Adonis/Core/Application";
import File from "App/Models/File";
export default class FileController {
public async index () {
return File.query()
}
public async store ({request}: HttpContextContract) {
const file = await request.file('file', {
extnames: ['jpg', 'png', 'jpeg']
})
const label = request.input('label')
if (!file) {
return 'Please upload file'
}
if (file.hasErrors) {
return file.errors
}
await file.move(Application.makePath('storage'), {
name: `${label}.${file.extname}`
})
return await File.create({
fileName: `${label}.${file.extname}`,
label: label
})
}
public async destroy({ params }: HttpContextContract) {
const file = await File.findOrFail(params.id)
await file.delete()
return { message: "Le fichier a bien été supprimée" }
}
}

View File

@@ -0,0 +1,74 @@
import {HttpContextContract} from '@ioc:Adonis/Core/HttpContext'
import Post from "App/Models/Post";
import Redis from "@ioc:Adonis/Addons/Redis";
export default class PostsController {
public async getLikes ({params}: HttpContextContract) {
let post = await Post.findBy('slug', params.slug)
if (!post) {
post = await Post.create({
slug: params.slug,
likes: 0
})
}
return post.likes
}
public async unlike ({request, params}: HttpContextContract) {
const post = await Post.findByOrFail('slug', params.slug)
const ip = await request.ip()
const getLikes = post.likes - 1
const isLiked = await Redis.exists(`artapi/posts/${post.slug}/${ip}`)
if (isLiked) {
await Redis.del(`artapi/posts/${post.slug}/${ip}`)
await post.merge({
likes: getLikes
}).save()
return {
code: 200,
post
}
}
}
public async isLiked ({params, request}: HttpContextContract) {
const post = await Post.findBy('slug', params.slug)
if (post) {
const ip = request.ip()
return Redis.exists(`artapi/posts/${post.slug}/${ip}`);
}
return false
}
public async like ({request, params}: HttpContextContract) {
let post = await Post.findBy('slug', params.slug)
const ip = await request.ip()
if (!post) {
post = await Post.create({
slug: params.slug,
likes: 0
})
}
const getLikes = post.likes + 1
const isLiked = await Redis.exists(`artapi/posts/${post.slug}/${ip}`)
if (!isLiked) {
await Redis.set(`artapi/posts/${post.slug}/${ip}`, Date.now())
await post.merge({
likes: getLikes
}).save()
return {
code: 200,
post
}
}
}
}

View File

@@ -0,0 +1,41 @@
import {HttpContextContract} from "@ioc:Adonis/Core/HttpContext";
import Subscriber from "App/Models/Subscriber";
import StoreValidator from "App/Validators/subscriber/StoreValidator";
import UpdateValidator from "App/Validators/subscriber/UpdateValidator";
export default class SubscribersController {
public async index ({request}: HttpContextContract) {
const limit = request.input('limit')
const page = request.input('page')
if (limit && page) {
return Subscriber.query().orderBy('id', 'asc').paginate(page, limit);
} else {
return Subscriber.query().orderBy('id', 'asc');
}
}
public async store ({request}: HttpContextContract) {
const data = await request.validate(StoreValidator)
return await Subscriber.create(data)
}
public async show ({params}: HttpContextContract) {
return await Subscriber.findOrFail(params.id)
}
public async update({ request, params }: HttpContextContract) {
const subscriber = await Subscriber.findOrFail(params.id)
const data = await request.validate(UpdateValidator)
await subscriber.merge(data).save()
return { message: 'L\'abonné a été mis à jour' }
}
public async destroy({ params }: HttpContextContract) {
const subscriber = await Subscriber.findOrFail(params.id)
await subscriber.delete()
return { message: "L\'abonné a bien été supprimé" }
}
}

View File

@@ -0,0 +1,50 @@
import {HttpContextContract} from '@ioc:Adonis/Core/HttpContext'
import User from "App/Models/User";
import StoreValidator from "App/Validators/users/StoreValidator";
import UpdateValidator from "App/Validators/users/UpdateValidator";
export default class UsersController {
public async index () {
return User.query()
}
public async store ({request}: HttpContextContract) {
const data = await request.validate(StoreValidator)
return await User.create(data)
}
public async show ({params}: HttpContextContract) {
return await User.findOrFail(params.id)
}
public async update({ request, params, response }: HttpContextContract) {
const user = await User.findOrFail(params.id)
const data = await request.validate(UpdateValidator)
const { email } = data
const user2 = await User.findBy('email', email)
if (user2 !== null && user.email !== email) {
return response.abort({
message: 'L\' adresse mail n\'est pas unique !'
})
}
await user.merge(data).save()
return { message: 'Le compte a été mis à jour' }
}
public async destroy({ response, params, auth }: HttpContextContract) {
const user = await User.findOrFail(params.id)
const admin = await User.findBy('email', 'arthurdanjou@outlook.fr')
if (auth.user?.id != admin?.id) {
return response.unauthorized()
}
await user.delete()
return { message: "L'utilisateur a bien été supprimé" }
}
}

23
app/Exceptions/Handler.ts Normal file
View File

@@ -0,0 +1,23 @@
/*
|--------------------------------------------------------------------------
| Http Exception Handler
|--------------------------------------------------------------------------
|
| AdonisJs will forward all exceptions occurred during an HTTP request to
| the following class. You can learn more about exception handling by
| reading docs.
|
| The exception handler extends a base `HttpExceptionHandler` which is not
| mandatory, however it can do lot of heavy lifting to handle the errors
| properly.
|
*/
import Logger from '@ioc:Adonis/Core/Logger'
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
export default class ExceptionHandler extends HttpExceptionHandler {
constructor () {
super(Logger)
}
}

71
app/Middleware/Auth.ts Normal file
View File

@@ -0,0 +1,71 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { AuthenticationException } from '@adonisjs/auth/build/standalone'
/**
* Auth middleware is meant to restrict un-authenticated access to a given route
* or a group of routes.
*
* You must register this middleware inside `start/kernel.ts` file under the list
* of named middleware.
*/
export default class AuthMiddleware {
/**
* The URL to redirect to when request is Unauthorized
*/
protected redirectTo = '/login'
/**
* Authenticates the current HTTP request against a custom set of defined
* guards.
*
* The authentication loop stops as soon as the user is authenticated using any
* of the mentioned guards and that guard will be used by the rest of the code
* during the current request.
*/
protected async authenticate (auth: HttpContextContract['auth'], guards: any[]) {
/**
* Hold reference to the guard last attempted within the for loop. We pass
* the reference of the guard to the "AuthenticationException", so that
* it can decide the correct response behavior based upon the guard
* driver
*/
let guardLastAttempted: string | undefined
for (let guard of guards) {
guardLastAttempted = guard
if (await auth.use(guard).check()) {
/**
* Instruct auth to use the given guard as the default guard for
* the rest of the request, since the user authenticated
* succeeded here
*/
auth.defaultGuard = guard
return true
}
}
/**
* Unable to authenticate using any guard
*/
throw new AuthenticationException(
'Unauthorized access',
'E_UNAUTHORIZED_ACCESS',
guardLastAttempted,
this.redirectTo,
)
}
/**
* Handle request
*/
public async handle ({ auth }: HttpContextContract, next: () => Promise<void>, customGuards: string[]) {
/**
* Uses the user defined guards or the default guard mentioned in
* the config file
*/
const guards = customGuards.length ? customGuards : [auth.name]
await this.authenticate(auth, guards)
await next()
}
}

View File

@@ -0,0 +1,21 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
/**
* 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 {
/**
* Handle request
*/
public async handle ({ auth }: HttpContextContract, next: () => Promise<void>) {
/**
* Check if user is logged-in or not. If yes, then `ctx.auth.user` will be
* set to the instance of the currently logged in user.
*/
await auth.check()
await next()
}
}

19
app/Models/File.ts Normal file
View File

@@ -0,0 +1,19 @@
import { DateTime } from 'luxon'
import {BaseModel, column} from '@ioc:Adonis/Lucid/Orm'
export default class File extends BaseModel {
@column({ isPrimary: true })
public id: number
@column()
public label: string
@column()
public fileName: string
@column.dateTime({ autoCreate: true })
public createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime
}

19
app/Models/Post.ts Normal file
View File

@@ -0,0 +1,19 @@
import { DateTime } from 'luxon'
import {BaseModel, column} from '@ioc:Adonis/Lucid/Orm'
export default class Post extends BaseModel {
@column({ isPrimary: true })
public id: number
@column()
public slug: string
@column()
public likes: number
@column.dateTime({ autoCreate: true })
public createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime
}

19
app/Models/Subscriber.ts Normal file
View File

@@ -0,0 +1,19 @@
import { DateTime } from 'luxon'
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'
export default class Subscriber extends BaseModel {
@column({ isPrimary: true })
public id: number
@column()
public name: string
@column()
public email: string
@column.dateTime({ autoCreate: true })
public createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime
}

40
app/Models/User.ts Normal file
View File

@@ -0,0 +1,40 @@
import { DateTime } from 'luxon'
import Hash from '@ioc:Adonis/Core/Hash'
import {
column,
beforeSave,
BaseModel,
} from '@ioc:Adonis/Lucid/Orm'
export default class User extends BaseModel {
@column({ isPrimary: true })
public id: number
@column()
public email: string
@column()
public password: string
@column()
public confirmationToken: string
@column()
public isConfirmed: boolean
@column()
public rememberMeToken?: string
@column.dateTime({ autoCreate: true })
public createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime
@beforeSave()
public static async hashPassword (user: User) {
if (user.$dirty.password) {
user.password = await Hash.make(user.password)
}
}
}

View File

@@ -0,0 +1,26 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import {rules, schema} from '@ioc:Adonis/Core/Validator'
export default class AuthValidator {
constructor (private ctx: HttpContextContract) {
}
public schema = schema.create({
email: schema.string({ trim: true }, [
rules.email(),
rules.required()
]),
password: schema.string({ trim: true }, [
rules.required()
]),
remember_me: schema.boolean()
})
public cacheKey = this.ctx.routeKey
public messages = {
'email.email': 'L\'adresse mail n\'est pas valide !',
'email.required': 'Veuillez renseigner une adresse mail !',
'password.required': 'Veuillez renseigner un mot de passe !',
}
}

View File

@@ -0,0 +1,19 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import {schema} from '@ioc:Adonis/Core/Validator'
export default class StoreValidator {
constructor (private ctx: HttpContextContract) {
}
public schema = schema.create({
place: schema.string(),
since: schema.date(),
left: schema.string(),
})
public cacheKey = this.ctx.routeKey
public messages = {
required: 'Le champ {{field}} doit être valide !',
}
}

View File

@@ -0,0 +1,19 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import {schema} from '@ioc:Adonis/Core/Validator'
export default class UpdateValidator {
constructor (private ctx: HttpContextContract) {
}
public schema = schema.create({
place: schema.string.optional(),
since: schema.date.optional(),
left: schema.string.optional(),
})
public cacheKey = this.ctx.routeKey
public messages = {
required: 'Le champ {{field}} doit être valide !'
}
}

View File

@@ -0,0 +1,25 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import {rules, schema} from '@ioc:Adonis/Core/Validator'
export default class StoreValidator {
constructor (private ctx: HttpContextContract) {
}
public schema = schema.create({
title: schema.string(),
content: schema.string(),
description: schema.string(),
readingTime: schema.number(),
coverId: schema.number([rules.exists({ table: 'files', column: 'id' })]),
tags: schema.array().members(
schema.number()
),
lightBackGround: schema.boolean()
})
public cacheKey = this.ctx.routeKey
public messages = {
required: 'Le champ {{field}} doit être valide !',
}
}

View File

@@ -0,0 +1,25 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import {rules, schema} from '@ioc:Adonis/Core/Validator'
export default class UpdateValidator {
constructor (private ctx: HttpContextContract) {
}
public schema = schema.create({
title: schema.string.optional(),
content: schema.string.optional(),
description: schema.string.optional(),
readingTime: schema.number.optional(),
coverId: schema.number.optional([rules.exists({ table: 'files', column: 'id' })]),
tags: schema.array.optional().members(
schema.number()
),
lightBackGround: schema.boolean.optional()
})
public cacheKey = this.ctx.routeKey
public messages = {
required: 'Le champ {{field}} doit être valide !'
}
}

View File

@@ -0,0 +1,21 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import {rules, schema} from '@ioc:Adonis/Core/Validator'
export default class StoreValidator {
constructor (private ctx: HttpContextContract) {
}
public schema = schema.create({
email: schema.string({ trim: true }, [
rules.email(),
rules.unique({table: 'subscribers', column: 'email'})
]),
name: schema.string()
})
public cacheKey = this.ctx.routeKey
public messages = {
required: 'Le champ {{field}} doit être valide !',
}
}

View File

@@ -0,0 +1,21 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import {rules, schema} from '@ioc:Adonis/Core/Validator'
export default class UpdateValidator {
constructor (private ctx: HttpContextContract) {
}
public schema = schema.create({
email: schema.string({ trim: true }, [
rules.email(),
rules.unique({table: 'subscribers', column: 'email'})
]),
name: schema.string.optional()
})
public cacheKey = this.ctx.routeKey
public messages = {
required: 'Le champ {{field}} doit être valide !'
}
}

View File

@@ -0,0 +1,22 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import {rules, schema} from '@ioc:Adonis/Core/Validator'
export default class StoreValidator {
constructor (private ctx: HttpContextContract) {
}
public schema = schema.create({
email: schema.string({ trim: true, escape: true }, [
rules.email(),
rules.required(),
rules.unique({table: 'users', column: 'email'})
])
})
public cacheKey = this.ctx.routeKey
public messages = {
required: 'Le champ {{field}} doit être valide !',
'email.email': 'L\'adresse mail doit être valide !',
}
}

View File

@@ -0,0 +1,23 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import {rules, schema} from '@ioc:Adonis/Core/Validator'
export default class UpdateValidator {
constructor (private ctx: HttpContextContract) {
}
public schema = schema.create({
email: schema.string.optional({ trim: true, escape: true }, [rules.email()]),
password: schema.string.optional({ trim: true, escape: true }, [rules.confirmed()]),
is_confirmed: schema.boolean.optional(),
confirmation_token: schema.string.optional({ trim: true, escape: true }),
remember_me: schema.string.optional({ trim: true, escape: true }),
})
public cacheKey = this.ctx.routeKey
public messages = {
required: 'Le champ {{field}} doit être valide !',
'email.email': 'L\'adresse mail doit être valide !',
'password.confirmation': 'Les mots de passe ne correspondent pas !'
}
}