rework and update deps

This commit is contained in:
2021-04-18 17:02:17 +02:00
commit 16216a4906
44 changed files with 5399 additions and 0 deletions

30
.adonisrc.json Normal file
View File

@@ -0,0 +1,30 @@
{
"typescript": true,
"commands": [
"./commands",
"@adonisjs/core/build/commands",
"@adonisjs/repl/build/commands",
"@adonisjs/lucid/build/commands"
],
"exceptionHandlerNamespace": "App/Exceptions/Handler",
"aliases": {
"App": "app",
"Config": "config",
"Database": "database",
"Contracts": "contracts"
},
"preloads": [
"./start/routes",
"./start/kernel"
],
"providers": [
"./providers/AppProvider",
"@adonisjs/core",
"@adonisjs/auth",
"@adonisjs/redis",
"@adonisjs/lucid"
],
"aceProviders": [
"@adonisjs/repl"
]
}

14
.editorconfig Normal file
View File

@@ -0,0 +1,14 @@
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.json]
insert_final_newline = ignore
[*.md]
trim_trailing_whitespace = false

14
.env.example Normal file
View File

@@ -0,0 +1,14 @@
PORT=
HOST=
NODE_ENV=
APP_KEY=
REDIS_CONNECTION=
REDIS_HOST=
REDIS_PORT=
REDIS_PASSWORD=
DB_CONNECTION=
MYSQL_HOST=
MYSQL_PORT=
MYSQL_USER=
MYSQL_PASSWORD=
MYSQL_DB_NAME=

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
node_modules
build
coverage
.vscode
.DS_STORE
.env
tmp

5
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

12
.idea/artclick.iml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/discord.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/artclick.iml" filepath="$PROJECT_DIR$/.idea/artclick.iml" />
</modules>
</component>
</project>

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM node:15.8.0-alpine3.10
RUN mkdir -p /usr/src/artclick
WORKDIR /usr/src/artclick
COPY . /usr/src/artclick
RUN apk update && \
apk add git
RUN yarn install
RUN yarn build
RUN cp .env build
WORKDIR /usr/src/artclick/build
RUN yarn install --production
EXPOSE 1012
CMD ["yarn", "start"]

16
ace Normal file
View File

@@ -0,0 +1,16 @@
/*
|--------------------------------------------------------------------------
| Ace Commands
|--------------------------------------------------------------------------
|
| This file is the entry point for running ace commands.
|
*/
require('reflect-metadata')
require('source-map-support').install({ handleUncaughtExceptions: false })
const { Ignitor } = require('@adonisjs/core/build/standalone')
new Ignitor(__dirname)
.ace()
.handle(process.argv.slice(2))

1
ace-manifest.json Normal file
View File

@@ -0,0 +1 @@
{"dump:rcfile":{"settings":{},"commandPath":"@adonisjs/core/build/commands/DumpRc","commandName":"dump:rcfile","description":"Dump contents of .adonisrc.json file along with defaults","args":[],"flags":[]},"list:routes":{"settings":{"loadApp":true},"commandPath":"@adonisjs/core/build/commands/ListRoutes","commandName":"list:routes","description":"List application routes","args":[],"flags":[{"name":"json","propertyName":"json","type":"boolean","description":"Output as JSON"}]},"generate:key":{"settings":{},"commandPath":"@adonisjs/core/build/commands/GenerateKey","commandName":"generate:key","description":"Generate a new APP_KEY secret","args":[],"flags":[]},"repl":{"settings":{"loadApp":true,"environment":"repl","stayAlive":true},"commandPath":"@adonisjs/repl/build/commands/AdonisRepl","commandName":"repl","description":"Start a new REPL session","args":[],"flags":[]},"db:seed":{"settings":{"loadApp":true},"commandPath":"@adonisjs/lucid/build/commands/DbSeed","commandName":"db:seed","description":"Execute database seeder files","args":[],"flags":[{"name":"connection","propertyName":"connection","type":"string","description":"Define a custom database connection for the seeders","alias":"c"},{"name":"interactive","propertyName":"interactive","type":"boolean","description":"Run seeders in interactive mode","alias":"i"},{"name":"files","propertyName":"files","type":"array","description":"Define a custom set of seeders files names to run","alias":"f"}]},"make:model":{"settings":{},"commandPath":"@adonisjs/lucid/build/commands/MakeModel","commandName":"make:model","description":"Make a new Lucid model","args":[{"type":"string","propertyName":"name","name":"name","required":true,"description":"Name of the model class"}],"flags":[{"name":"migration","propertyName":"migration","type":"boolean","alias":"m","description":"Generate the migration for the model"},{"name":"controller","propertyName":"controller","type":"boolean","alias":"c","description":"Generate the controller for the model"}]},"make:migration":{"settings":{"loadApp":true},"commandPath":"@adonisjs/lucid/build/commands/MakeMigration","commandName":"make:migration","description":"Make a new migration file","args":[{"type":"string","propertyName":"name","name":"name","required":true,"description":"Name of the migration file"}],"flags":[{"name":"connection","propertyName":"connection","type":"string","description":"Define a custom database connection for the migration"},{"name":"folder","propertyName":"folder","type":"string","description":"Pre-select a migration directory"},{"name":"create","propertyName":"create","type":"string","description":"Define the table name for creating a new table"},{"name":"table","propertyName":"table","type":"string","description":"Define the table name for altering an existing table"}]},"make:seeder":{"settings":{},"commandPath":"@adonisjs/lucid/build/commands/MakeSeeder","commandName":"make:seeder","description":"Make a new Seeder file","args":[{"type":"string","propertyName":"name","name":"name","required":true,"description":"Name of the seeder class"}],"flags":[]},"migration:run":{"settings":{"loadApp":true},"commandPath":"@adonisjs/lucid/build/commands/Migration/Run","commandName":"migration:run","description":"Run pending migrations","args":[],"flags":[{"name":"connection","propertyName":"connection","type":"string","description":"Define a custom database connection","alias":"c"},{"name":"force","propertyName":"force","type":"boolean","description":"Explicitly force to run migrations in production"},{"name":"dry-run","propertyName":"dryRun","type":"boolean","description":"Print SQL queries, instead of running the migrations"}]},"migration:rollback":{"settings":{"loadApp":true},"commandPath":"@adonisjs/lucid/build/commands/Migration/Rollback","commandName":"migration:rollback","description":"Rollback migrations to a given batch number","args":[],"flags":[{"name":"connection","propertyName":"connection","type":"string","description":"Define a custom database connection","alias":"c"},{"name":"force","propertyName":"force","type":"boolean","description":"Explictly force to run migrations in production"},{"name":"dry-run","propertyName":"dryRun","type":"boolean","description":"Print SQL queries, instead of running the migrations"},{"name":"batch","propertyName":"batch","type":"number","description":"Define custom batch number for rollback. Use 0 to rollback to initial state"}]},"migration:status":{"settings":{"loadApp":true},"commandPath":"@adonisjs/lucid/build/commands/Migration/Status","commandName":"migration:status","description":"Check migrations current status.","args":[],"flags":[{"name":"connection","propertyName":"connection","type":"string","description":"Define a custom database connection","alias":"c"}]}}

View File

@@ -0,0 +1,62 @@
import {HttpContextContract} from '@ioc:Adonis/Core/HttpContext'
import StoreValidator from "App/Validators/StoreValidator";
import Link from "App/Models/Link";
import UpdateValidator from "App/Validators/UpdateValidator";
export default class LinksController {
public async getLink ({params, response}: HttpContextContract) {
const code = params.id
const link = await Link.findByOrFail('code', code)
if (link.code === code) {
let visitCount = link.visitCount
await link.merge({
visitCount: visitCount++
}).save()
return response.redirect(link.target)
}
return response.badRequest(`Code does not exist ! (/${code})`)
}
public async getAllLinks ({response}: HttpContextContract) {
const links = await Link.all()
return response.ok(links);
}
public async getVisitCount ({params}: HttpContextContract) {
const code = params.id
const link = await Link.findByOrFail('code', code)
//Check if code exists
if (link.code === code) {
return {
count: link.visitCount
}
}
return {
message: `Code does not exist ! (${code}`
}
}
public async createLink ({request}: HttpContextContract) {
const link = await Link.create(await request.validate(StoreValidator))
return {
message: `Link successfully created : ${link.code} + ${link.target}`
}
}
public async updateLink ({request}: HttpContextContract) {
const link = await Link.findByOrFail('code', request.input('link'))
const data = await request.validate(UpdateValidator)
await link.merge(data).save()
return link
}
public async deleteLink ({request}: HttpContextContract) {
const code = request.input('code')
const link = await Link.findByOrFail('code', code)
await link.delete()
}
}

View File

@@ -0,0 +1,27 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class UsersController {
public async login ({request, auth}: HttpContextContract) {
const email = request.input('email')
const password = request.input('password')
const token = await auth.use('api').attempt(email, password, {
expiresIn: '2 days'
})
return token.toJSON()
}
public async createInfiniteToken ({request, auth}: HttpContextContract) {
const email = request.input('email')
const password = request.input('password')
const token = await auth.use('api').attempt(email, password)
return token.toJSON()
}
public async logout ({auth}: HttpContextContract) {
await auth.use('api').logout()
return { message: 'Vous avez été déconnecté' }
}
}

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()
}
}

24
app/Models/Link.ts Normal file
View File

@@ -0,0 +1,24 @@
import {
BaseModel, column,
} from '@ioc:Adonis/Lucid/Orm'
import {DateTime} from "luxon";
export default class Link extends BaseModel {
@column({ isPrimary: true })
public id: number
@column()
public code: string;
@column()
public target: string;
@column({columnName: 'visit_count'})
public visitCount: number;
@column.dateTime({ autoCreate: true })
public createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime
}

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

@@ -0,0 +1,31 @@
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({ serializeAs: null })
public password: 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,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({
code: schema.string({}, [
rules.unique({table: 'links', column: 'code' }),
rules.maxLength(10)
]),
target: schema.string({}),
})
public cacheKey = this.ctx.routeKey
public messages = {
'code.unique': 'The code already exist !',
'code.maxLength': 'The code is too long ! (max 10 caracteres)'
}
}

View File

@@ -0,0 +1,22 @@
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({
code: schema.string.optional({}, [
rules.unique({table: 'links', column: 'code' }),
rules.maxLength(10)
]),
target: schema.string.optional({}),
visit_count: schema.number.optional()
})
public cacheKey = this.ctx.routeKey
public messages = {}
}

19
commands/index.ts Normal file
View File

@@ -0,0 +1,19 @@
import { listDirectoryFiles } from '@adonisjs/core/build/standalone'
import Application from '@ioc:Adonis/Core/Application'
/*
|--------------------------------------------------------------------------
| Exporting an array of commands
|--------------------------------------------------------------------------
|
| Instead of manually exporting each file from this directory, we use the
| helper `listDirectoryFiles` to recursively collect and export an array
| of filenames.
|
| Couple of things to note:
|
| 1. The file path must be relative from the project root and not this directory.
| 2. We must ignore this file to avoid getting into an infinite loop
|
*/
export default listDirectoryFiles(__dirname, Application.appRoot, ['./commands/index'])

235
config/app.ts Normal file
View File

@@ -0,0 +1,235 @@
/**
* Config source: https://git.io/JfefZ
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import proxyAddr from 'proxy-addr'
import Env from '@ioc:Adonis/Core/Env'
import { ServerConfig } from '@ioc:Adonis/Core/Server'
import { LoggerConfig } from '@ioc:Adonis/Core/Logger'
import { ProfilerConfig } from '@ioc:Adonis/Core/Profiler'
import { ValidatorConfig } from '@ioc:Adonis/Core/Validator'
/*
|--------------------------------------------------------------------------
| Application secret key
|--------------------------------------------------------------------------
|
| The secret to encrypt and sign different values in your application.
| Make sure to keep the `APP_KEY` as an environment variable and secure.
|
| Note: Changing the application key for an existing app will make all
| the cookies invalid and also the existing encrypted data will not
| be decrypted.
|
*/
export const appKey: string = Env.get('APP_KEY')
/*
|--------------------------------------------------------------------------
| Http server configuration
|--------------------------------------------------------------------------
|
| The configuration for the HTTP(s) server. Make sure to go through all
| the config properties to make keep server secure.
|
*/
export const http: ServerConfig = {
/*
|--------------------------------------------------------------------------
| Allow method spoofing
|--------------------------------------------------------------------------
|
| Method spoofing enables defining custom HTTP methods using a query string
| `_method`. This is usually required when you are making traditional
| form requests and wants to use HTTP verbs like `PUT`, `DELETE` and
| so on.
|
*/
allowMethodSpoofing: false,
/*
|--------------------------------------------------------------------------
| Subdomain offset
|--------------------------------------------------------------------------
*/
subdomainOffset: 2,
/*
|--------------------------------------------------------------------------
| Request Ids
|--------------------------------------------------------------------------
|
| Setting this value to `true` will generate a unique request id for each
| HTTP request and set it as `x-request-id` header.
|
*/
generateRequestId: false,
/*
|--------------------------------------------------------------------------
| Trusting proxy servers
|--------------------------------------------------------------------------
|
| Define the proxy servers that AdonisJs must trust for reading `X-Forwarded`
| headers.
|
*/
trustProxy: proxyAddr.compile('loopback'),
/*
|--------------------------------------------------------------------------
| Generating Etag
|--------------------------------------------------------------------------
|
| Whether or not to generate an etag for every response.
|
*/
etag: false,
/*
|--------------------------------------------------------------------------
| JSONP Callback
|--------------------------------------------------------------------------
*/
jsonpCallbackName: 'callback',
/*
|--------------------------------------------------------------------------
| Cookie settings
|--------------------------------------------------------------------------
*/
cookie: {
domain: '',
path: '/',
maxAge: '2h',
httpOnly: true,
secure: false,
sameSite: false,
},
/*
|--------------------------------------------------------------------------
| Force content negotiation to JSON
|--------------------------------------------------------------------------
|
| The internals of the framework relies on the content negotiation to
| detect the best possible response type for a given HTTP request.
|
| However, it is a very common these days that API servers always wants to
| make response in JSON regardless of the existence of the `Accept` header.
|
| By setting `forceContentNegotiationToJSON = true`, you negotiate with the
| server in advance to always return JSON without relying on the client
| to set the header explicitly.
|
*/
forceContentNegotiationToJSON: true,
}
/*
|--------------------------------------------------------------------------
| Logger
|--------------------------------------------------------------------------
*/
export const logger: LoggerConfig = {
/*
|--------------------------------------------------------------------------
| Application name
|--------------------------------------------------------------------------
|
| The name of the application you want to add to the log. It is recommended
| to always have app name in every log line.
|
| The `APP_NAME` environment variable is automatically set by AdonisJS by
| reading the `name` property from the `package.json` file.
|
*/
name: Env.get('APP_NAME'),
/*
|--------------------------------------------------------------------------
| Toggle logger
|--------------------------------------------------------------------------
|
| Enable or disable logger application wide
|
*/
enabled: true,
/*
|--------------------------------------------------------------------------
| Logging level
|--------------------------------------------------------------------------
|
| The level from which you want the logger to flush logs. It is recommended
| to make use of the environment variable, so that you can define log levels
| at deployment level and not code level.
|
*/
level: Env.get('LOG_LEVEL', 'info'),
/*
|--------------------------------------------------------------------------
| Pretty print
|--------------------------------------------------------------------------
|
| It is highly advised NOT to use `prettyPrint` in production, since it
| can have huge impact on performance.
|
*/
prettyPrint: Env.get('NODE_ENV') === 'development',
}
/*
|--------------------------------------------------------------------------
| Profiler
|--------------------------------------------------------------------------
*/
export const profiler: ProfilerConfig = {
/*
|--------------------------------------------------------------------------
| Toggle profiler
|--------------------------------------------------------------------------
|
| Enable or disable profiler
|
*/
enabled: true,
/*
|--------------------------------------------------------------------------
| Blacklist actions/row labels
|--------------------------------------------------------------------------
|
| Define an array of actions or row labels that you want to disable from
| getting profiled.
|
*/
blacklist: [],
/*
|--------------------------------------------------------------------------
| Whitelist actions/row labels
|--------------------------------------------------------------------------
|
| Define an array of actions or row labels that you want to whitelist for
| the profiler. When whitelist is defined, then `blacklist` is ignored.
|
*/
whitelist: [],
}
/*
|--------------------------------------------------------------------------
| Validator
|--------------------------------------------------------------------------
|
| Configure the global configuration for the validator. Here's the reference
| to the default config https://git.io/JT0WE
|
*/
export const validator: ValidatorConfig = {
}

111
config/auth.ts Normal file
View File

@@ -0,0 +1,111 @@
/**
* Config source: https://git.io/JvyKy
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { AuthConfig } from '@ioc:Adonis/Addons/Auth'
/*
|--------------------------------------------------------------------------
| Authentication Mapping
|--------------------------------------------------------------------------
|
| List of available authentication mapping. You must first define them
| inside the `contracts/auth.ts` file before mentioning them here.
|
*/
const authConfig: AuthConfig = {
guard: 'api',
list: {
/*
|--------------------------------------------------------------------------
| OAT Guard
|--------------------------------------------------------------------------
|
| OAT (Opaque access tokens) guard uses database backed tokens to authenticate
| HTTP request. This guard DOES NOT rely on sessions or cookies and uses
| Authorization header value for authentication.
|
| Use this guard to authenticate mobile apps or web clients that cannot rely
| on cookies/sessions.
|
*/
api: {
driver: 'oat',
/*
|--------------------------------------------------------------------------
| Redis provider for managing tokens
|--------------------------------------------------------------------------
|
| Uses Redis for managing tokens. We recommend using the "redis" driver
| over the "database" driver when the tokens based auth is the
| primary authentication mode.
|
| Redis ensure that all the expired tokens gets cleaned up automatically.
| Whereas with SQL, you have to cleanup expired tokens manually.
|
| The foreignKey column is used to make the relationship between the user
| and the token. You are free to use any column name here.
|
*/
tokenProvider: {
driver: 'redis',
redisConnection: 'local',
foreignKey: 'user_id',
},
provider: {
/*
|--------------------------------------------------------------------------
| Driver
|--------------------------------------------------------------------------
|
| Name of the driver
|
*/
driver: 'lucid',
/*
|--------------------------------------------------------------------------
| Identifier key
|--------------------------------------------------------------------------
|
| The identifier key is the unique key on the model. In most cases specifying
| the primary key is the right choice.
|
*/
identifierKey: 'id',
/*
|--------------------------------------------------------------------------
| Uids
|--------------------------------------------------------------------------
|
| Uids are used to search a user against one of the mentioned columns. During
| login, the auth module will search the user mentioned value against one
| of the mentioned columns to find their user record.
|
*/
uids: ['email'],
/*
|--------------------------------------------------------------------------
| Model
|--------------------------------------------------------------------------
|
| The model to use for fetching or finding users. The model is imported
| lazily since the config files are read way earlier in the lifecycle
| of booting the app and the models may not be in a usable state at
| that time.
|
*/
model: () => import('App/Models/User'),
},
},
},
}
export default authConfig

186
config/bodyparser.ts Normal file
View File

@@ -0,0 +1,186 @@
/**
* Config source: https://git.io/Jfefn
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { BodyParserConfig } from '@ioc:Adonis/Core/BodyParser'
const bodyParserConfig: BodyParserConfig = {
/*
|--------------------------------------------------------------------------
| White listed methods
|--------------------------------------------------------------------------
|
| HTTP methods for which body parsing must be performed. It is a good practice
| to avoid body parsing for `GET` requests.
|
*/
whitelistedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
/*
|--------------------------------------------------------------------------
| JSON parser settings
|--------------------------------------------------------------------------
|
| The settings for the JSON parser. The types defines the request content
| types which gets processed by the JSON parser.
|
*/
json: {
encoding: 'utf-8',
limit: '1mb',
strict: true,
types: [
'application/json',
'application/json-patch+json',
'application/vnd.api+json',
'application/csp-report',
],
},
/*
|--------------------------------------------------------------------------
| Form parser settings
|--------------------------------------------------------------------------
|
| The settings for the `application/x-www-form-urlencoded` parser. The types
| defines the request content types which gets processed by the form parser.
|
*/
form: {
encoding: 'utf-8',
limit: '1mb',
queryString: {},
types: [
'application/x-www-form-urlencoded',
],
},
/*
|--------------------------------------------------------------------------
| Raw body parser settings
|--------------------------------------------------------------------------
|
| Raw body just reads the request body stream as a plain text, which you
| can process by hand. This must be used when request body type is not
| supported by the body parser.
|
*/
raw: {
encoding: 'utf-8',
limit: '1mb',
queryString: {},
types: [
'text/*',
],
},
/*
|--------------------------------------------------------------------------
| Multipart parser settings
|--------------------------------------------------------------------------
|
| The settings for the `multipart/form-data` parser. The types defines the
| request content types which gets processed by the form parser.
|
*/
multipart: {
/*
|--------------------------------------------------------------------------
| Auto process
|--------------------------------------------------------------------------
|
| The auto process option will process uploaded files and writes them to
| the `tmp` folder. You can turn it off and then manually use the stream
| to pipe stream to a different destination.
|
| It is recommended to keep `autoProcess=true`. Unless you are processing bigger
| file sizes.
|
*/
autoProcess: true,
/*
|--------------------------------------------------------------------------
| Files to be processed manually
|--------------------------------------------------------------------------
|
| You can turn off `autoProcess` for certain routes by defining
| routes inside the following array.
|
| NOTE: Make sure the route pattern starts with a leading slash.
|
| Correct
| ```js
| /projects/:id/file
| ```
|
| Incorrect
| ```js
| projects/:id/file
| ```
*/
processManually: [],
/*
|--------------------------------------------------------------------------
| Temporary file name
|--------------------------------------------------------------------------
|
| When auto processing is on. We will use this method to compute the temporary
| file name. AdonisJs will compute a unique `tmpPath` for you automatically,
| However, you can also define your own custom method.
|
*/
// tmpFileName () {
// },
/*
|--------------------------------------------------------------------------
| Encoding
|--------------------------------------------------------------------------
|
| Request body encoding
|
*/
encoding: 'utf-8',
/*
|--------------------------------------------------------------------------
| Max Fields
|--------------------------------------------------------------------------
|
| The maximum number of fields allowed in the request body. The field includes
| text inputs and files both.
|
*/
maxFields: 1000,
/*
|--------------------------------------------------------------------------
| Request body limit
|--------------------------------------------------------------------------
|
| The total limit to the multipart body. This includes all request files
| and fields data.
|
*/
limit: '20mb',
/*
|--------------------------------------------------------------------------
| Types
|--------------------------------------------------------------------------
|
| The types that will be considered and parsed as multipart body.
|
*/
types: [
'multipart/form-data',
],
},
}
export default bodyParserConfig

134
config/cors.ts Normal file
View File

@@ -0,0 +1,134 @@
/**
* Config source: https://git.io/JfefC
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { CorsConfig } from '@ioc:Adonis/Core/Cors'
const corsConfig: CorsConfig = {
/*
|--------------------------------------------------------------------------
| Enabled
|--------------------------------------------------------------------------
|
| A boolean to enable or disable CORS integration from your AdonisJs
| application.
|
| Setting the value to `true` will enable the CORS for all HTTP request. However,
| you can define a function to enable/disable it on per request basis as well.
|
*/
enabled: true,
// You can also use a function that return true or false.
// enabled: (request) => request.url().startsWith('/api')
/*
|--------------------------------------------------------------------------
| Origin
|--------------------------------------------------------------------------
|
| Set a list of origins to be allowed for `Access-Control-Allow-Origin`.
| The value can be one of the following:
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
| Boolean (true) - Allow current request origin.
| Boolean (false) - Disallow all.
| String - Comma separated list of allowed origins.
| Array - An array of allowed origins.
| String (*) - A wildcard (*) to allow all request origins.
| Function - Receives the current origin string and should return
| one of the above values.
|
*/
origin: true,
/*
|--------------------------------------------------------------------------
| Methods
|--------------------------------------------------------------------------
|
| An array of allowed HTTP methods for CORS. The `Access-Control-Request-Method`
| is checked against the following list.
|
| Following is the list of default methods. Feel free to add more.
*/
methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
/*
|--------------------------------------------------------------------------
| Headers
|--------------------------------------------------------------------------
|
| List of headers to be allowed for `Access-Control-Allow-Headers` header.
| The value can be one of the following:
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers
|
| Boolean(true) - Allow all headers mentioned in `Access-Control-Request-Headers`.
| Boolean(false) - Disallow all headers.
| String - Comma separated list of allowed headers.
| Array - An array of allowed headers.
| Function - Receives the current header and should return one of the above values.
|
*/
headers: true,
/*
|--------------------------------------------------------------------------
| Expose Headers
|--------------------------------------------------------------------------
|
| A list of headers to be exposed by setting `Access-Control-Expose-Headers`.
| header. By default following 6 simple response headers are exposed.
|
| Cache-Control
| Content-Language
| Content-Type
| Expires
| Last-Modified
| Pragma
|
| In order to add more headers, simply define them inside the following array.
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
|
*/
exposeHeaders: [
'cache-control',
'content-language',
'content-type',
'expires',
'last-modified',
'pragma',
],
/*
|--------------------------------------------------------------------------
| Credentials
|--------------------------------------------------------------------------
|
| Toggle `Access-Control-Allow-Credentials` header. If value is set to `true`,
| then header will be set, otherwise not.
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
|
*/
credentials: true,
/*
|--------------------------------------------------------------------------
| MaxAge
|--------------------------------------------------------------------------
|
| Define `Access-Control-Max-Age` header in seconds.
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
|
*/
maxAge: 90,
}
export default corsConfig

68
config/database.ts Normal file
View File

@@ -0,0 +1,68 @@
/**
* Config source: https://git.io/JesV9
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import Env from '@ioc:Adonis/Core/Env'
import { OrmConfig } from '@ioc:Adonis/Lucid/Orm'
import { DatabaseConfig } from '@ioc:Adonis/Lucid/Database'
const databaseConfig: DatabaseConfig & { orm: Partial<OrmConfig> } = {
/*
|--------------------------------------------------------------------------
| Connection
|--------------------------------------------------------------------------
|
| The primary connection for making database queries across the application
| You can use any key from the `connections` object defined in this same
| file.
|
*/
connection: Env.get('DB_CONNECTION'),
connections: {
/*
|--------------------------------------------------------------------------
| MySQL config
|--------------------------------------------------------------------------
|
| Configuration for MySQL database. Make sure to install the driver
| from npm when using this connection
|
| npm i mysql
|
*/
mysql: {
client: 'mysql',
connection: {
host: Env.get('MYSQL_HOST'),
port: Env.get('MYSQL_PORT'),
user: Env.get('MYSQL_USER'),
password: Env.get('MYSQL_PASSWORD', ''),
database: Env.get('MYSQL_DB_NAME'),
},
healthCheck: true,
debug: false,
},
},
/*
|--------------------------------------------------------------------------
| ORM Configuration
|--------------------------------------------------------------------------
|
| Following are some of the configuration options to tweak the conventional
| settings of the ORM. For example:
|
| - Define a custom function to compute the default table name for a given model.
| - Or define a custom function to compute the primary key for a given model.
|
*/
orm: {
},
}
export default databaseConfig

75
config/hash.ts Normal file
View File

@@ -0,0 +1,75 @@
/**
* Config source: https://git.io/JfefW
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import Env from '@ioc:Adonis/Core/Env'
import { HashConfig } from '@ioc:Adonis/Core/Hash'
/*
|--------------------------------------------------------------------------
| Hash Config
|--------------------------------------------------------------------------
|
| The `HashConfig` relies on the `HashList` interface which is
| defined inside `contracts` directory.
|
*/
const hashConfig: HashConfig = {
/*
|--------------------------------------------------------------------------
| Default hasher
|--------------------------------------------------------------------------
|
| By default we make use of the bcrypt hasher to hash values. However, feel
| free to change the default value
|
*/
default: Env.get('HASH_DRIVER', 'argon'),
list: {
/*
|--------------------------------------------------------------------------
| Argon
|--------------------------------------------------------------------------
|
| Argon mapping uses the `argon2` driver to hash values.
|
| Make sure you install the underlying dependency for this driver to work.
| https://www.npmjs.com/package/phc-argon2.
|
| npm install phc-argon2
|
*/
argon: {
driver: 'argon2',
variant: 'id',
iterations: 3,
memory: 4096,
parallelism: 1,
saltSize: 16,
},
/*
|--------------------------------------------------------------------------
| Bcrypt
|--------------------------------------------------------------------------
|
| Bcrypt mapping uses the `bcrypt` driver to hash values.
|
| Make sure you install the underlying dependency for this driver to work.
| https://www.npmjs.com/package/phc-bcrypt.
|
| npm install phc-bcrypt
|
*/
bcrypt: {
driver: 'bcrypt',
rounds: 10,
},
},
}
export default hashConfig

49
config/redis.ts Normal file
View File

@@ -0,0 +1,49 @@
/**
* Config source: https://git.io/JemcF
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import Env from '@ioc:Adonis/Core/Env'
import { RedisConfig } from '@ioc:Adonis/Addons/Redis'
/*
|--------------------------------------------------------------------------
| Redis configuration
|--------------------------------------------------------------------------
|
| Following is the configuration used by the Redis provider to connect to
| the redis server and execute redis commands.
|
| Do make sure to pre-define the connections type inside `contracts/redis.ts`
| file for AdonisJs to recognize connections.
|
| Make sure to check `contracts/redis.ts` file for defining extra connections
*/
const redisConfig: RedisConfig = {
connection: Env.get('REDIS_CONNECTION'),
connections: {
/*
|--------------------------------------------------------------------------
| The default connection
|--------------------------------------------------------------------------
|
| The main connection you want to use to execute redis commands. The same
| connection will be used by the session provider, if you rely on the
| redis driver.
|
*/
local: {
host: Env.get('REDIS_HOST'),
port: Env.get('REDIS_PORT'),
password: Env.get('REDIS_PASSWORD', ''),
db: 0,
keyPrefix: 'artclick:',
healthCheck: true
},
},
}
export default redisConfig

73
contracts/auth.ts Normal file
View File

@@ -0,0 +1,73 @@
/**
* Contract source: https://git.io/JvyKD
*
* Feel free to let us know via PR, if you find something broken in this
* file.
*/
import User from 'App/Models/User'
declare module '@ioc:Adonis/Addons/Auth' {
/*
|--------------------------------------------------------------------------
| Providers
|--------------------------------------------------------------------------
|
| The providers are used to fetch users. The Auth module comes pre-bundled
| with two providers that are `Lucid` and `Database`. Both uses database
| to fetch user details.
|
| You can also create and register your own custom providers.
|
*/
interface ProvidersList {
/*
|--------------------------------------------------------------------------
| User Provider
|--------------------------------------------------------------------------
|
| The following provider uses Lucid models as a driver for fetching user
| details from the database for authentication.
|
| You can create multiple providers using the same underlying driver with
| different Lucid models.
|
*/
user: {
implementation: LucidProviderContract<typeof User>,
config: LucidProviderConfig<typeof User>,
},
}
/*
|--------------------------------------------------------------------------
| Guards
|--------------------------------------------------------------------------
|
| The guards are used for authenticating users using different drivers.
| The auth module comes with 4 different guards.
|
| - SessionGuardContract
| - BasicAuthGuardContract
| - JwtGuardContract
| - OATGuardContract ( Opaque access token )
|
| Every guard needs a provider for looking up users from the database.
|
*/
interface GuardsList {
/*
|--------------------------------------------------------------------------
| OAT Guard
|--------------------------------------------------------------------------
|
| OAT, stands for (Opaque access tokens) guard uses database backed tokens
| to authenticate requests.
|
*/
api: {
implementation: OATGuardContract<'user', 'api'>,
config: OATGuardConfig<'user'>,
},
}
}

24
contracts/env.ts Normal file
View File

@@ -0,0 +1,24 @@
/**
* Contract source: https://git.io/JTm6U
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Core/Env' {
/*
|--------------------------------------------------------------------------
| Getting types for validated environment variables
|--------------------------------------------------------------------------
|
| The `default` export from the "../env.ts" file exports types for the
| validated environment variables. Here we merge them with the `EnvTypes`
| interface so that you can enjoy intellisense when using the "Env"
| module.
|
*/
type CustomTypes = typeof import("../env").default;
interface EnvTypes extends CustomTypes {
}
}

30
contracts/events.ts Normal file
View File

@@ -0,0 +1,30 @@
/**
* Contract source: https://git.io/JfefG
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Core/Event' {
/*
|--------------------------------------------------------------------------
| Define typed events
|--------------------------------------------------------------------------
|
| You can define types for events inside the following interface and
| AdonisJS will make sure that all listeners and emit calls adheres
| to the defined types.
|
| For example:
|
| interface EventsList {
| 'new:user': UserModel
| }
|
| Now calling `Event.emit('new:user')` will statically ensure that passed value is
| an instance of the the UserModel only.
|
*/
interface EventsList {
}
}

21
contracts/hash.ts Normal file
View File

@@ -0,0 +1,21 @@
/**
* Contract source: https://git.io/Jfefs
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Core/Hash' {
import { HashDrivers } from '@ioc:Adonis/Core/Hash'
interface HashersList {
bcrypt: {
config: BcryptConfig,
implementation: BcryptContract,
},
argon: {
config: ArgonConfig,
implementation: ArgonContract,
},
}
}

12
contracts/redis.ts Normal file
View File

@@ -0,0 +1,12 @@
/**
* Contract source: https://git.io/JemcN
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
declare module '@ioc:Adonis/Addons/Redis' {
interface RedisConnectionsList {
local: RedisConnectionConfig,
}
}

View File

@@ -0,0 +1 @@
// import Factory from '@ioc:Adonis/Lucid/Factory'

View File

@@ -0,0 +1,18 @@
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
export default class UsersSchema extends BaseSchema {
protected tableName = 'users'
public async up () {
this.schema.createTable(this.tableName, (table) => {
table.increments('id').primary()
table.string('email', 255).notNullable()
table.string('password', 180).notNullable()
table.timestamps(true)
})
}
public async down () {
this.schema.dropTable(this.tableName)
}
}

View File

@@ -0,0 +1,19 @@
import BaseSchema from '@ioc:Adonis/Lucid/Schema'
export default class Links extends BaseSchema {
protected tableName = 'links'
public async up () {
this.schema.createTable(this.tableName, (table) => {
table.increments('id')
table.string('code').notNullable()
table.string('target').notNullable()
table.integer('visit_count').defaultTo(0)
table.timestamps(true)
})
}
public async down () {
this.schema.dropTable(this.tableName)
}
}

34
env.ts Normal file
View File

@@ -0,0 +1,34 @@
/*
|--------------------------------------------------------------------------
| Validating Environment Variables
|--------------------------------------------------------------------------
|
| In this file we define the rules for validating environment variables.
| By performing validation we ensure that your application is running in
| a stable environment with correct configuration values.
|
| This file is read automatically by the framework during the boot lifecycle
| and hence do not rename or move this file to a different location.
|
*/
import Env from '@ioc:Adonis/Core/Env'
export default Env.rules({
HOST: Env.schema.string({ format: 'host' }),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
APP_NAME: Env.schema.string(),
NODE_ENV: Env.schema.enum(['development', 'production', 'testing'] as const),
REDIS_CONNECTION: Env.schema.enum(['local'] as const),
REDIS_HOST: Env.schema.string({ format: 'host' }),
REDIS_PORT: Env.schema.number(),
REDIS_PASSWORD: Env.schema.string.optional(),
MYSQL_HOST: Env.schema.string({ format: 'host' }),
MYSQL_PORT: Env.schema.number(),
MYSQL_USER: Env.schema.string(),
MYSQL_PASSWORD: Env.schema.string.optional(),
MYSQL_DB_NAME: Env.schema.string(),
})

30
package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "artclick",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "node ace build --production",
"start": "node server.js",
"dev": "node ace serve --watch"
},
"devDependencies": {
"@adonisjs/assembler": "^3.0.0",
"adonis-preset-ts": "^2.1.0",
"pino-pretty": "^4.7.1",
"typescript": "~4.1",
"youch": "^2.2.1",
"youch-terminal": "^1.1.0"
},
"dependencies": {
"@adonisjs/auth": "^5.1.1",
"@adonisjs/core": "~5.0.4-preview-rc",
"@adonisjs/lucid": "^10.0.0",
"@adonisjs/redis": "^5.0.9",
"@adonisjs/repl": "^1.0.0",
"luxon": "^1.26.0",
"mysql": "^2.18.1",
"proxy-addr": "^2.0.6",
"reflect-metadata": "^0.1.13",
"source-map-support": "^0.5.19"
}
}

24
providers/AppProvider.ts Normal file
View File

@@ -0,0 +1,24 @@
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
export default class AppProvider {
public static needsApplication = true
constructor (protected app: ApplicationContract) {
}
public register () {
// Register your own bindings
}
public async boot () {
// IoC container is ready
}
public async ready () {
// App is ready
}
public async shutdown () {
// Cleanup, since app is going down
}
}

21
server.ts Normal file
View File

@@ -0,0 +1,21 @@
/*
|--------------------------------------------------------------------------
| AdonisJs Server
|--------------------------------------------------------------------------
|
| The contents in this file is meant to bootstrap the AdonisJs application
| and start the HTTP server to accept incoming connections. You must avoid
| making this file dirty and instead make use of `lifecycle hooks` provided
| by AdonisJs service providers for custom code.
|
*/
import 'reflect-metadata'
import sourceMapSupport from 'source-map-support'
import { Ignitor } from '@adonisjs/core/build/standalone'
sourceMapSupport.install({ handleUncaughtExceptions: false })
new Ignitor(__dirname)
.httpServer()
.start()

46
start/kernel.ts Normal file
View File

@@ -0,0 +1,46 @@
/*
|--------------------------------------------------------------------------
| Application middleware
|--------------------------------------------------------------------------
|
| This file is used to define middleware for HTTP requests. You can register
| middleware as a `closure` or an IoC container binding. The bindings are
| preferred, since they keep this file clean.
|
*/
import Server from '@ioc:Adonis/Core/Server'
/*
|--------------------------------------------------------------------------
| Global middleware
|--------------------------------------------------------------------------
|
| An array of global middleware, that will be executed in the order they
| are defined for every HTTP requests.
|
*/
Server.middleware.register([
'Adonis/Core/BodyParserMiddleware',
'App/Middleware/SilentAuth',
])
/*
|--------------------------------------------------------------------------
| Named middleware
|--------------------------------------------------------------------------
|
| Named middleware are defined as key-value pair. The value is the namespace
| or middleware function and key is the alias. Later you can use these
| alias on individual routes. For example:
|
| { auth: 'App/Middleware/Auth' }
|
| and then use it as follows
|
| Route.get('dashboard', 'UserController.dashboard').middleware('auth')
|
*/
Server.middleware.registerNamed({
auth: 'App/Middleware/Auth',
})

47
start/routes.ts Normal file
View File

@@ -0,0 +1,47 @@
import Route from '@ioc:Adonis/Core/Route'
import HealthCheck from "@ioc:Adonis/Core/HealthCheck";
import {HttpContextContract} from "@ioc:Adonis/Core/HttpContext";
const BASE_URL = "https://go.arthurdanjou.fr"
Route.get('/', async ({response}: HttpContextContract) => {
return response.status(200).send({
domain: BASE_URL,
version: "1.0",
source: `${BASE_URL}/source`,
healthCheck: `${BASE_URL}/health`,
links: `${BASE_URL}/links`
})
})
Route.get('/source', async ({response}: HttpContextContract) => {
return response.redirect('https://github.com/arthurdanjou/artclick')
})
Route.get('health', async ({response}: HttpContextContract) => {
const report = await HealthCheck.getReport()
const isLive = await HealthCheck.isLive()
const isReady = await HealthCheck.isReady()
return report.healthy ? response.ok({ isLive, isReady, report: report.report }) : response.badRequest({ isLive, isReady, report: report.report })
})
Route
.group(() => {
Route.post('/login', 'UsersController.login')
Route.post('/logout', 'UsersController.logout')
Route.post('/token', 'UsersController.createInfiniteToken')
})
.prefix('auth')
Route
.group(() => {
Route.post('/create', 'LinksController.createLink')
Route.post('/update', 'LinksController.updateLink')
Route.post('/delete', 'LinksController.deleteLink')
})
.prefix('options')
.middleware('auth')
Route.get('/links', 'LinksController.getAllLinks') // --> Home
Route.get('/:id', 'LinksController.getLink') // --> Go to target's code
Route.get('/:id/count', 'LinksController.getVisitCount') // --> Get Visit count's code

36
tsconfig.json Normal file
View File

@@ -0,0 +1,36 @@
{
"extends": "./node_modules/adonis-preset-ts/tsconfig",
"include": [
"**/*"
],
"exclude": [
"node_modules",
"build"
],
"compilerOptions": {
"outDir": "build",
"rootDir": "./",
"sourceMap": true,
"paths": {
"App/*": [
"./app/*"
],
"Config/*": [
"./config/*"
],
"Contracts/*": [
"./contracts/*"
],
"Database/*": [
"./database/*"
]
},
"types": [
"@adonisjs/core",
"@adonisjs/repl",
"@adonisjs/auth",
"@adonisjs/redis",
"@adonisjs/lucid"
]
}
}

3647
yarn.lock Normal file

File diff suppressed because it is too large Load Diff