diff --git a/.editorconfig b/.editorconfig index b2697c4..db14be0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,7 @@ root = true [*] -indent_style = space +indent_style = tab indent_size = 2 end_of_line = lf charset = utf-8 @@ -17,8 +17,5 @@ insert_final_newline = unset indent_style = unset insert_final_newline = unset -[MakeFile] -indent_style = space - [*.md] trim_trailing_whitespace = false diff --git a/adonisrc.ts b/adonisrc.ts index 7e77f98..f39d7a1 100644 --- a/adonisrc.ts +++ b/adonisrc.ts @@ -1,7 +1,7 @@ import { defineConfig } from '@adonisjs/core/app'; export default defineConfig({ - /* + /* |-------------------------------------------------------------------------- | Commands |-------------------------------------------------------------------------- @@ -10,13 +10,13 @@ export default defineConfig({ | will be scanned automatically from the "./commands" directory. | */ - commands: [ - () => import('@adonisjs/core/commands'), - () => import('@adonisjs/lucid/commands'), - () => import('@izzyjs/route/commands'), - ], + commands: [ + () => import('@adonisjs/core/commands'), + () => import('@adonisjs/lucid/commands'), + () => import('@izzyjs/route/commands'), + ], - /* + /* |-------------------------------------------------------------------------- | Service providers |-------------------------------------------------------------------------- @@ -25,29 +25,29 @@ export default defineConfig({ | application | */ - providers: [ - () => import('@adonisjs/core/providers/app_provider'), - () => import('@adonisjs/core/providers/hash_provider'), - { - file: () => import('@adonisjs/core/providers/repl_provider'), - environment: ['repl', 'test'], - }, - () => import('@adonisjs/core/providers/vinejs_provider'), - () => import('@adonisjs/core/providers/edge_provider'), - () => import('@adonisjs/session/session_provider'), - () => import('@adonisjs/vite/vite_provider'), - () => import('@adonisjs/shield/shield_provider'), - () => import('@adonisjs/static/static_provider'), - () => import('@adonisjs/cors/cors_provider'), - () => import('@adonisjs/lucid/database_provider'), - () => import('@adonisjs/auth/auth_provider'), - () => import('@adonisjs/inertia/inertia_provider'), - () => import('@adonisjs/ally/ally_provider'), - () => import('@izzyjs/route/izzy_provider'), - () => import('#providers/route_provider'), - ], + providers: [ + () => import('@adonisjs/core/providers/app_provider'), + () => import('@adonisjs/core/providers/hash_provider'), + { + file: () => import('@adonisjs/core/providers/repl_provider'), + environment: ['repl', 'test'], + }, + () => import('@adonisjs/core/providers/vinejs_provider'), + () => import('@adonisjs/core/providers/edge_provider'), + () => import('@adonisjs/session/session_provider'), + () => import('@adonisjs/vite/vite_provider'), + () => import('@adonisjs/shield/shield_provider'), + () => import('@adonisjs/static/static_provider'), + () => import('@adonisjs/cors/cors_provider'), + () => import('@adonisjs/lucid/database_provider'), + () => import('@adonisjs/auth/auth_provider'), + () => import('@adonisjs/inertia/inertia_provider'), + () => import('@adonisjs/ally/ally_provider'), + () => import('@izzyjs/route/izzy_provider'), + () => import('#providers/route_provider'), + ], - /* + /* |-------------------------------------------------------------------------- | Preloads |-------------------------------------------------------------------------- @@ -55,9 +55,9 @@ export default defineConfig({ | List of modules to import before starting the application. | */ - preloads: [() => import('#start/routes'), () => import('#start/kernel')], + preloads: [() => import('#start/routes'), () => import('#start/kernel')], - /* + /* |-------------------------------------------------------------------------- | Tests |-------------------------------------------------------------------------- @@ -66,23 +66,23 @@ export default defineConfig({ | and add additional suites. | */ - tests: { - suites: [ - { - files: ['tests/unit/**/*.spec(.ts|.js)'], - name: 'unit', - timeout: 2000, - }, - { - files: ['tests/functional/**/*.spec(.ts|.js)'], - name: 'functional', - timeout: 30000, - }, - ], - forceExit: false, - }, + tests: { + suites: [ + { + files: ['tests/unit/**/*.spec(.ts|.js)'], + name: 'unit', + timeout: 2000, + }, + { + files: ['tests/functional/**/*.spec(.ts|.js)'], + name: 'functional', + timeout: 30000, + }, + ], + forceExit: false, + }, - /* + /* |-------------------------------------------------------------------------- | Metafiles |-------------------------------------------------------------------------- @@ -91,20 +91,20 @@ export default defineConfig({ | the production build. | */ - metaFiles: [ - { - pattern: 'resources/views/**/*.edge', - reloadServer: false, - }, - { - pattern: 'public/**', - reloadServer: false, - }, - ], + metaFiles: [ + { + pattern: 'resources/views/**/*.edge', + reloadServer: false, + }, + { + pattern: 'public/**', + reloadServer: false, + }, + ], - assetsBundler: false, - unstable_assembler: { - onBuildStarting: [() => import('@adonisjs/vite/build_hook')], - onDevServerStarted: [() => import('@izzyjs/route/dev_hook')], - }, + assetsBundler: false, + unstable_assembler: { + onBuildStarting: [() => import('@adonisjs/vite/build_hook')], + onDevServerStarted: [() => import('@izzyjs/route/dev_hook')], + }, }); diff --git a/app/constants/keys.ts b/app/constants/keys.ts index 9becb69..6a92cd8 100644 --- a/app/constants/keys.ts +++ b/app/constants/keys.ts @@ -8,12 +8,12 @@ const ARROW_UP = 'ArrowUp'; const ARROW_DOWN = 'ArrowDown'; const KEYS = { - ARROW_DOWN, - ARROW_UP, - ESCAPE_KEY, - OPEN_CREATE_COLLECTION_KEY, - OPEN_CREATE_LINK_KEY, - OPEN_SEARCH_KEY, + ARROW_DOWN, + ARROW_UP, + ESCAPE_KEY, + OPEN_CREATE_COLLECTION_KEY, + OPEN_CREATE_LINK_KEY, + OPEN_SEARCH_KEY, }; export default KEYS; diff --git a/app/constants/paths.ts b/app/constants/paths.ts index 811fb3b..b593040 100644 --- a/app/constants/paths.ts +++ b/app/constants/paths.ts @@ -1,8 +1,8 @@ const PATHS = { - AUTHOR: 'https://www.sonny.dev/', - REPO_GITHUB: 'https://github.com/Sonny93/my-links', - EXTENSION: - 'https://chromewebstore.google.com/detail/mylinks/agkmlplihacolkakgeccnbhphnepphma', + AUTHOR: 'https://www.sonny.dev/', + REPO_GITHUB: 'https://github.com/Sonny93/my-links', + EXTENSION: + 'https://chromewebstore.google.com/detail/mylinks/agkmlplihacolkakgeccnbhphnepphma', } as const; export default PATHS; diff --git a/app/controllers/admin_controller.ts b/app/controllers/admin_controller.ts index 6f5c90d..022920d 100644 --- a/app/controllers/admin_controller.ts +++ b/app/controllers/admin_controller.ts @@ -6,41 +6,41 @@ import { inject } from '@adonisjs/core'; import type { HttpContext } from '@adonisjs/core/http'; class UserWithRelationCountDto { - constructor(private user: User) {} + constructor(private user: User) {} - toJson = () => ({ - id: this.user.id, - email: this.user.email, - fullname: this.user.name, - avatarUrl: this.user.avatarUrl, - isAdmin: this.user.isAdmin, - createdAt: this.user.createdAt, - updatedAt: this.user.updatedAt, - count: { - link: Number(this.user.$extras.totalLinks), - collection: Number(this.user.$extras.totalCollections), - }, - }); + toJson = () => ({ + id: this.user.id, + email: this.user.email, + fullname: this.user.name, + avatarUrl: this.user.avatarUrl, + isAdmin: this.user.isAdmin, + createdAt: this.user.createdAt, + updatedAt: this.user.updatedAt, + count: { + link: Number(this.user.$extras.totalLinks), + collection: Number(this.user.$extras.totalCollections), + }, + }); } @inject() export default class AdminController { - constructor( - protected usersController: UsersController, - protected linksController: LinksController, - protected collectionsController: CollectionsController - ) {} + constructor( + protected usersController: UsersController, + protected linksController: LinksController, + protected collectionsController: CollectionsController + ) {} - async index({ inertia }: HttpContext) { - const users = await this.usersController.getAllUsersWithTotalRelations(); - const linksCount = await this.linksController.getTotalLinksCount(); - const collectionsCount = - await this.collectionsController.getTotalCollectionsCount(); + async index({ inertia }: HttpContext) { + const users = await this.usersController.getAllUsersWithTotalRelations(); + const linksCount = await this.linksController.getTotalLinksCount(); + const collectionsCount = + await this.collectionsController.getTotalCollectionsCount(); - return inertia.render('admin/dashboard', { - users: users.map((user) => new UserWithRelationCountDto(user).toJson()), - totalLinks: linksCount, - totalCollections: collectionsCount, - }); - } + return inertia.render('admin/dashboard', { + users: users.map((user) => new UserWithRelationCountDto(user).toJson()), + totalLinks: linksCount, + totalCollections: collectionsCount, + }); + } } diff --git a/app/controllers/apps_controller.ts b/app/controllers/apps_controller.ts index b452368..43e33b4 100644 --- a/app/controllers/apps_controller.ts +++ b/app/controllers/apps_controller.ts @@ -3,11 +3,11 @@ import { updateUserThemeValidator } from '#validators/user'; import type { HttpContext } from '@adonisjs/core/http'; export default class AppsController { - async updateUserTheme({ request, session, response }: HttpContext) { - const { preferDarkTheme } = await request.validateUsing( - updateUserThemeValidator - ); - session.put(PREFER_DARK_THEME, preferDarkTheme); - return response.ok({ message: 'ok' }); - } + async updateUserTheme({ request, session, response }: HttpContext) { + const { preferDarkTheme } = await request.validateUsing( + updateUserThemeValidator + ); + session.put(PREFER_DARK_THEME, preferDarkTheme); + return response.ok({ message: 'ok' }); + } } diff --git a/app/controllers/collections_controller.ts b/app/controllers/collections_controller.ts index 0586e53..39ccc18 100644 --- a/app/controllers/collections_controller.ts +++ b/app/controllers/collections_controller.ts @@ -1,143 +1,143 @@ import Collection from '#models/collection'; import User from '#models/user'; import { - createCollectionValidator, - deleteCollectionValidator, - updateCollectionValidator, + createCollectionValidator, + deleteCollectionValidator, + updateCollectionValidator, } from '#validators/collection'; import type { HttpContext } from '@adonisjs/core/http'; import db from '@adonisjs/lucid/services/db'; export default class CollectionsController { - // Dashboard - async index({ auth, inertia, request, response }: HttpContext) { - const collections = await this.getCollectionsByAuthorId(auth.user!.id); - if (collections.length === 0) { - return response.redirectToNamedRoute('collection.create-form'); - } + // Dashboard + async index({ auth, inertia, request, response }: HttpContext) { + const collections = await this.getCollectionsByAuthorId(auth.user!.id); + if (collections.length === 0) { + return response.redirectToNamedRoute('collection.create-form'); + } - const activeCollectionId = Number(request.qs()?.collectionId ?? ''); - const activeCollection = collections.find( - (c) => c.id === activeCollectionId - ); + const activeCollectionId = Number(request.qs()?.collectionId ?? ''); + const activeCollection = collections.find( + (c) => c.id === activeCollectionId + ); - if (!activeCollection && !!activeCollectionId) { - return response.redirectToNamedRoute('dashboard'); - } + if (!activeCollection && !!activeCollectionId) { + return response.redirectToNamedRoute('dashboard'); + } - // TODO: Create DTOs - return inertia.render('dashboard', { - collections: collections.map((collection) => collection.serialize()), - activeCollection: - activeCollection?.serialize() || collections[0].serialize(), - }); - } + // TODO: Create DTOs + return inertia.render('dashboard', { + collections: collections.map((collection) => collection.serialize()), + activeCollection: + activeCollection?.serialize() || collections[0].serialize(), + }); + } - // Create collection form - async showCreatePage({ inertia, auth }: HttpContext) { - const collections = await this.getCollectionsByAuthorId(auth.user!.id); - return inertia.render('collections/create', { - disableHomeLink: collections.length === 0, - }); - } + // Create collection form + async showCreatePage({ inertia, auth }: HttpContext) { + const collections = await this.getCollectionsByAuthorId(auth.user!.id); + return inertia.render('collections/create', { + disableHomeLink: collections.length === 0, + }); + } - // Method called when creating a collection - async store({ request, response, auth }: HttpContext) { - const payload = await request.validateUsing(createCollectionValidator); - const collection = await Collection.create({ - ...payload, - authorId: auth.user?.id!, - }); - return this.redirectToCollectionId(response, collection.id); - } + // Method called when creating a collection + async store({ request, response, auth }: HttpContext) { + const payload = await request.validateUsing(createCollectionValidator); + const collection = await Collection.create({ + ...payload, + authorId: auth.user?.id!, + }); + return this.redirectToCollectionId(response, collection.id); + } - async showEditPage({ auth, request, inertia, response }: HttpContext) { - const collectionId = request.qs()?.collectionId; - if (!collectionId) { - return response.redirectToNamedRoute('dashboard'); - } + async showEditPage({ auth, request, inertia, response }: HttpContext) { + const collectionId = request.qs()?.collectionId; + if (!collectionId) { + return response.redirectToNamedRoute('dashboard'); + } - const collection = await this.getCollectionById( - collectionId, - auth.user!.id - ); - return inertia.render('collections/edit', { - collection, - }); - } + const collection = await this.getCollectionById( + collectionId, + auth.user!.id + ); + return inertia.render('collections/edit', { + collection, + }); + } - async update({ request, auth, response }: HttpContext) { - const { params, ...payload } = await request.validateUsing( - updateCollectionValidator - ); + async update({ request, auth, response }: HttpContext) { + const { params, ...payload } = await request.validateUsing( + updateCollectionValidator + ); - // Cant use validator (vinejs) custom rule 'cause its too generic, - // because we have to find a collection by identifier and - // check whether the current user is the author. - // https://vinejs.dev/docs/extend/custom_rules - await this.getCollectionById(params.id, auth.user!.id); + // Cant use validator (vinejs) custom rule 'cause its too generic, + // because we have to find a collection by identifier and + // check whether the current user is the author. + // https://vinejs.dev/docs/extend/custom_rules + await this.getCollectionById(params.id, auth.user!.id); - await Collection.updateOrCreate( - { - id: params.id, - }, - payload - ); - return this.redirectToCollectionId(response, params.id); - } + await Collection.updateOrCreate( + { + id: params.id, + }, + payload + ); + return this.redirectToCollectionId(response, params.id); + } - async showDeletePage({ auth, request, inertia, response }: HttpContext) { - const collectionId = request.qs()?.collectionId; - if (!collectionId) { - return response.redirectToNamedRoute('dashboard'); - } + async showDeletePage({ auth, request, inertia, response }: HttpContext) { + const collectionId = request.qs()?.collectionId; + if (!collectionId) { + return response.redirectToNamedRoute('dashboard'); + } - const collection = await this.getCollectionById( - collectionId, - auth.user!.id - ); - return inertia.render('collections/delete', { - collection, - }); - } + const collection = await this.getCollectionById( + collectionId, + auth.user!.id + ); + return inertia.render('collections/delete', { + collection, + }); + } - async delete({ request, auth, response }: HttpContext) { - const { params } = await request.validateUsing(deleteCollectionValidator); - const collection = await this.getCollectionById(params.id, auth.user!.id); - await collection.delete(); - return response.redirectToNamedRoute('dashboard'); - } + async delete({ request, auth, response }: HttpContext) { + const { params } = await request.validateUsing(deleteCollectionValidator); + const collection = await this.getCollectionById(params.id, auth.user!.id); + await collection.delete(); + return response.redirectToNamedRoute('dashboard'); + } - async getTotalCollectionsCount() { - const totalCount = await db.from('collections').count('* as total'); - return Number(totalCount[0].total); - } + async getTotalCollectionsCount() { + const totalCount = await db.from('collections').count('* as total'); + return Number(totalCount[0].total); + } - /** - * Get collection by id. - * - * /!\ Only return private collection (create by the current user) - */ - async getCollectionById(id: Collection['id'], userId: User['id']) { - return await Collection.query() - .where('id', id) - .andWhere('author_id', userId) - .firstOrFail(); - } + /** + * Get collection by id. + * + * /!\ Only return private collection (create by the current user) + */ + async getCollectionById(id: Collection['id'], userId: User['id']) { + return await Collection.query() + .where('id', id) + .andWhere('author_id', userId) + .firstOrFail(); + } - async getCollectionsByAuthorId(authorId: User['id']) { - return await Collection.query() - .where('author_id', authorId) - .orderBy('created_at') - .preload('links'); - } + async getCollectionsByAuthorId(authorId: User['id']) { + return await Collection.query() + .where('author_id', authorId) + .orderBy('created_at') + .preload('links'); + } - redirectToCollectionId( - response: HttpContext['response'], - collectionId: Collection['id'] - ) { - return response.redirectToNamedRoute('dashboard', { - qs: { collectionId }, - }); - } + redirectToCollectionId( + response: HttpContext['response'], + collectionId: Collection['id'] + ) { + return response.redirectToNamedRoute('dashboard', { + qs: { collectionId }, + }); + } } diff --git a/app/controllers/favicons_controller.ts b/app/controllers/favicons_controller.ts index 95c990d..3094e9d 100644 --- a/app/controllers/favicons_controller.ts +++ b/app/controllers/favicons_controller.ts @@ -5,156 +5,156 @@ import logger from '@adonisjs/core/services/logger'; import { parse } from 'node-html-parser'; interface Favicon { - buffer: Buffer; - url: string; - type: string; - size: number; + buffer: Buffer; + url: string; + type: string; + size: number; } export default class FaviconsController { - private userAgent = - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0'; - private relList = [ - 'icon', - 'shortcut icon', - 'apple-touch-icon', - 'apple-touch-icon-precomposed', - 'apple-touch-startup-image', - 'mask-icon', - 'fluid-icon', - ]; + private userAgent = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0'; + private relList = [ + 'icon', + 'shortcut icon', + 'apple-touch-icon', + 'apple-touch-icon-precomposed', + 'apple-touch-startup-image', + 'mask-icon', + 'fluid-icon', + ]; - async index(ctx: HttpContext) { - const url = ctx.request.qs()?.url; - if (!url) { - throw new Error('Missing URL'); - } + async index(ctx: HttpContext) { + const url = ctx.request.qs()?.url; + if (!url) { + throw new Error('Missing URL'); + } - const cacheNs = cache.namespace('favicon'); - const favicon = await cacheNs.getOrSet({ - key: url, - ttl: '1h', - factory: () => this.tryGetFavicon(url), - }); - return this.sendImage(ctx, favicon); - } + const cacheNs = cache.namespace('favicon'); + const favicon = await cacheNs.getOrSet({ + key: url, + ttl: '1h', + factory: () => this.tryGetFavicon(url), + }); + return this.sendImage(ctx, favicon); + } - private async tryGetFavicon(url: string): Promise { - const faviconUrl = this.buildFaviconUrl(url, '/favicon.ico'); - try { - return await this.fetchFavicon(faviconUrl); - } catch { - logger.debug(`Unable to retrieve favicon from ${faviconUrl}`); - } + private async tryGetFavicon(url: string): Promise { + const faviconUrl = this.buildFaviconUrl(url, '/favicon.ico'); + try { + return await this.fetchFavicon(faviconUrl); + } catch { + logger.debug(`Unable to retrieve favicon from ${faviconUrl}`); + } - const documentText = await this.fetchDocumentText(url); - const faviconPath = this.extractFaviconPath(documentText); + const documentText = await this.fetchDocumentText(url); + const faviconPath = this.extractFaviconPath(documentText); - if (!faviconPath) { - throw new FaviconNotFoundException(`No favicon path found in ${url}`); - } + if (!faviconPath) { + throw new FaviconNotFoundException(`No favicon path found in ${url}`); + } - if (faviconPath.startsWith('http')) { - try { - return await this.fetchFavicon(faviconPath); - } catch { - logger.debug(`Unable to retrieve favicon from ${faviconPath}`); - } - } + if (faviconPath.startsWith('http')) { + try { + return await this.fetchFavicon(faviconPath); + } catch { + logger.debug(`Unable to retrieve favicon from ${faviconPath}`); + } + } - return this.fetchFaviconFromPath(url, faviconPath); - } + return this.fetchFaviconFromPath(url, faviconPath); + } - private async fetchFavicon(url: string): Promise { - const response = await this.fetchWithUserAgent(url); - if (!response.ok) { - throw new FaviconNotFoundException(`Request to favicon ${url} failed`); - } + private async fetchFavicon(url: string): Promise { + const response = await this.fetchWithUserAgent(url); + if (!response.ok) { + throw new FaviconNotFoundException(`Request to favicon ${url} failed`); + } - const blob = await response.blob(); - if (!this.isImage(blob.type) || blob.size === 0) { - throw new FaviconNotFoundException(`Invalid image at ${url}`); - } + const blob = await response.blob(); + if (!this.isImage(blob.type) || blob.size === 0) { + throw new FaviconNotFoundException(`Invalid image at ${url}`); + } - return { - buffer: Buffer.from(await blob.arrayBuffer()), - url: response.url, - type: blob.type, - size: blob.size, - }; - } + return { + buffer: Buffer.from(await blob.arrayBuffer()), + url: response.url, + type: blob.type, + size: blob.size, + }; + } - private async fetchDocumentText(url: string): Promise { - const response = await this.fetchWithUserAgent(url); - if (!response.ok) { - throw new FaviconNotFoundException(`Request to ${url} failed`); - } + private async fetchDocumentText(url: string): Promise { + const response = await this.fetchWithUserAgent(url); + if (!response.ok) { + throw new FaviconNotFoundException(`Request to ${url} failed`); + } - return await response.text(); - } + return await response.text(); + } - private extractFaviconPath(html: string): string | undefined { - const document = parse(html); - const link = document - .getElementsByTagName('link') - .find((element) => this.relList.includes(element.getAttribute('rel')!)); - return link?.getAttribute('href'); - } + private extractFaviconPath(html: string): string | undefined { + const document = parse(html); + const link = document + .getElementsByTagName('link') + .find((element) => this.relList.includes(element.getAttribute('rel')!)); + return link?.getAttribute('href'); + } - private async fetchFaviconFromPath( - baseUrl: string, - path: string - ): Promise { - if (this.isBase64Image(path)) { - const buffer = this.convertBase64ToBuffer(path); - return { - buffer, - type: 'image/x-icon', - size: buffer.length, - url: path, - }; - } + private async fetchFaviconFromPath( + baseUrl: string, + path: string + ): Promise { + if (this.isBase64Image(path)) { + const buffer = this.convertBase64ToBuffer(path); + return { + buffer, + type: 'image/x-icon', + size: buffer.length, + url: path, + }; + } - const faviconUrl = this.buildFaviconUrl(baseUrl, path); - return this.fetchFavicon(faviconUrl); - } + const faviconUrl = this.buildFaviconUrl(baseUrl, path); + return this.fetchFavicon(faviconUrl); + } - private buildFaviconUrl(base: string, path: string): string { - const { origin } = new URL(base); - if (path.startsWith('/')) { - return origin + path; - } + private buildFaviconUrl(base: string, path: string): string { + const { origin } = new URL(base); + if (path.startsWith('/')) { + return origin + path; + } - const basePath = this.urlWithoutSearchParams(base); - const baseUrl = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath; - return `${baseUrl}/${path}`; - } + const basePath = this.urlWithoutSearchParams(base); + const baseUrl = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath; + return `${baseUrl}/${path}`; + } - private urlWithoutSearchParams(url: string): string { - const { protocol, host, pathname } = new URL(url); - return `${protocol}//${host}${pathname}`; - } + private urlWithoutSearchParams(url: string): string { + const { protocol, host, pathname } = new URL(url); + return `${protocol}//${host}${pathname}`; + } - private isImage(type: string): boolean { - return type.startsWith('image/'); - } + private isImage(type: string): boolean { + return type.startsWith('image/'); + } - private isBase64Image(data: string): boolean { - return data.startsWith('data:image/'); - } + private isBase64Image(data: string): boolean { + return data.startsWith('data:image/'); + } - private convertBase64ToBuffer(base64: string): Buffer { - return Buffer.from(base64.split(',')[1], 'base64'); - } + private convertBase64ToBuffer(base64: string): Buffer { + return Buffer.from(base64.split(',')[1], 'base64'); + } - private async fetchWithUserAgent(url: string): Promise { - const headers = new Headers({ 'User-Agent': this.userAgent }); - return fetch(url, { headers }); - } + private async fetchWithUserAgent(url: string): Promise { + const headers = new Headers({ 'User-Agent': this.userAgent }); + return fetch(url, { headers }); + } - private sendImage(ctx: HttpContext, { buffer, type, size }: Favicon) { - ctx.response.header('Content-Type', type); - ctx.response.header('Content-Length', size.toString()); - ctx.response.send(buffer, true); - } + private sendImage(ctx: HttpContext, { buffer, type, size }: Favicon) { + ctx.response.header('Content-Type', type); + ctx.response.header('Content-Length', size.toString()); + ctx.response.send(buffer, true); + } } diff --git a/app/controllers/links_controller.ts b/app/controllers/links_controller.ts index a51e064..ce36361 100644 --- a/app/controllers/links_controller.ts +++ b/app/controllers/links_controller.ts @@ -1,10 +1,10 @@ import CollectionsController from '#controllers/collections_controller'; import Link from '#models/link'; import { - createLinkValidator, - deleteLinkValidator, - updateLinkFavoriteStatusValidator, - updateLinkValidator, + createLinkValidator, + deleteLinkValidator, + updateLinkFavoriteStatusValidator, + updateLinkValidator, } from '#validators/link'; import { inject } from '@adonisjs/core'; import type { HttpContext } from '@adonisjs/core/http'; @@ -12,120 +12,120 @@ import db from '@adonisjs/lucid/services/db'; @inject() export default class LinksController { - constructor(protected collectionsController: CollectionsController) {} + constructor(protected collectionsController: CollectionsController) {} - async showCreatePage({ auth, inertia }: HttpContext) { - const collections = - await this.collectionsController.getCollectionsByAuthorId(auth.user!.id); - return inertia.render('links/create', { collections }); - } + async showCreatePage({ auth, inertia }: HttpContext) { + const collections = + await this.collectionsController.getCollectionsByAuthorId(auth.user!.id); + return inertia.render('links/create', { collections }); + } - async store({ auth, request, response }: HttpContext) { - const { collectionId, ...payload } = - await request.validateUsing(createLinkValidator); + async store({ auth, request, response }: HttpContext) { + const { collectionId, ...payload } = + await request.validateUsing(createLinkValidator); - await this.collectionsController.getCollectionById( - collectionId, - auth.user!.id - ); - await Link.create({ - ...payload, - collectionId, - authorId: auth.user?.id!, - }); - return this.collectionsController.redirectToCollectionId( - response, - collectionId - ); - } + await this.collectionsController.getCollectionById( + collectionId, + auth.user!.id + ); + await Link.create({ + ...payload, + collectionId, + authorId: auth.user?.id!, + }); + return this.collectionsController.redirectToCollectionId( + response, + collectionId + ); + } - async showEditPage({ auth, inertia, request, response }: HttpContext) { - const linkId = request.qs()?.linkId; - if (!linkId) { - return response.redirectToNamedRoute('dashboard'); - } + async showEditPage({ auth, inertia, request, response }: HttpContext) { + const linkId = request.qs()?.linkId; + if (!linkId) { + return response.redirectToNamedRoute('dashboard'); + } - const userId = auth.user!.id; - const collections = - await this.collectionsController.getCollectionsByAuthorId(userId); - const link = await this.getLinkById(linkId, userId); + const userId = auth.user!.id; + const collections = + await this.collectionsController.getCollectionsByAuthorId(userId); + const link = await this.getLinkById(linkId, userId); - return inertia.render('links/edit', { collections, link }); - } + return inertia.render('links/edit', { collections, link }); + } - async update({ request, auth, response }: HttpContext) { - const { params, ...payload } = - await request.validateUsing(updateLinkValidator); + async update({ request, auth, response }: HttpContext) { + const { params, ...payload } = + await request.validateUsing(updateLinkValidator); - // Throw if invalid link id provided - await this.getLinkById(params.id, auth.user!.id); + // Throw if invalid link id provided + await this.getLinkById(params.id, auth.user!.id); - await Link.updateOrCreate( - { - id: params.id, - }, - payload - ); + await Link.updateOrCreate( + { + id: params.id, + }, + payload + ); - return response.redirectToNamedRoute('dashboard', { - qs: { collectionId: payload.collectionId }, - }); - } + return response.redirectToNamedRoute('dashboard', { + qs: { collectionId: payload.collectionId }, + }); + } - async toggleFavorite({ request, auth, response }: HttpContext) { - const { params, favorite } = await request.validateUsing( - updateLinkFavoriteStatusValidator - ); + async toggleFavorite({ request, auth, response }: HttpContext) { + const { params, favorite } = await request.validateUsing( + updateLinkFavoriteStatusValidator + ); - // Throw if invalid link id provided - await this.getLinkById(params.id, auth.user!.id); + // Throw if invalid link id provided + await this.getLinkById(params.id, auth.user!.id); - await Link.updateOrCreate( - { - id: params.id, - }, - { favorite } - ); + await Link.updateOrCreate( + { + id: params.id, + }, + { favorite } + ); - return response.json({ status: 'ok' }); - } + return response.json({ status: 'ok' }); + } - async showDeletePage({ auth, inertia, request, response }: HttpContext) { - const linkId = request.qs()?.linkId; - if (!linkId) { - return response.redirectToNamedRoute('dashboard'); - } + async showDeletePage({ auth, inertia, request, response }: HttpContext) { + const linkId = request.qs()?.linkId; + if (!linkId) { + return response.redirectToNamedRoute('dashboard'); + } - const link = await this.getLinkById(linkId, auth.user!.id); - await link.load('collection'); - return inertia.render('links/delete', { link }); - } + const link = await this.getLinkById(linkId, auth.user!.id); + await link.load('collection'); + return inertia.render('links/delete', { link }); + } - async delete({ request, auth, response }: HttpContext) { - const { params } = await request.validateUsing(deleteLinkValidator); + async delete({ request, auth, response }: HttpContext) { + const { params } = await request.validateUsing(deleteLinkValidator); - const link = await this.getLinkById(params.id, auth.user!.id); - await link.delete(); + const link = await this.getLinkById(params.id, auth.user!.id); + await link.delete(); - return response.redirectToNamedRoute('dashboard', { - qs: { collectionId: link.id }, - }); - } + return response.redirectToNamedRoute('dashboard', { + qs: { collectionId: link.id }, + }); + } - async getTotalLinksCount() { - const totalCount = await db.from('links').count('* as total'); - return Number(totalCount[0].total); - } + async getTotalLinksCount() { + const totalCount = await db.from('links').count('* as total'); + return Number(totalCount[0].total); + } - /** - * Get link by id. - * - * /!\ Only return private link (create by the current user) - */ - private async getLinkById(id: Link['id'], userId: Link['id']) { - return await Link.query() - .where('id', id) - .andWhere('author_id', userId) - .firstOrFail(); - } + /** + * Get link by id. + * + * /!\ Only return private link (create by the current user) + */ + private async getLinkById(id: Link['id'], userId: Link['id']) { + return await Link.query() + .where('id', id) + .andWhere('author_id', userId) + .firstOrFail(); + } } diff --git a/app/controllers/searches_controller.ts b/app/controllers/searches_controller.ts index 5645cda..91db28e 100644 --- a/app/controllers/searches_controller.ts +++ b/app/controllers/searches_controller.ts @@ -2,17 +2,17 @@ import type { HttpContext } from '@adonisjs/core/http'; import db from '@adonisjs/lucid/services/db'; export default class SearchesController { - async search({ request, auth }: HttpContext) { - const term = request.qs()?.term; - if (!term) { - console.warn('qs term null'); - return { error: 'missing "term" query param' }; - } + async search({ request, auth }: HttpContext) { + const term = request.qs()?.term; + if (!term) { + console.warn('qs term null'); + return { error: 'missing "term" query param' }; + } - const { rows } = await db.rawQuery('SELECT * FROM search_text(?, ?)', [ - term, - auth.user!.id, - ]); - return { results: rows }; - } + const { rows } = await db.rawQuery('SELECT * FROM search_text(?, ?)', [ + term, + auth.user!.id, + ]); + return { results: rows }; + } } diff --git a/app/controllers/shared_collections_controller.ts b/app/controllers/shared_collections_controller.ts index ac94ddf..6354f96 100644 --- a/app/controllers/shared_collections_controller.ts +++ b/app/controllers/shared_collections_controller.ts @@ -4,21 +4,21 @@ import { getSharedCollectionValidator } from '#validators/shared_collection'; import type { HttpContext } from '@adonisjs/core/http'; export default class SharedCollectionsController { - async index({ request, inertia }: HttpContext) { - const { params } = await request.validateUsing( - getSharedCollectionValidator - ); + async index({ request, inertia }: HttpContext) { + const { params } = await request.validateUsing( + getSharedCollectionValidator + ); - const collection = await this.getSharedCollectionById(params.id); - return inertia.render('shared', { collection }); - } + const collection = await this.getSharedCollectionById(params.id); + return inertia.render('shared', { collection }); + } - private async getSharedCollectionById(id: Collection['id']) { - return await Collection.query() - .where('id', id) - .andWhere('visibility', Visibility.PUBLIC) - .preload('links') - .preload('author') - .firstOrFail(); - } + private async getSharedCollectionById(id: Collection['id']) { + return await Collection.query() + .where('id', id) + .andWhere('visibility', Visibility.PUBLIC) + .preload('links') + .preload('author') + .firstOrFail(); + } } diff --git a/app/controllers/users_controller.ts b/app/controllers/users_controller.ts index 244df59..c8df55f 100644 --- a/app/controllers/users_controller.ts +++ b/app/controllers/users_controller.ts @@ -5,74 +5,74 @@ import db from '@adonisjs/lucid/services/db'; import { RouteName } from '@izzyjs/route/types'; export default class UsersController { - private redirectTo: RouteName = 'auth.login'; + private redirectTo: RouteName = 'auth.login'; - login({ inertia }: HttpContext) { - return inertia.render('login'); - } + login({ inertia }: HttpContext) { + return inertia.render('login'); + } - google = ({ ally }: HttpContext) => ally.use('google').redirect(); + google = ({ ally }: HttpContext) => ally.use('google').redirect(); - async callbackAuth({ ally, auth, response, session }: HttpContext) { - const google = ally.use('google'); - if (google.accessDenied()) { - // TODO: translate error messages + show them in UI - session.flash('flash', 'Access was denied'); - return response.redirectToNamedRoute(this.redirectTo); - } + async callbackAuth({ ally, auth, response, session }: HttpContext) { + const google = ally.use('google'); + if (google.accessDenied()) { + // TODO: translate error messages + show them in UI + session.flash('flash', 'Access was denied'); + return response.redirectToNamedRoute(this.redirectTo); + } - if (google.stateMisMatch()) { - session.flash('flash', 'Request expired. Retry again'); - return response.redirectToNamedRoute(this.redirectTo); - } + if (google.stateMisMatch()) { + session.flash('flash', 'Request expired. Retry again'); + return response.redirectToNamedRoute(this.redirectTo); + } - if (google.hasError()) { - session.flash('flash', google.getError() || 'Something went wrong'); - return response.redirectToNamedRoute(this.redirectTo); - } + if (google.hasError()) { + session.flash('flash', google.getError() || 'Something went wrong'); + return response.redirectToNamedRoute(this.redirectTo); + } - const userCount = await db.from('users').count('* as total'); - const { - email, - id: providerId, - name, - nickName, - avatarUrl, - token, - } = await google.user(); - const user = await User.updateOrCreate( - { - email, - }, - { - email, - providerId, - name, - nickName, - avatarUrl, - token, - providerType: 'google', - isAdmin: userCount[0].total === '0' ? true : undefined, - } - ); + const userCount = await db.from('users').count('* as total'); + const { + email, + id: providerId, + name, + nickName, + avatarUrl, + token, + } = await google.user(); + const user = await User.updateOrCreate( + { + email, + }, + { + email, + providerId, + name, + nickName, + avatarUrl, + token, + providerType: 'google', + isAdmin: userCount[0].total === '0' ? true : undefined, + } + ); - await auth.use('web').login(user); - session.flash('flash', 'Successfully authenticated'); - logger.info(`[${user.email}] auth success`); + await auth.use('web').login(user); + session.flash('flash', 'Successfully authenticated'); + logger.info(`[${user.email}] auth success`); - response.redirectToNamedRoute('dashboard'); - } + response.redirectToNamedRoute('dashboard'); + } - async logout({ auth, response, session }: HttpContext) { - await auth.use('web').logout(); - session.flash('flash', 'Successfully disconnected'); - logger.info(`[${auth.user?.email}] disconnected successfully`); - response.redirectToNamedRoute(this.redirectTo); - } + async logout({ auth, response, session }: HttpContext) { + await auth.use('web').logout(); + session.flash('flash', 'Successfully disconnected'); + logger.info(`[${auth.user?.email}] disconnected successfully`); + response.redirectToNamedRoute(this.redirectTo); + } - async getAllUsersWithTotalRelations() { - return User.query() - .withCount('collections', (q) => q.as('totalCollections')) - .withCount('links', (q) => q.as('totalLinks')); - } + async getAllUsersWithTotalRelations() { + return User.query() + .withCount('collections', (q) => q.as('totalCollections')) + .withCount('links', (q) => q.as('totalLinks')); + } } diff --git a/app/enums/visibility.ts b/app/enums/visibility.ts index 862b3e0..e65ee5a 100644 --- a/app/enums/visibility.ts +++ b/app/enums/visibility.ts @@ -1,4 +1,4 @@ export enum Visibility { - PUBLIC = 'PUBLIC', - PRIVATE = 'PRIVATE', + PUBLIC = 'PUBLIC', + PRIVATE = 'PRIVATE', } diff --git a/app/exceptions/favicon_not_found_exception.ts b/app/exceptions/favicon_not_found_exception.ts index 5dadd52..cc96862 100644 --- a/app/exceptions/favicon_not_found_exception.ts +++ b/app/exceptions/favicon_not_found_exception.ts @@ -5,16 +5,16 @@ import { createReadStream } from 'node:fs'; import { resolve } from 'node:path'; export default class FaviconNotFoundException extends Exception { - static status = 404; - static code = 'E_FAVICON_NOT_FOUND'; + static status = 404; + static code = 'E_FAVICON_NOT_FOUND'; - async handle(error: this, ctx: HttpContext) { - const readStream = createReadStream( - resolve(process.cwd(), './public/empty-image.png') - ); + async handle(error: this, ctx: HttpContext) { + const readStream = createReadStream( + resolve(process.cwd(), './public/empty-image.png') + ); - ctx.response.header('Content-Type', 'image/png'); - ctx.response.stream(readStream); - logger.debug(error.message); - } + ctx.response.header('Content-Type', 'image/png'); + ctx.response.stream(readStream); + logger.debug(error.message); + } } diff --git a/app/exceptions/handler.ts b/app/exceptions/handler.ts index 27fc76b..79ad3df 100644 --- a/app/exceptions/handler.ts +++ b/app/exceptions/handler.ts @@ -1,54 +1,54 @@ import { ExceptionHandler, HttpContext } from '@adonisjs/core/http'; import app from '@adonisjs/core/services/app'; import type { - StatusPageRange, - StatusPageRenderer, + StatusPageRange, + StatusPageRenderer, } from '@adonisjs/core/types/http'; import { errors } from '@adonisjs/lucid'; 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; + /** + * 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 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 = { - '404': (error, { inertia }) => - inertia.render('errors/not_found', { error }), - '500..599': (error, { inertia }) => - inertia.render('errors/server_error', { error }), - }; + /** + * 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 = { + '404': (error, { inertia }) => + inertia.render('errors/not_found', { error }), + '500..599': (error, { inertia }) => + inertia.render('errors/server_error', { error }), + }; - /** - * The method is used for handling errors and returning - * response to the client - */ - async handle(error: unknown, ctx: HttpContext) { - if (error instanceof errors.E_ROW_NOT_FOUND) { - return ctx.response.redirectToNamedRoute('dashboard'); - } - return super.handle(error, ctx); - } + /** + * The method is used for handling errors and returning + * response to the client + */ + async handle(error: unknown, ctx: HttpContext) { + if (error instanceof errors.E_ROW_NOT_FOUND) { + return ctx.response.redirectToNamedRoute('dashboard'); + } + 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); - } + /** + * 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); + } } diff --git a/app/lib/cache.ts b/app/lib/cache.ts index f74035f..5caeace 100644 --- a/app/lib/cache.ts +++ b/app/lib/cache.ts @@ -2,9 +2,9 @@ import { BentoCache, bentostore } from 'bentocache'; import { memoryDriver } from 'bentocache/drivers/memory'; export const cache = new BentoCache({ - default: 'cache', + default: 'cache', - stores: { - cache: bentostore().useL1Layer(memoryDriver({ maxSize: 10_000 })), - }, + stores: { + cache: bentostore().useL1Layer(memoryDriver({ maxSize: 10_000 })), + }, }); diff --git a/app/middleware/admin_middleware.ts b/app/middleware/admin_middleware.ts index b7f2467..8a24f32 100644 --- a/app/middleware/admin_middleware.ts +++ b/app/middleware/admin_middleware.ts @@ -2,10 +2,10 @@ import type { HttpContext } from '@adonisjs/core/http'; import type { NextFn } from '@adonisjs/core/types/http'; export default class AdminMiddleware { - async handle(ctx: HttpContext, next: NextFn) { - if (!ctx.auth.user?.isAdmin) { - return ctx.response.redirectToNamedRoute('dashboard'); - } - return next(); - } + async handle(ctx: HttpContext, next: NextFn) { + if (!ctx.auth.user?.isAdmin) { + return ctx.response.redirectToNamedRoute('dashboard'); + } + return next(); + } } diff --git a/app/middleware/auth_middleware.ts b/app/middleware/auth_middleware.ts index 37d881e..afb149e 100644 --- a/app/middleware/auth_middleware.ts +++ b/app/middleware/auth_middleware.ts @@ -8,21 +8,21 @@ import { route } from '@izzyjs/route/client'; * access to unauthenticated users. */ export default class AuthMiddleware { - /** - * The URL to redirect to, when authentication fails - */ - redirectTo = route('auth.login').url; + /** + * The URL to redirect to, when authentication fails + */ + redirectTo = route('auth.login').url; - async handle( - ctx: HttpContext, - next: NextFn, - options: { - guards?: (keyof Authenticators)[]; - } = {} - ) { - await ctx.auth.authenticateUsing(options.guards, { - loginRoute: this.redirectTo, - }); - return next(); - } + async handle( + ctx: HttpContext, + next: NextFn, + options: { + guards?: (keyof Authenticators)[]; + } = {} + ) { + await ctx.auth.authenticateUsing(options.guards, { + loginRoute: this.redirectTo, + }); + return next(); + } } diff --git a/app/middleware/container_bindings_middleware.ts b/app/middleware/container_bindings_middleware.ts index 0195e45..a138869 100644 --- a/app/middleware/container_bindings_middleware.ts +++ b/app/middleware/container_bindings_middleware.ts @@ -10,10 +10,10 @@ import { NextFn } from '@adonisjs/core/types/http'; * - 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); + handle(ctx: HttpContext, next: NextFn) { + ctx.containerResolver.bindValue(HttpContext, ctx); + ctx.containerResolver.bindValue(Logger, ctx.logger); - return next(); - } + return next(); + } } diff --git a/app/middleware/guest_middleware.ts b/app/middleware/guest_middleware.ts index 4464243..6b2e43a 100644 --- a/app/middleware/guest_middleware.ts +++ b/app/middleware/guest_middleware.ts @@ -10,22 +10,22 @@ import type { Authenticators } from '@adonisjs/auth/types'; * is already logged-in */ export default class GuestMiddleware { - /** - * The URL to redirect to when user is logged-in - */ - redirectTo = '/'; + /** + * The URL to redirect to when user is logged-in + */ + redirectTo = '/'; - 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()) { - return ctx.response.redirect(this.redirectTo, true); - } - } + 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()) { + return ctx.response.redirect(this.redirectTo, true); + } + } - return next(); - } + return next(); + } } diff --git a/app/middleware/log_request.ts b/app/middleware/log_request.ts index e86b63c..24a8b8f 100644 --- a/app/middleware/log_request.ts +++ b/app/middleware/log_request.ts @@ -2,16 +2,16 @@ import { HttpContext } from '@adonisjs/core/http'; import logger from '@adonisjs/core/services/logger'; export default class LogRequest { - async handle({ request }: HttpContext, next: () => Promise) { - if ( - !request.url().startsWith('/node_modules') && - !request.url().startsWith('/inertia') && - !request.url().startsWith('/@vite') && - !request.url().startsWith('/@react-refresh') && - !request.url().includes('.ts') - ) { - logger.debug(`[${request.method()}]: ${request.url()}`); - } - await next(); - } + async handle({ request }: HttpContext, next: () => Promise) { + if ( + !request.url().startsWith('/node_modules') && + !request.url().startsWith('/inertia') && + !request.url().startsWith('/@vite') && + !request.url().startsWith('/@react-refresh') && + !request.url().includes('.ts') + ) { + logger.debug(`[${request.method()}]: ${request.url()}`); + } + await next(); + } } diff --git a/app/models/app_base_model.ts b/app/models/app_base_model.ts index b3a5f72..857257c 100644 --- a/app/models/app_base_model.ts +++ b/app/models/app_base_model.ts @@ -1,25 +1,25 @@ import { - BaseModel, - CamelCaseNamingStrategy, - column, + BaseModel, + CamelCaseNamingStrategy, + column, } from '@adonisjs/lucid/orm'; import { DateTime } from 'luxon'; export default class AppBaseModel extends BaseModel { - static namingStrategy = new CamelCaseNamingStrategy(); - serializeExtras = true; + static namingStrategy = new CamelCaseNamingStrategy(); + serializeExtras = true; - @column({ isPrimary: true }) - declare id: number; + @column({ isPrimary: true }) + declare id: number; - @column.dateTime({ - autoCreate: true, - }) - declare createdAt: DateTime; + @column.dateTime({ + autoCreate: true, + }) + declare createdAt: DateTime; - @column.dateTime({ - autoCreate: true, - autoUpdate: true, - }) - declare updatedAt: DateTime; + @column.dateTime({ + autoCreate: true, + autoUpdate: true, + }) + declare updatedAt: DateTime; } diff --git a/app/models/collection.ts b/app/models/collection.ts index 33e9853..8268083 100644 --- a/app/models/collection.ts +++ b/app/models/collection.ts @@ -6,24 +6,24 @@ import type { BelongsTo, HasMany } from '@adonisjs/lucid/types/relations'; import { Visibility } from '#enums/visibility'; export default class Collection extends AppBaseModel { - @column() - declare name: string; + @column() + declare name: string; - @column() - declare description: string | null; + @column() + declare description: string | null; - @column() - declare visibility: Visibility; + @column() + declare visibility: Visibility; - @column() - declare nextId: number; + @column() + declare nextId: number; - @column() - declare authorId: number; + @column() + declare authorId: number; - @belongsTo(() => User, { foreignKey: 'authorId' }) - declare author: BelongsTo; + @belongsTo(() => User, { foreignKey: 'authorId' }) + declare author: BelongsTo; - @hasMany(() => Link) - declare links: HasMany; + @hasMany(() => Link) + declare links: HasMany; } diff --git a/app/models/link.ts b/app/models/link.ts index 0c13584..91c5e92 100644 --- a/app/models/link.ts +++ b/app/models/link.ts @@ -5,27 +5,27 @@ import { belongsTo, column } from '@adonisjs/lucid/orm'; import type { BelongsTo } from '@adonisjs/lucid/types/relations'; export default class Link extends AppBaseModel { - @column() - declare name: string; + @column() + declare name: string; - @column() - declare description: string | null; + @column() + declare description: string | null; - @column() - declare url: string; + @column() + declare url: string; - @column() - declare favorite: boolean; + @column() + declare favorite: boolean; - @column() - declare collectionId: number; + @column() + declare collectionId: number; - @belongsTo(() => Collection, { foreignKey: 'collectionId' }) - declare collection: BelongsTo; + @belongsTo(() => Collection, { foreignKey: 'collectionId' }) + declare collection: BelongsTo; - @column() - declare authorId: number; + @column() + declare authorId: number; - @belongsTo(() => User, { foreignKey: 'authorId' }) - declare author: BelongsTo; + @belongsTo(() => User, { foreignKey: 'authorId' }) + declare author: BelongsTo; } diff --git a/app/models/user.ts b/app/models/user.ts index 8922270..8afe92b 100644 --- a/app/models/user.ts +++ b/app/models/user.ts @@ -6,42 +6,42 @@ import type { HasMany } from '@adonisjs/lucid/types/relations'; import AppBaseModel from './app_base_model.js'; export default class User extends AppBaseModel { - @column() - declare email: string; + @column() + declare email: string; - @column() - declare name: string; + @column() + declare name: string; - @column() - declare nickName: string; // public username + @column() + declare nickName: string; // public username - @column() - declare avatarUrl: string; + @column() + declare avatarUrl: string; - @column() - declare isAdmin: boolean; + @column() + declare isAdmin: boolean; - @column({ serializeAs: null }) - declare token?: GoogleToken; + @column({ serializeAs: null }) + declare token?: GoogleToken; - @column({ serializeAs: null }) - declare providerId: number; + @column({ serializeAs: null }) + declare providerId: number; - @column({ serializeAs: null }) - declare providerType: 'google'; + @column({ serializeAs: null }) + declare providerType: 'google'; - @hasMany(() => Collection, { - foreignKey: 'authorId', - }) - declare collections: HasMany; + @hasMany(() => Collection, { + foreignKey: 'authorId', + }) + declare collections: HasMany; - @hasMany(() => Link, { - foreignKey: 'authorId', - }) - declare links: HasMany; + @hasMany(() => Link, { + foreignKey: 'authorId', + }) + declare links: HasMany; - @computed() - get fullname() { - return this.nickName || this.name; - } + @computed() + get fullname() { + return this.nickName || this.name; + } } diff --git a/app/validators/collection.ts b/app/validators/collection.ts index 1f9aaf1..37871bf 100644 --- a/app/validators/collection.ts +++ b/app/validators/collection.ts @@ -2,36 +2,36 @@ import { Visibility } from '#enums/visibility'; import vine, { SimpleMessagesProvider } from '@vinejs/vine'; const params = vine.object({ - id: vine.number(), + id: vine.number(), }); export const createCollectionValidator = vine.compile( - vine.object({ - name: vine.string().trim().minLength(1).maxLength(254), - description: vine.string().trim().maxLength(254).nullable(), - visibility: vine.enum(Visibility), - nextId: vine.number().optional(), - }) + vine.object({ + name: vine.string().trim().minLength(1).maxLength(254), + description: vine.string().trim().maxLength(254).nullable(), + visibility: vine.enum(Visibility), + nextId: vine.number().optional(), + }) ); export const updateCollectionValidator = vine.compile( - vine.object({ - name: vine.string().trim().minLength(1).maxLength(254), - description: vine.string().trim().maxLength(254).nullable(), - visibility: vine.enum(Visibility), - nextId: vine.number().optional(), + vine.object({ + name: vine.string().trim().minLength(1).maxLength(254), + description: vine.string().trim().maxLength(254).nullable(), + visibility: vine.enum(Visibility), + nextId: vine.number().optional(), - params, - }) + params, + }) ); export const deleteCollectionValidator = vine.compile( - vine.object({ - params, - }) + vine.object({ + params, + }) ); createCollectionValidator.messagesProvider = new SimpleMessagesProvider({ - name: 'Collection name is required', - 'visibility.required': 'Collection visibiliy is required', + name: 'Collection name is required', + 'visibility.required': 'Collection visibiliy is required', }); diff --git a/app/validators/link.ts b/app/validators/link.ts index f247377..e68b154 100644 --- a/app/validators/link.ts +++ b/app/validators/link.ts @@ -1,43 +1,43 @@ import vine from '@vinejs/vine'; const params = vine.object({ - id: vine.number(), + id: vine.number(), }); export const createLinkValidator = vine.compile( - vine.object({ - name: vine.string().trim().minLength(1).maxLength(254), - description: vine.string().trim().maxLength(300).optional(), - url: vine.string().trim(), - favorite: vine.boolean(), - collectionId: vine.number(), - }) + vine.object({ + name: vine.string().trim().minLength(1).maxLength(254), + description: vine.string().trim().maxLength(300).optional(), + url: vine.string().trim(), + favorite: vine.boolean(), + collectionId: vine.number(), + }) ); export const updateLinkValidator = vine.compile( - vine.object({ - name: vine.string().trim().minLength(1).maxLength(254), - description: vine.string().trim().maxLength(300).optional(), - url: vine.string().trim(), - favorite: vine.boolean(), - collectionId: vine.number(), + vine.object({ + name: vine.string().trim().minLength(1).maxLength(254), + description: vine.string().trim().maxLength(300).optional(), + url: vine.string().trim(), + favorite: vine.boolean(), + collectionId: vine.number(), - params, - }) + params, + }) ); export const deleteLinkValidator = vine.compile( - vine.object({ - params, - }) + vine.object({ + params, + }) ); export const updateLinkFavoriteStatusValidator = vine.compile( - vine.object({ - favorite: vine.boolean(), + vine.object({ + favorite: vine.boolean(), - params: vine.object({ - id: vine.number(), - }), - }) + params: vine.object({ + id: vine.number(), + }), + }) ); diff --git a/app/validators/shared_collection.ts b/app/validators/shared_collection.ts index 4b580eb..6c47eb2 100644 --- a/app/validators/shared_collection.ts +++ b/app/validators/shared_collection.ts @@ -1,11 +1,11 @@ import vine from '@vinejs/vine'; const params = vine.object({ - id: vine.number(), + id: vine.number(), }); export const getSharedCollectionValidator = vine.compile( - vine.object({ - params, - }) + vine.object({ + params, + }) ); diff --git a/app/validators/user.ts b/app/validators/user.ts index 9923292..7056af4 100644 --- a/app/validators/user.ts +++ b/app/validators/user.ts @@ -1,7 +1,7 @@ import vine from '@vinejs/vine'; export const updateUserThemeValidator = vine.compile( - vine.object({ - preferDarkTheme: vine.boolean(), - }) + vine.object({ + preferDarkTheme: vine.boolean(), + }) ); diff --git a/bin/console.ts b/bin/console.ts index 506e3b4..f914bb9 100644 --- a/bin/console.ts +++ b/bin/console.ts @@ -25,23 +25,23 @@ const APP_ROOT = new URL('../', import.meta.url); * application. */ const IMPORTER = (filePath: string) => { - if (filePath.startsWith('./') || filePath.startsWith('../')) { - return import(new URL(filePath, APP_ROOT).href); - } - return import(filePath); + if (filePath.startsWith('./') || filePath.startsWith('../')) { + return import(new URL(filePath, APP_ROOT).href); + } + return import(filePath); }; new Ignitor(APP_ROOT, { importer: IMPORTER }) - .tap((app) => { - app.booting(async () => { - await import('#start/env'); - }); - app.listen('SIGTERM', () => app.terminate()); - app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()); - }) - .ace() - .handle(process.argv.splice(2)) - .catch((error) => { - process.exitCode = 1; - prettyPrintError(error); - }); + .tap((app) => { + app.booting(async () => { + await import('#start/env'); + }); + app.listen('SIGTERM', () => app.terminate()); + app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()); + }) + .ace() + .handle(process.argv.splice(2)) + .catch((error) => { + process.exitCode = 1; + prettyPrintError(error); + }); diff --git a/bin/server.ts b/bin/server.ts index 474c90b..7064191 100644 --- a/bin/server.ts +++ b/bin/server.ts @@ -23,23 +23,23 @@ const APP_ROOT = new URL('../', import.meta.url); * application. */ const IMPORTER = (filePath: string) => { - if (filePath.startsWith('./') || filePath.startsWith('../')) { - return import(new URL(filePath, APP_ROOT).href); - } - return import(filePath); + if (filePath.startsWith('./') || filePath.startsWith('../')) { + return import(new URL(filePath, APP_ROOT).href); + } + return import(filePath); }; new Ignitor(APP_ROOT, { importer: IMPORTER }) - .tap((app) => { - app.booting(async () => { - await import('#start/env'); - }); - app.listen('SIGTERM', () => app.terminate()); - app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()); - }) - .httpServer() - .start() - .catch((error) => { - process.exitCode = 1; - prettyPrintError(error); - }); + .tap((app) => { + app.booting(async () => { + await import('#start/env'); + }); + app.listen('SIGTERM', () => app.terminate()); + app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()); + }) + .httpServer() + .start() + .catch((error) => { + process.exitCode = 1; + prettyPrintError(error); + }); diff --git a/bin/test.ts b/bin/test.ts index 63d281a..521c371 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -27,36 +27,36 @@ const APP_ROOT = new URL('../', import.meta.url); * application. */ const IMPORTER = (filePath: string) => { - if (filePath.startsWith('./') || filePath.startsWith('../')) { - return import(new URL(filePath, APP_ROOT).href); - } - return import(filePath); + if (filePath.startsWith('./') || filePath.startsWith('../')) { + return import(new URL(filePath, APP_ROOT).href); + } + return import(filePath); }; new Ignitor(APP_ROOT, { importer: IMPORTER }) - .tap((app) => { - app.booting(async () => { - await import('#start/env'); - }); - app.listen('SIGTERM', () => app.terminate()); - app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()); - }) - .testRunner() - .configure(async (app) => { - const { runnerHooks, ...config } = await import('../tests/bootstrap.js'); + .tap((app) => { + app.booting(async () => { + await import('#start/env'); + }); + app.listen('SIGTERM', () => app.terminate()); + app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()); + }) + .testRunner() + .configure(async (app) => { + const { runnerHooks, ...config } = await import('../tests/bootstrap.js'); - processCLIArgs(process.argv.splice(2)); - configure({ - ...app.rcFile.tests, - ...config, - ...{ - setup: runnerHooks.setup, - teardown: runnerHooks.teardown.concat([() => app.terminate()]), - }, - }); - }) - .run(() => run()) - .catch((error) => { - process.exitCode = 1; - prettyPrintError(error); - }); + processCLIArgs(process.argv.splice(2)); + configure({ + ...app.rcFile.tests, + ...config, + ...{ + setup: runnerHooks.setup, + teardown: runnerHooks.teardown.concat([() => app.terminate()]), + }, + }); + }) + .run(() => run()) + .catch((error) => { + process.exitCode = 1; + prettyPrintError(error); + }); diff --git a/config/ally.ts b/config/ally.ts index a14282d..2205ade 100644 --- a/config/ally.ts +++ b/config/ally.ts @@ -2,18 +2,18 @@ import env from '#start/env'; import { defineConfig, services } from '@adonisjs/ally'; const allyConfig = defineConfig({ - google: services.google({ - clientId: env.get('GOOGLE_CLIENT_ID'), - clientSecret: env.get('GOOGLE_CLIENT_SECRET'), - callbackUrl: env.get('GOOGLE_CLIENT_CALLBACK_URL'), - prompt: 'select_account', - display: 'page', - scopes: ['userinfo.email', 'userinfo.profile'], - }), + google: services.google({ + clientId: env.get('GOOGLE_CLIENT_ID'), + clientSecret: env.get('GOOGLE_CLIENT_SECRET'), + callbackUrl: env.get('GOOGLE_CLIENT_CALLBACK_URL'), + prompt: 'select_account', + display: 'page', + scopes: ['userinfo.email', 'userinfo.profile'], + }), }); export default allyConfig; declare module '@adonisjs/ally/types' { - interface SocialProviders extends InferSocialProviders {} + interface SocialProviders extends InferSocialProviders {} } diff --git a/config/app.ts b/config/app.ts index a9ce7a0..aa30afc 100644 --- a/config/app.ts +++ b/config/app.ts @@ -16,25 +16,25 @@ export const appKey = new Secret(env.get('APP_KEY')); * The configuration settings used by the HTTP server */ export const http = defineConfig({ - generateRequestId: true, - allowMethodSpoofing: false, + generateRequestId: true, + allowMethodSpoofing: false, - /** - * Enabling async local storage will let you access HTTP context - * from anywhere inside your application. - */ - useAsyncLocalStorage: false, + /** + * Enabling async local storage will let you access HTTP context + * from anywhere inside your application. + */ + useAsyncLocalStorage: false, - /** - * Manage cookies configuration. The settings for the session id cookie are - * defined inside the "config/session.ts" file. - */ - cookie: { - domain: '', - path: '/', - maxAge: '2h', - httpOnly: true, - secure: app.inProduction, - sameSite: 'lax', - }, + /** + * Manage cookies configuration. The settings for the session id cookie are + * defined inside the "config/session.ts" file. + */ + cookie: { + domain: '', + path: '/', + maxAge: '2h', + httpOnly: true, + secure: app.inProduction, + sameSite: 'lax', + }, }); diff --git a/config/auth.ts b/config/auth.ts index ccf832f..86b8fe4 100644 --- a/config/auth.ts +++ b/config/auth.ts @@ -3,15 +3,15 @@ import { InferAuthEvents, Authenticators } from '@adonisjs/auth/types'; import { sessionGuard, sessionUserProvider } from '@adonisjs/auth/session'; const authConfig = defineConfig({ - default: 'web', - guards: { - web: sessionGuard({ - useRememberMeTokens: false, - provider: sessionUserProvider({ - model: () => import('#models/user'), - }), - }), - }, + default: 'web', + guards: { + web: sessionGuard({ + useRememberMeTokens: false, + provider: sessionUserProvider({ + model: () => import('#models/user'), + }), + }), + }, }); export default authConfig; @@ -21,8 +21,8 @@ export default authConfig; * guards. */ declare module '@adonisjs/auth/types' { - interface Authenticators extends InferAuthenticators {} + interface Authenticators extends InferAuthenticators {} } declare module '@adonisjs/core/types' { - interface EventsList extends InferAuthEvents {} + interface EventsList extends InferAuthEvents {} } diff --git a/config/bodyparser.ts b/config/bodyparser.ts index cdfd37f..c996afe 100644 --- a/config/bodyparser.ts +++ b/config/bodyparser.ts @@ -1,55 +1,55 @@ import { defineConfig } from '@adonisjs/core/bodyparser'; const bodyParserConfig = defineConfig({ - /** - * The bodyparser middleware will parse the request body - * for the following HTTP methods. - */ - allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'], + /** + * The bodyparser middleware will parse the request body + * for the following HTTP methods. + */ + allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'], - /** - * Config for the "application/x-www-form-urlencoded" - * content-type parser - */ - form: { - convertEmptyStringsToNull: true, - types: ['application/x-www-form-urlencoded'], - }, + /** + * Config for the "application/x-www-form-urlencoded" + * content-type parser + */ + form: { + convertEmptyStringsToNull: true, + types: ['application/x-www-form-urlencoded'], + }, - /** - * Config for the JSON parser - */ - json: { - convertEmptyStringsToNull: true, - types: [ - 'application/json', - 'application/json-patch+json', - 'application/vnd.api+json', - 'application/csp-report', - ], - }, + /** + * Config for the JSON parser + */ + json: { + convertEmptyStringsToNull: true, + types: [ + 'application/json', + 'application/json-patch+json', + 'application/vnd.api+json', + 'application/csp-report', + ], + }, - /** - * Config for the "multipart/form-data" content-type parser. - * File uploads are handled by the multipart parser. - */ - multipart: { - /** - * Enabling auto process allows bodyparser middleware to - * move all uploaded files inside the tmp folder of your - * operating system - */ - autoProcess: true, - convertEmptyStringsToNull: true, - processManually: [], + /** + * Config for the "multipart/form-data" content-type parser. + * File uploads are handled by the multipart parser. + */ + multipart: { + /** + * Enabling auto process allows bodyparser middleware to + * move all uploaded files inside the tmp folder of your + * operating system + */ + autoProcess: true, + convertEmptyStringsToNull: true, + processManually: [], - /** - * Maximum limit of data to parse including all files - * and fields - */ - limit: '20mb', - types: ['multipart/form-data'], - }, + /** + * Maximum limit of data to parse including all files + * and fields + */ + limit: '20mb', + types: ['multipart/form-data'], + }, }); export default bodyParserConfig; diff --git a/config/cors.ts b/config/cors.ts index 433db24..1d15368 100644 --- a/config/cors.ts +++ b/config/cors.ts @@ -7,13 +7,13 @@ import { defineConfig } from '@adonisjs/cors'; * https://docs.adonisjs.com/guides/security/cors */ const corsConfig = defineConfig({ - enabled: true, - origin: [], - methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'], - headers: true, - exposeHeaders: [], - credentials: true, - maxAge: 90, + enabled: true, + origin: [], + methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'], + headers: true, + exposeHeaders: [], + credentials: true, + maxAge: 90, }); export default corsConfig; diff --git a/config/database.ts b/config/database.ts index cdf5995..d017fc0 100644 --- a/config/database.ts +++ b/config/database.ts @@ -2,26 +2,26 @@ import env from '#start/env'; import { defineConfig } from '@adonisjs/lucid'; const dbConfig = defineConfig({ - connection: 'postgres', - connections: { - postgres: { - client: 'pg', - connection: { - host: env.get('DB_HOST'), - port: env.get('DB_PORT'), - user: env.get('DB_USER'), - password: env.get('DB_PASSWORD'), - database: env.get('DB_DATABASE'), - }, - migrations: { - naturalSort: true, - paths: ['database/migrations'], - }, - seeders: { - paths: ['./database/seeders/main'], - }, - }, - }, + connection: 'postgres', + connections: { + postgres: { + client: 'pg', + connection: { + host: env.get('DB_HOST'), + port: env.get('DB_PORT'), + user: env.get('DB_USER'), + password: env.get('DB_PASSWORD'), + database: env.get('DB_DATABASE'), + }, + migrations: { + naturalSort: true, + paths: ['database/migrations'], + }, + seeders: { + paths: ['./database/seeders/main'], + }, + }, + }, }); export default dbConfig; diff --git a/config/hash.ts b/config/hash.ts index 015f9ce..e42a1b7 100644 --- a/config/hash.ts +++ b/config/hash.ts @@ -1,16 +1,16 @@ import { defineConfig, drivers } from '@adonisjs/core/hash'; const hashConfig = defineConfig({ - default: 'scrypt', + default: 'scrypt', - list: { - scrypt: drivers.scrypt({ - cost: 16384, - blockSize: 8, - parallelization: 1, - maxMemory: 33554432, - }), - }, + list: { + scrypt: drivers.scrypt({ + cost: 16384, + blockSize: 8, + parallelization: 1, + maxMemory: 33554432, + }), + }, }); export default hashConfig; @@ -20,5 +20,5 @@ export default hashConfig; * in your application. */ declare module '@adonisjs/core/types' { - export interface HashersList extends InferHashers {} + export interface HashersList extends InferHashers {} } diff --git a/config/inertia.ts b/config/inertia.ts index 992f43d..6942a0d 100644 --- a/config/inertia.ts +++ b/config/inertia.ts @@ -1,36 +1,36 @@ import { - DARK_THEME_DEFAULT_VALUE, - PREFER_DARK_THEME, + DARK_THEME_DEFAULT_VALUE, + PREFER_DARK_THEME, } from '#constants/session'; import { defineConfig } from '@adonisjs/inertia'; export default defineConfig({ - /** - * Path to the Edge view that will be used as the root view for Inertia responses - */ - rootView: 'inertia_layout', + /** + * Path to the Edge view that will be used as the root view for Inertia responses + */ + rootView: 'inertia_layout', - /** - * Data that should be shared with all rendered pages - */ - sharedData: { - errors: (ctx) => ctx.session?.flashMessages.get('errors'), - preferDarkTheme: (ctx) => - ctx.session?.get(PREFER_DARK_THEME, DARK_THEME_DEFAULT_VALUE), - auth: async (ctx) => { - await ctx.auth?.check(); - return { - user: ctx.auth?.user || null, - isAuthenticated: ctx.auth?.isAuthenticated || false, - }; - }, - }, + /** + * Data that should be shared with all rendered pages + */ + sharedData: { + errors: (ctx) => ctx.session?.flashMessages.get('errors'), + preferDarkTheme: (ctx) => + ctx.session?.get(PREFER_DARK_THEME, DARK_THEME_DEFAULT_VALUE), + auth: async (ctx) => { + await ctx.auth?.check(); + return { + user: ctx.auth?.user || null, + isAuthenticated: ctx.auth?.isAuthenticated || false, + }; + }, + }, - /** - * Options for the server-side rendering - */ - ssr: { - enabled: true, - entrypoint: 'inertia/app/ssr.tsx', - }, + /** + * Options for the server-side rendering + */ + ssr: { + enabled: true, + entrypoint: 'inertia/app/ssr.tsx', + }, }); diff --git a/config/logger.ts b/config/logger.ts index bc207d3..c91d06a 100644 --- a/config/logger.ts +++ b/config/logger.ts @@ -3,25 +3,25 @@ import app from '@adonisjs/core/services/app'; import { defineConfig, targets } from '@adonisjs/core/logger'; const loggerConfig = defineConfig({ - default: 'app', + default: 'app', - /** - * The loggers object can be used to define multiple loggers. - * By default, we configure only one logger (named "app"). - */ - loggers: { - app: { - enabled: true, - name: env.get('APP_NAME'), - level: env.get('LOG_LEVEL'), - transport: { - targets: targets() - .pushIf(!app.inProduction, targets.pretty()) - .pushIf(app.inProduction, targets.file({ destination: 1 })) - .toArray(), - }, - }, - }, + /** + * The loggers object can be used to define multiple loggers. + * By default, we configure only one logger (named "app"). + */ + loggers: { + app: { + enabled: true, + name: env.get('APP_NAME'), + level: env.get('LOG_LEVEL'), + transport: { + targets: targets() + .pushIf(!app.inProduction, targets.pretty()) + .pushIf(app.inProduction, targets.file({ destination: 1 })) + .toArray(), + }, + }, + }, }); export default loggerConfig; @@ -31,5 +31,5 @@ export default loggerConfig; * in your application. */ declare module '@adonisjs/core/types' { - export interface LoggersList extends InferLoggers {} + export interface LoggersList extends InferLoggers {} } diff --git a/config/session.ts b/config/session.ts index 735bd41..0103d6e 100644 --- a/config/session.ts +++ b/config/session.ts @@ -2,48 +2,48 @@ import env from '#start/env'; import { defineConfig, stores } from '@adonisjs/session'; const sessionConfig = defineConfig({ - enabled: true, - cookieName: 'adonis-session', + enabled: true, + cookieName: 'adonis-session', - /** - * When set to true, the session id cookie will be deleted - * once the user closes the browser. - */ - clearWithBrowser: false, + /** + * When set to true, the session id cookie will be deleted + * once the user closes the browser. + */ + clearWithBrowser: false, - /** - * Define how long to keep the session data alive without - * any activity. - */ - age: '7d', + /** + * Define how long to keep the session data alive without + * any activity. + */ + age: '7d', - /** - * Configuration for session cookie and the - * cookie store - */ - cookie: { - path: '/', - httpOnly: true, - secure: true, + /** + * Configuration for session cookie and the + * cookie store + */ + cookie: { + path: '/', + httpOnly: true, + secure: true, - // TODO: set this to lax and found a solution to keep auth when using extension - sameSite: 'none', - }, + // TODO: set this to lax and found a solution to keep auth when using extension + sameSite: 'none', + }, - /** - * The store to use. Make sure to validate the environment - * variable in order to infer the store name without any - * errors. - */ - store: env.get('SESSION_DRIVER'), + /** + * The store to use. Make sure to validate the environment + * variable in order to infer the store name without any + * errors. + */ + store: env.get('SESSION_DRIVER'), - /** - * List of configured stores. Refer documentation to see - * list of available stores and their config. - */ - stores: { - cookie: stores.cookie(), - }, + /** + * List of configured stores. Refer documentation to see + * list of available stores and their config. + */ + stores: { + cookie: stores.cookie(), + }, }); export default sessionConfig; diff --git a/config/shield.ts b/config/shield.ts index 2d836e7..505cdda 100644 --- a/config/shield.ts +++ b/config/shield.ts @@ -1,50 +1,50 @@ import { defineConfig } from '@adonisjs/shield'; const shieldConfig = defineConfig({ - /** - * Configure CSP policies for your app. Refer documentation - * to learn more - */ - csp: { - enabled: false, - directives: {}, - reportOnly: false, - }, + /** + * Configure CSP policies for your app. Refer documentation + * to learn more + */ + csp: { + enabled: false, + directives: {}, + reportOnly: false, + }, - /** - * Configure CSRF protection options. Refer documentation - * to learn more - */ - csrf: { - enabled: false, - exceptRoutes: [], - enableXsrfCookie: true, - methods: ['POST', 'PUT', 'PATCH', 'DELETE'], - }, + /** + * Configure CSRF protection options. Refer documentation + * to learn more + */ + csrf: { + enabled: false, + exceptRoutes: [], + enableXsrfCookie: true, + methods: ['POST', 'PUT', 'PATCH', 'DELETE'], + }, - /** - * Control how your website should be embedded inside - * iFrames - */ - xFrame: { - enabled: false, - }, + /** + * Control how your website should be embedded inside + * iFrames + */ + xFrame: { + enabled: false, + }, - /** - * Force browser to always use HTTPS - */ - hsts: { - enabled: true, - maxAge: '180 days', - }, + /** + * Force browser to always use HTTPS + */ + hsts: { + enabled: true, + maxAge: '180 days', + }, - /** - * Disable browsers from sniffing the content type of a - * response and always rely on the "content-type" header. - */ - contentTypeSniffing: { - enabled: true, - }, + /** + * Disable browsers from sniffing the content type of a + * response and always rely on the "content-type" header. + */ + contentTypeSniffing: { + enabled: true, + }, }); export default shieldConfig; diff --git a/config/static.ts b/config/static.ts index 5e7b910..02de498 100644 --- a/config/static.ts +++ b/config/static.ts @@ -8,10 +8,10 @@ import { defineConfig } from '@adonisjs/static'; * https://docs.adonisjs.com/guides/static-assets */ const staticServerConfig = defineConfig({ - enabled: true, - etag: true, - lastModified: true, - dotFiles: 'ignore', + enabled: true, + etag: true, + lastModified: true, + dotFiles: 'ignore', }); export default staticServerConfig; diff --git a/config/vite.ts b/config/vite.ts index bf35a89..6ec8944 100644 --- a/config/vite.ts +++ b/config/vite.ts @@ -1,28 +1,28 @@ import { defineConfig } from '@adonisjs/vite'; const viteBackendConfig = defineConfig({ - /** - * The output of vite will be written inside this - * directory. The path should be relative from - * the application root. - */ - buildDirectory: 'public/assets', + /** + * The output of vite will be written inside this + * directory. The path should be relative from + * the application root. + */ + buildDirectory: 'public/assets', - /** - * The path to the manifest file generated by the - * "vite build" command. - */ - manifestFile: 'public/assets/.vite/manifest.json', + /** + * The path to the manifest file generated by the + * "vite build" command. + */ + manifestFile: 'public/assets/.vite/manifest.json', - /** - * Feel free to change the value of the "assetsUrl" to - * point to a CDN in production. - */ - assetsUrl: '/assets', + /** + * Feel free to change the value of the "assetsUrl" to + * point to a CDN in production. + */ + assetsUrl: '/assets', - scriptAttributes: { - defer: true, - }, + scriptAttributes: { + defer: true, + }, }); export default viteBackendConfig; diff --git a/database/default_table_fields.ts b/database/default_table_fields.ts index b896379..e4e94e6 100644 --- a/database/default_table_fields.ts +++ b/database/default_table_fields.ts @@ -1,8 +1,8 @@ import { Knex } from 'knex'; export function defaultTableFields(table: Knex.CreateTableBuilder) { - table.increments('id').primary().first().unique().notNullable(); + table.increments('id').primary().first().unique().notNullable(); - table.timestamp('created_at').notNullable(); - table.timestamp('updated_at').nullable(); + table.timestamp('created_at').notNullable(); + table.timestamp('updated_at').nullable(); } diff --git a/database/migrations/1714218548323_create_users_table.ts b/database/migrations/1714218548323_create_users_table.ts index 87068c7..b6537df 100644 --- a/database/migrations/1714218548323_create_users_table.ts +++ b/database/migrations/1714218548323_create_users_table.ts @@ -2,25 +2,25 @@ import { defaultTableFields } from '#database/default_table_fields'; import { BaseSchema } from '@adonisjs/lucid/schema'; export default class CreateUsersTable extends BaseSchema { - static tableName = 'users'; + static tableName = 'users'; - async up() { - this.schema.createTableIfNotExists(CreateUsersTable.tableName, (table) => { - table.string('email', 254).notNullable().unique(); - table.string('name', 254).notNullable(); - table.string('nick_name', 254).nullable(); - table.text('avatar_url').notNullable(); - table.boolean('is_admin').defaultTo(0).notNullable(); + async up() { + this.schema.createTableIfNotExists(CreateUsersTable.tableName, (table) => { + table.string('email', 254).notNullable().unique(); + table.string('name', 254).notNullable(); + table.string('nick_name', 254).nullable(); + table.text('avatar_url').notNullable(); + table.boolean('is_admin').defaultTo(0).notNullable(); - table.json('token').nullable(); - table.string('provider_id').notNullable(); - table.enum('provider_type', ['google']).notNullable().defaultTo('google'); + table.json('token').nullable(); + table.string('provider_id').notNullable(); + table.enum('provider_type', ['google']).notNullable().defaultTo('google'); - defaultTableFields(table); - }); - } + defaultTableFields(table); + }); + } - async down() { - this.schema.dropTable(CreateUsersTable.tableName); - } + async down() { + this.schema.dropTable(CreateUsersTable.tableName); + } } diff --git a/database/migrations/1714253983443_create_collections_table.ts b/database/migrations/1714253983443_create_collections_table.ts index 7880592..bef26a7 100644 --- a/database/migrations/1714253983443_create_collections_table.ts +++ b/database/migrations/1714253983443_create_collections_table.ts @@ -3,42 +3,42 @@ import { Visibility } from '#enums/visibility'; import { BaseSchema } from '@adonisjs/lucid/schema'; export default class CreateCollectionTable extends BaseSchema { - static tableName = 'collections'; - private visibilityEnumName = 'collection_visibility'; + static tableName = 'collections'; + private visibilityEnumName = 'collection_visibility'; - async up() { - this.schema.raw(`DROP TYPE IF EXISTS ${this.visibilityEnumName}`); - this.schema.createTableIfNotExists( - CreateCollectionTable.tableName, - (table) => { - table.string('name', 254).notNullable(); - table.string('description', 254).nullable(); - table - .enum('visibility', Object.values(Visibility), { - useNative: true, - enumName: this.visibilityEnumName, - existingType: false, - }) - .nullable() - .defaultTo(Visibility.PRIVATE); - table - .integer('next_id') - .references('id') - .inTable('collections') - .defaultTo(null); - table - .integer('author_id') - .references('id') - .inTable('users') - .onDelete('CASCADE'); + async up() { + this.schema.raw(`DROP TYPE IF EXISTS ${this.visibilityEnumName}`); + this.schema.createTableIfNotExists( + CreateCollectionTable.tableName, + (table) => { + table.string('name', 254).notNullable(); + table.string('description', 254).nullable(); + table + .enum('visibility', Object.values(Visibility), { + useNative: true, + enumName: this.visibilityEnumName, + existingType: false, + }) + .nullable() + .defaultTo(Visibility.PRIVATE); + table + .integer('next_id') + .references('id') + .inTable('collections') + .defaultTo(null); + table + .integer('author_id') + .references('id') + .inTable('users') + .onDelete('CASCADE'); - defaultTableFields(table); - } - ); - } + defaultTableFields(table); + } + ); + } - async down() { - this.schema.raw(`DROP TYPE IF EXISTS ${this.visibilityEnumName}`); - this.schema.dropTable(CreateCollectionTable.tableName); - } + async down() { + this.schema.raw(`DROP TYPE IF EXISTS ${this.visibilityEnumName}`); + this.schema.dropTable(CreateCollectionTable.tableName); + } } diff --git a/database/migrations/1714254076754_create_links_table.ts b/database/migrations/1714254076754_create_links_table.ts index f65a26d..027cbe5 100644 --- a/database/migrations/1714254076754_create_links_table.ts +++ b/database/migrations/1714254076754_create_links_table.ts @@ -2,30 +2,30 @@ import { defaultTableFields } from '#database/default_table_fields'; import { BaseSchema } from '@adonisjs/lucid/schema'; export default class CreateLinksTable extends BaseSchema { - static tableName = 'links'; + static tableName = 'links'; - async up() { - this.schema.createTableIfNotExists(CreateLinksTable.tableName, (table) => { - table.string('name', 254).notNullable(); - table.string('description', 254).nullable(); - table.text('url').notNullable(); - table.boolean('favorite').notNullable().defaultTo(0); - table - .integer('collection_id') - .references('id') - .inTable('collections') - .onDelete('CASCADE'); - table - .integer('author_id') - .references('id') - .inTable('users') - .onDelete('CASCADE'); + async up() { + this.schema.createTableIfNotExists(CreateLinksTable.tableName, (table) => { + table.string('name', 254).notNullable(); + table.string('description', 254).nullable(); + table.text('url').notNullable(); + table.boolean('favorite').notNullable().defaultTo(0); + table + .integer('collection_id') + .references('id') + .inTable('collections') + .onDelete('CASCADE'); + table + .integer('author_id') + .references('id') + .inTable('users') + .onDelete('CASCADE'); - defaultTableFields(table); - }); - } + defaultTableFields(table); + }); + } - async down() { - this.schema.dropTable(CreateLinksTable.tableName); - } + async down() { + this.schema.dropTable(CreateLinksTable.tableName); + } } diff --git a/database/migrations/1716561885061_create_full_text_searches_table.ts b/database/migrations/1716561885061_create_full_text_searches_table.ts index eda55d7..7ecdf11 100644 --- a/database/migrations/1716561885061_create_full_text_searches_table.ts +++ b/database/migrations/1716561885061_create_full_text_searches_table.ts @@ -1,18 +1,18 @@ import { BaseSchema } from '@adonisjs/lucid/schema'; export default class extends BaseSchema { - async up() { - this.schema.raw(` + async up() { + this.schema.raw(` CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS pg_trgm; `); - this.schema.raw(` + this.schema.raw(` CREATE INDEX ON links USING gin(to_tsvector('english', name)); CREATE INDEX ON collections USING gin(to_tsvector('english', name)); CREATE INDEX ON links USING gin(to_tsvector('french', name)); CREATE INDEX ON collections USING gin(to_tsvector('french', name)); `); - this.schema.raw(` + this.schema.raw(` CREATE OR REPLACE FUNCTION search_text(search_query TEXT, p_author_id INTEGER) RETURNS TABLE ( id INTEGER, @@ -45,9 +45,9 @@ export default class extends BaseSchema { $$ LANGUAGE plpgsql; `); - } + } - async down() { - this.schema.raw('DROP FUNCTION IF EXISTS search_text'); - } + async down() { + this.schema.raw('DROP FUNCTION IF EXISTS search_text'); + } } diff --git a/database/seeders/collection_seeder.ts b/database/seeders/collection_seeder.ts index b165f0b..ed65c8e 100644 --- a/database/seeders/collection_seeder.ts +++ b/database/seeders/collection_seeder.ts @@ -5,36 +5,36 @@ import { BaseSeeder } from '@adonisjs/lucid/seeders'; import { faker } from '@faker-js/faker'; export default class extends BaseSeeder { - static environment = ['development', 'testing']; + static environment = ['development', 'testing']; - async run() { - const users = await getUserIds(); + async run() { + const users = await getUserIds(); - const collections = faker.helpers.multiple( - () => createRandomCollection(users), - { - count: 50, - } - ); - await Collection.createMany(collections); - } + const collections = faker.helpers.multiple( + () => createRandomCollection(users), + { + count: 50, + } + ); + await Collection.createMany(collections); + } } export async function getUserIds() { - const users = await User.all(); - return users.map(({ id }) => id); + const users = await User.all(); + return users.map(({ id }) => id); } let collectionId = 0; function createRandomCollection(userIds: User['id'][]) { - const authorId = faker.helpers.arrayElements(userIds, 1).at(0); - collectionId++; - return { - id: collectionId, - name: faker.string.alphanumeric({ length: { min: 5, max: 25 } }), - description: faker.string.alphanumeric({ length: { min: 0, max: 254 } }), - visibility: Visibility.PRIVATE, - nextId: collectionId + 1, - authorId, - }; + const authorId = faker.helpers.arrayElements(userIds, 1).at(0); + collectionId++; + return { + id: collectionId, + name: faker.string.alphanumeric({ length: { min: 5, max: 25 } }), + description: faker.string.alphanumeric({ length: { min: 0, max: 254 } }), + visibility: Visibility.PRIVATE, + nextId: collectionId + 1, + authorId, + }; } diff --git a/database/seeders/link_seeder.ts b/database/seeders/link_seeder.ts index 097de5f..a85e311 100644 --- a/database/seeders/link_seeder.ts +++ b/database/seeders/link_seeder.ts @@ -6,41 +6,41 @@ import { BaseSeeder } from '@adonisjs/lucid/seeders'; import { faker } from '@faker-js/faker'; export default class extends BaseSeeder { - static environment = ['development', 'testing']; + static environment = ['development', 'testing']; - async run() { - const users = await getUserIds(); + async run() { + const users = await getUserIds(); - const links = await Promise.all( - faker.helpers.multiple(async () => createRandomLink(users), { - count: 500, - }) - ); - await Link.createMany(links.filter((a) => typeof a !== 'undefined') as any); - } + const links = await Promise.all( + faker.helpers.multiple(async () => createRandomLink(users), { + count: 500, + }) + ); + await Link.createMany(links.filter((a) => typeof a !== 'undefined') as any); + } } async function getCollectionIds(authorId: User['id']) { - const collection = await Collection.findManyBy('author_id', authorId); - return collection.map(({ id }) => id); + const collection = await Collection.findManyBy('author_id', authorId); + return collection.map(({ id }) => id); } async function createRandomLink(userIds: User['id'][]) { - const authorId = faker.helpers.arrayElements(userIds, 1).at(0)!; - const collections = await getCollectionIds(authorId); + const authorId = faker.helpers.arrayElements(userIds, 1).at(0)!; + const collections = await getCollectionIds(authorId); - const collectionId = faker.helpers.arrayElements(collections, 1).at(0); - if (!collectionId) { - return undefined; - } + const collectionId = faker.helpers.arrayElements(collections, 1).at(0); + if (!collectionId) { + return undefined; + } - return { - id: faker.string.uuid(), - name: faker.string.alphanumeric({ length: { min: 5, max: 25 } }), - description: faker.string.alphanumeric({ length: { min: 0, max: 254 } }), - url: faker.internet.url(), - favorite: faker.number.int({ min: 0, max: 1 }), - authorId, - collectionId, - }; + return { + id: faker.string.uuid(), + name: faker.string.alphanumeric({ length: { min: 5, max: 25 } }), + description: faker.string.alphanumeric({ length: { min: 0, max: 254 } }), + url: faker.internet.url(), + favorite: faker.number.int({ min: 0, max: 1 }), + authorId, + collectionId, + }; } diff --git a/database/seeders/main/index_seeder.ts b/database/seeders/main/index_seeder.ts index e567f4c..21c41a7 100644 --- a/database/seeders/main/index_seeder.ts +++ b/database/seeders/main/index_seeder.ts @@ -3,31 +3,31 @@ import logger from '@adonisjs/core/services/logger'; import { BaseSeeder } from '@adonisjs/lucid/seeders'; export default class IndexSeeder extends BaseSeeder { - private async seed(Seeder: { default: typeof BaseSeeder }) { - /** - * Do not run when not in a environment specified in Seeder - */ - if ( - !Seeder.default.environment || - (!Seeder.default.environment.includes('development') && app.inDev) || - (!Seeder.default.environment.includes('testing') && app.inTest) || - (!Seeder.default.environment.includes('production') && app.inProduction) - ) { - return; - } + private async seed(Seeder: { default: typeof BaseSeeder }) { + /** + * Do not run when not in a environment specified in Seeder + */ + if ( + !Seeder.default.environment || + (!Seeder.default.environment.includes('development') && app.inDev) || + (!Seeder.default.environment.includes('testing') && app.inTest) || + (!Seeder.default.environment.includes('production') && app.inProduction) + ) { + return; + } - await new Seeder.default(this.client).run(); - } + await new Seeder.default(this.client).run(); + } - async run() { - logger.info('Start user seed'); - await this.seed(await import('#database/seeders/user_seeder')); - logger.info('User seed done'); - logger.info('Collection user seed'); - await this.seed(await import('#database/seeders/collection_seeder')); - logger.info('Collection seed done'); - logger.info('Link user seed'); - await this.seed(await import('#database/seeders/link_seeder')); - logger.info('Link seed done'); - } + async run() { + logger.info('Start user seed'); + await this.seed(await import('#database/seeders/user_seeder')); + logger.info('User seed done'); + logger.info('Collection user seed'); + await this.seed(await import('#database/seeders/collection_seeder')); + logger.info('Collection seed done'); + logger.info('Link user seed'); + await this.seed(await import('#database/seeders/link_seeder')); + logger.info('Link seed done'); + } } diff --git a/database/seeders/user_seeder.ts b/database/seeders/user_seeder.ts index 69889cf..43ce731 100644 --- a/database/seeders/user_seeder.ts +++ b/database/seeders/user_seeder.ts @@ -4,26 +4,26 @@ import { BaseSeeder } from '@adonisjs/lucid/seeders'; import { faker } from '@faker-js/faker'; export default class extends BaseSeeder { - static environment = ['development', 'testing']; + static environment = ['development', 'testing']; - async run() { - const users = faker.helpers.multiple(createRandomUser, { - count: 25, - }); - await User.createMany(users); - } + async run() { + const users = faker.helpers.multiple(createRandomUser, { + count: 25, + }); + await User.createMany(users); + } } export function createRandomUser() { - return { - id: faker.number.int(), - email: faker.internet.email(), - name: faker.internet.userName(), - nickName: faker.internet.displayName(), - avatarUrl: faker.image.avatar(), - isAdmin: false, - providerId: faker.number.int(), - providerType: 'google' as const, - token: {} as GoogleToken, - }; + return { + id: faker.number.int(), + email: faker.internet.email(), + name: faker.internet.userName(), + nickName: faker.internet.displayName(), + avatarUrl: faker.image.avatar(), + isAdmin: false, + providerId: faker.number.int(), + providerType: 'google' as const, + token: {} as GoogleToken, + }; } diff --git a/inertia/app/app.tsx b/inertia/app/app.tsx index 0b79a22..67913ee 100644 --- a/inertia/app/app.tsx +++ b/inertia/app/app.tsx @@ -10,18 +10,18 @@ import '../i18n/index'; const appName = import.meta.env.VITE_APP_NAME || 'MyLinks'; createInertiaApp({ - progress: { color: primaryColor }, + progress: { color: primaryColor }, - title: (title) => `${appName}${title && ` - ${title}`}`, + title: (title) => `${appName}${title && ` - ${title}`}`, - resolve: (name) => { - return resolvePageComponent( - `../pages/${name}.tsx`, - import.meta.glob('../pages/**/*.tsx') - ); - }, + resolve: (name) => { + return resolvePageComponent( + `../pages/${name}.tsx`, + import.meta.glob('../pages/**/*.tsx') + ); + }, - setup({ el, App, props }) { - hydrateRoot(el, ); - }, + setup({ el, App, props }) { + hydrateRoot(el, ); + }, }); diff --git a/inertia/app/ssr.tsx b/inertia/app/ssr.tsx index 60a3832..1a90544 100644 --- a/inertia/app/ssr.tsx +++ b/inertia/app/ssr.tsx @@ -2,13 +2,13 @@ import { createInertiaApp } from '@inertiajs/react'; import ReactDOMServer from 'react-dom/server'; export default function render(page: any) { - return createInertiaApp({ - page, - render: ReactDOMServer.renderToString, - resolve: (name) => { - const pages = import.meta.glob('../pages/**/*.tsx', { eager: true }); - return pages[`../pages/${name}.tsx`]; - }, - setup: ({ App, props }) => , - }); + return createInertiaApp({ + page, + render: ReactDOMServer.renderToString, + resolve: (name) => { + const pages = import.meta.glob('../pages/**/*.tsx', { eager: true }); + return pages[`../pages/${name}.tsx`]; + }, + setup: ({ App, props }) => , + }); } diff --git a/inertia/components/common/dropdown/dropdown.tsx b/inertia/components/common/dropdown/dropdown.tsx index 9944be3..fe8d28f 100644 --- a/inertia/components/common/dropdown/dropdown.tsx +++ b/inertia/components/common/dropdown/dropdown.tsx @@ -7,58 +7,58 @@ import useToggle from '~/hooks/use_modal'; import useShortcut from '~/hooks/use_shortcut'; const DropdownStyle = styled.div<{ opened: boolean; svgSize?: number }>( - ({ opened, theme, svgSize = 24 }) => ({ - cursor: 'pointer', - userSelect: 'none', - position: 'relative', - minWidth: 'fit-content', - width: 'fit-content', - maxWidth: '250px', - backgroundColor: opened ? theme.colors.secondary : theme.colors.background, - padding: '4px', - borderRadius: theme.border.radius, + ({ opened, theme, svgSize = 24 }) => ({ + cursor: 'pointer', + userSelect: 'none', + position: 'relative', + minWidth: 'fit-content', + width: 'fit-content', + maxWidth: '250px', + backgroundColor: opened ? theme.colors.secondary : theme.colors.background, + padding: '4px', + borderRadius: theme.border.radius, - '&:hover': { - backgroundColor: theme.colors.secondary, - }, + '&:hover': { + backgroundColor: theme.colors.secondary, + }, - '& svg': { - height: `${svgSize}px`, - width: `${svgSize}px`, - }, - }) + '& svg': { + height: `${svgSize}px`, + width: `${svgSize}px`, + }, + }) ); export default function Dropdown({ - children, - label, - className, - svgSize, - onClick, + children, + label, + className, + svgSize, + onClick, }: HtmlHTMLAttributes & { - label: ReactNode | string; - className?: string; - svgSize?: number; + label: ReactNode | string; + className?: string; + svgSize?: number; }) { - const dropdownRef = useRef(null); - const { isShowing, toggle, close } = useToggle(); + const dropdownRef = useRef(null); + const { isShowing, toggle, close } = useToggle(); - useClickOutside(dropdownRef, close); - useShortcut('ESCAPE_KEY', close, { disableGlobalCheck: true }); + useClickOutside(dropdownRef, close); + useShortcut('ESCAPE_KEY', close, { disableGlobalCheck: true }); - return ( - { - onClick?.(event); - toggle(); - }} - ref={dropdownRef} - className={className} - svgSize={svgSize} - > - {label} - {children} - - ); + return ( + { + onClick?.(event); + toggle(); + }} + ref={dropdownRef} + className={className} + svgSize={svgSize} + > + {label} + {children} + + ); } diff --git a/inertia/components/common/dropdown/dropdown_container.tsx b/inertia/components/common/dropdown/dropdown_container.tsx index c44165a..82c87e3 100644 --- a/inertia/components/common/dropdown/dropdown_container.tsx +++ b/inertia/components/common/dropdown/dropdown_container.tsx @@ -2,20 +2,20 @@ import styled from '@emotion/styled'; import TransitionLayout from '~/components/layouts/_transition_layout'; const DropdownContainer = styled(TransitionLayout)<{ show: boolean }>( - ({ show, theme }) => ({ - zIndex: 99, - position: 'absolute', - top: 'calc(100% + 0.5em)', - right: 0, - minWidth: '175px', - backgroundColor: show ? theme.colors.secondary : theme.colors.background, - border: `2px solid ${theme.colors.secondary}`, - borderRadius: theme.border.radius, - boxShadow: theme.colors.boxShadow, - display: show ? 'flex' : 'none', - flexDirection: 'column', - overflow: 'hidden', - }) + ({ show, theme }) => ({ + zIndex: 99, + position: 'absolute', + top: 'calc(100% + 0.5em)', + right: 0, + minWidth: '175px', + backgroundColor: show ? theme.colors.secondary : theme.colors.background, + border: `2px solid ${theme.colors.secondary}`, + borderRadius: theme.border.radius, + boxShadow: theme.colors.boxShadow, + display: show ? 'flex' : 'none', + flexDirection: 'column', + overflow: 'hidden', + }) ); export default DropdownContainer; diff --git a/inertia/components/common/dropdown/dropdown_item.tsx b/inertia/components/common/dropdown/dropdown_item.tsx index 0a8f47f..ed9e46b 100644 --- a/inertia/components/common/dropdown/dropdown_item.tsx +++ b/inertia/components/common/dropdown/dropdown_item.tsx @@ -2,30 +2,30 @@ import styled from '@emotion/styled'; import { Link } from '@inertiajs/react'; const DropdownItemBase = styled('div', { - shouldForwardProp: (propName) => propName !== 'danger', + shouldForwardProp: (propName) => propName !== 'danger', })<{ danger?: boolean }>(({ theme, danger }) => ({ - fontSize: '14px', - whiteSpace: 'nowrap', - color: danger ? theme.colors.lightRed : theme.colors.primary, - padding: '8px 12px', - borderRadius: theme.border.radius, + fontSize: '14px', + whiteSpace: 'nowrap', + color: danger ? theme.colors.lightRed : theme.colors.primary, + padding: '8px 12px', + borderRadius: theme.border.radius, - '&:hover': { - backgroundColor: theme.colors.background, - }, + '&:hover': { + backgroundColor: theme.colors.background, + }, })); const DropdownItemButton = styled(DropdownItemBase)({ - display: 'flex', - gap: '0.75em', - alignItems: 'center', + display: 'flex', + gap: '0.75em', + alignItems: 'center', }); const DropdownItemLink = styled(DropdownItemBase.withComponent(Link))({ - width: '100%', - display: 'flex', - gap: '0.75em', - alignItems: 'center', + width: '100%', + display: 'flex', + gap: '0.75em', + alignItems: 'center', }); export { DropdownItemButton, DropdownItemLink }; diff --git a/inertia/components/common/dropdown/dropdown_label.tsx b/inertia/components/common/dropdown/dropdown_label.tsx index f8071b6..e1339d1 100644 --- a/inertia/components/common/dropdown/dropdown_label.tsx +++ b/inertia/components/common/dropdown/dropdown_label.tsx @@ -1,11 +1,11 @@ import styled from '@emotion/styled'; const DropdownLabel = styled.div(({ theme }) => ({ - height: 'auto', - width: 'auto', - color: theme.colors.font, - display: 'flex', - gap: '0.35em', + height: 'auto', + width: 'auto', + color: theme.colors.font, + display: 'flex', + gap: '0.35em', })); export default DropdownLabel; diff --git a/inertia/components/common/external_link.tsx b/inertia/components/common/external_link.tsx index 0f6e228..18f4030 100644 --- a/inertia/components/common/external_link.tsx +++ b/inertia/components/common/external_link.tsx @@ -1,18 +1,18 @@ import { AnchorHTMLAttributes, CSSProperties, ReactNode } from 'react'; export default function ExternalLink({ - children, - title, - ...props + children, + title, + ...props }: AnchorHTMLAttributes & { - children: ReactNode; - style?: CSSProperties; - title?: string; - className?: string; + children: ReactNode; + style?: CSSProperties; + title?: string; + className?: string; }) { - return ( - - {children} - - ); + return ( + + {children} + + ); } diff --git a/inertia/components/common/form/_button.tsx b/inertia/components/common/form/_button.tsx index c42b21b..b41efeb 100644 --- a/inertia/components/common/form/_button.tsx +++ b/inertia/components/common/form/_button.tsx @@ -1,31 +1,31 @@ import styled from '@emotion/styled'; const Button = styled.button<{ danger?: boolean }>(({ theme, danger }) => { - const btnColor = !danger ? theme.colors.primary : theme.colors.lightRed; - const btnDarkColor = !danger ? theme.colors.darkBlue : theme.colors.lightRed; - return { - cursor: 'pointer', - width: '100%', - textTransform: 'uppercase', - fontSize: '14px', - color: theme.colors.white, - background: btnColor, - padding: '0.75em', - border: `1px solid ${btnColor}`, - borderRadius: theme.border.radius, - transition: theme.transition.delay, + const btnColor = !danger ? theme.colors.primary : theme.colors.lightRed; + const btnDarkColor = !danger ? theme.colors.darkBlue : theme.colors.lightRed; + return { + cursor: 'pointer', + width: '100%', + textTransform: 'uppercase', + fontSize: '14px', + color: theme.colors.white, + background: btnColor, + padding: '0.75em', + border: `1px solid ${btnColor}`, + borderRadius: theme.border.radius, + transition: theme.transition.delay, - '&:disabled': { - cursor: 'not-allowed', - opacity: '0.5', - }, + '&:disabled': { + cursor: 'not-allowed', + opacity: '0.5', + }, - '&:not(:disabled):hover': { - boxShadow: `${btnDarkColor} 0 0 3px 1px`, - background: btnDarkColor, - color: theme.colors.white, - }, - }; + '&:not(:disabled):hover': { + boxShadow: `${btnDarkColor} 0 0 3px 1px`, + background: btnDarkColor, + color: theme.colors.white, + }, + }; }); export default Button; diff --git a/inertia/components/common/form/_form.tsx b/inertia/components/common/form/_form.tsx index 65e51f4..4a88224 100644 --- a/inertia/components/common/form/_form.tsx +++ b/inertia/components/common/form/_form.tsx @@ -1,10 +1,10 @@ import styled from '@emotion/styled'; const Form = styled.form({ - width: '100%', - display: 'flex', - gap: '1em', - flexDirection: 'column', + width: '100%', + display: 'flex', + gap: '1em', + flexDirection: 'column', }); export default Form; diff --git a/inertia/components/common/form/_form_field.tsx b/inertia/components/common/form/_form_field.tsx index 4c9e707..d709557 100644 --- a/inertia/components/common/form/_form_field.tsx +++ b/inertia/components/common/form/_form_field.tsx @@ -1,25 +1,25 @@ import styled from '@emotion/styled'; const FormField = styled('div', { - shouldForwardProp: (propName) => propName !== 'required', + shouldForwardProp: (propName) => propName !== 'required', })<{ required?: boolean }>(({ required, theme }) => ({ - display: 'flex', - gap: '0.25em', - flexDirection: 'column', + display: 'flex', + gap: '0.25em', + flexDirection: 'column', - '& label': { - position: 'relative', - userSelect: 'none', - width: 'fit-content', - }, + '& label': { + position: 'relative', + userSelect: 'none', + width: 'fit-content', + }, - '& label::after': { - position: 'absolute', - top: 0, - right: '-0.75em', - color: theme.colors.lightRed, - content: (required ? '"*"' : '""') as any, - }, + '& label::after': { + position: 'absolute', + top: 0, + right: '-0.75em', + color: theme.colors.lightRed, + content: (required ? '"*"' : '""') as any, + }, })); export default FormField; diff --git a/inertia/components/common/form/_form_field_error.tsx b/inertia/components/common/form/_form_field_error.tsx index 3f1d661..de3fb4d 100644 --- a/inertia/components/common/form/_form_field_error.tsx +++ b/inertia/components/common/form/_form_field_error.tsx @@ -2,8 +2,8 @@ import styled from '@emotion/styled'; // TODO: create a global style variable (fontSize) const FormFieldError = styled.p(({ theme }) => ({ - fontSize: '12px', - color: theme.colors.lightRed, + fontSize: '12px', + color: theme.colors.lightRed, })); export default FormFieldError; diff --git a/inertia/components/common/form/_input.tsx b/inertia/components/common/form/_input.tsx index c045eb0..cb0a1e0 100644 --- a/inertia/components/common/form/_input.tsx +++ b/inertia/components/common/form/_input.tsx @@ -1,27 +1,27 @@ import styled from '@emotion/styled'; const Input = styled.input(({ theme }) => ({ - width: '100%', - color: theme.colors.font, - backgroundColor: theme.colors.secondary, - padding: '0.75em', - border: `1px solid ${theme.colors.lightGrey}`, - borderBottom: `2px solid ${theme.colors.lightGrey}`, - borderRadius: theme.border.radius, - transition: theme.transition.delay, + width: '100%', + color: theme.colors.font, + backgroundColor: theme.colors.secondary, + padding: '0.75em', + border: `1px solid ${theme.colors.lightGrey}`, + borderBottom: `2px solid ${theme.colors.lightGrey}`, + borderRadius: theme.border.radius, + transition: theme.transition.delay, - '&:focus': { - borderBottom: `2px solid ${theme.colors.primary}`, - }, + '&:focus': { + borderBottom: `2px solid ${theme.colors.primary}`, + }, - '&:disabled': { - opacity: 0.85, - }, + '&:disabled': { + opacity: 0.85, + }, - '&::placeholder': { - fontStyle: 'italic', - color: theme.colors.grey, - }, + '&::placeholder': { + fontStyle: 'italic', + color: theme.colors.grey, + }, })); export default Input; diff --git a/inertia/components/common/form/checkbox.tsx b/inertia/components/common/form/checkbox.tsx index 2d8c77b..22f7038 100644 --- a/inertia/components/common/form/checkbox.tsx +++ b/inertia/components/common/form/checkbox.tsx @@ -4,52 +4,52 @@ import FormField from '~/components/common/form/_form_field'; import FormFieldError from '~/components/common/form/_form_field_error'; interface InputProps - extends Omit, 'onChange'> { - label: string; - name: string; - checked: boolean; - errors?: string[]; - onChange?: (name: string, checked: boolean) => void; + extends Omit, 'onChange'> { + label: string; + name: string; + checked: boolean; + errors?: string[]; + onChange?: (name: string, checked: boolean) => void; } export default function Checkbox({ - name, - label, - checked = false, - errors = [], - onChange, - required = false, - ...props + name, + label, + checked = false, + errors = [], + onChange, + required = false, + ...props }: InputProps): JSX.Element { - const [checkboxChecked, setCheckboxChecked] = useState(checked); + const [checkboxChecked, setCheckboxChecked] = useState(checked); - if (typeof window === 'undefined') return ; + if (typeof window === 'undefined') return ; - function _onChange({ target }: ChangeEvent) { - setCheckboxChecked(target.checked); - if (onChange) { - onChange(target.name, target.checked); - } - } + function _onChange({ target }: ChangeEvent) { + setCheckboxChecked(target.checked); + if (onChange) { + onChange(target.name, target.checked); + } + } - return ( - - - - {errors.length > 0 && - errors.map((error) => {error})} - - ); + return ( + + + + {errors.length > 0 && + errors.map((error) => {error})} + + ); } diff --git a/inertia/components/common/form/selector.tsx b/inertia/components/common/form/selector.tsx index 49c9f80..af49212 100644 --- a/inertia/components/common/form/selector.tsx +++ b/inertia/components/common/form/selector.tsx @@ -1,79 +1,79 @@ import { useTheme } from '@emotion/react'; import { InputHTMLAttributes, ReactNode, useEffect, useState } from 'react'; import Select, { - FormatOptionLabelMeta, - GroupBase, - OptionsOrGroups, + FormatOptionLabelMeta, + GroupBase, + OptionsOrGroups, } from 'react-select'; import FormField from '~/components/common/form/_form_field'; type Option = { label: string | number; value: string | number }; interface SelectorProps - extends Omit, 'onChange'> { - label: string; - name: string; - errors?: string[]; - options: OptionsOrGroups>; - value: number | string; - onChangeCallback?: (value: number | string) => void; - formatOptionLabel?: ( - data: Option, - formatOptionLabelMeta: FormatOptionLabelMeta