feat: multiple changes

Signed-off-by: Avior <git@avior.me>
This commit is contained in:
Florian Bouillon 2024-09-09 14:27:46 +02:00
parent d8f203f434
commit 3e91597dca
31 changed files with 216 additions and 223 deletions

View File

@ -19,7 +19,7 @@ jobs:
run: npm ci run: npm ci
- name: Run BiomeJS - name: Run BiomeJS
run: npm run lint run: npx biome ci .
# - uses: mongolyy/reviewdog-action-biome@v1 # - uses: mongolyy/reviewdog-action-biome@v1
# with: # with:
# github_token: ${{ secrets.GITHUB_TOKEN }} # github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,6 +1,6 @@
import node from '@astrojs/node'
import tailwind from '@astrojs/tailwind'
import { defineConfig } from 'astro/config' import { defineConfig } from 'astro/config'
import tailwind from "@astrojs/tailwind"
import node from "@astrojs/node"
import routing from './hooks/routing' import routing from './hooks/routing'
// const faviconHook = { // const faviconHook = {
@ -18,7 +18,7 @@ import routing from './hooks/routing'
export default defineConfig({ export default defineConfig({
// Use the NodeJS adapter // Use the NodeJS adapter
adapter: node({ adapter: node({
mode: "standalone" mode: 'standalone'
}), }),
// some settings to the build output // some settings to the build output
@ -66,7 +66,5 @@ export default defineConfig({
usePolling: !!(process.env.USE_POLLING ?? process.env.WSL_DISTRO_NAME) usePolling: !!(process.env.USE_POLLING ?? process.env.WSL_DISTRO_NAME)
} }
} }
}, }
})
})

View File

@ -24,7 +24,8 @@
"noStaticOnlyClass": "warn", "noStaticOnlyClass": "warn",
"noUselessTypeConstraint": "warn", "noUselessTypeConstraint": "warn",
"noVoid": "error", "noVoid": "error",
"useSimplifiedLogicExpression": "warn" "useSimplifiedLogicExpression": "warn",
"noForEach": "off"
}, },
"performance": { "performance": {
"noBarrelFile": "error", "noBarrelFile": "error",
@ -46,7 +47,7 @@
"options": { "options": {
"strictCase": true, "strictCase": true,
"requireAscii": true, "requireAscii": true,
"filenameCases": ["camelCase", "export"] "filenameCases": ["camelCase", "PascalCase", "export"]
} }
}, },
"useForOf": "error", "useForOf": "error",
@ -61,6 +62,9 @@
"useNodeAssertStrict": "warn", "useNodeAssertStrict": "warn",
"useNumberNamespace": "warn", "useNumberNamespace": "warn",
"useSingleCaseStatement": "warn" "useSingleCaseStatement": "warn"
},
"suspicious": {
"noEmptyInterface": "off"
} }
} }
}, },
@ -74,7 +78,7 @@
"semicolons": "asNeeded", "semicolons": "asNeeded",
"quoteStyle": "single", "quoteStyle": "single",
"trailingComma": "none", "trailingComma": "none",
"lineWidth": 200, "lineWidth": 120,
"bracketSameLine": true "bracketSameLine": true
} }
} }

View File

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test' import { expect, test } from '@playwright/test'
test('has title', async ({ page }) => { test('has title', async ({ page }) => {
await page.goto('/'); await page.goto('/')
// Expect a title "to contain" a substring. // Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Dzeio/); await expect(page).toHaveTitle(/Dzeio/)
}) })

View File

@ -53,14 +53,14 @@ async function updateRoutes(output: string, routes: Array<string>) {
let file = baseFile let file = baseFile
file += `\n\nexport type Routes = ${routes.map((it) => `'${it}'`).join(' | ')}` file += `\n\nexport type Routes = ${routes.map((it) => `'${it}'`).join(' | ')}`
file += '\n\nexport default function route(route: Routes, query?: Record<string, string | number>) {' file +=
'\n\nexport default function route(route: Routes, query?: Record<string, string | number>) {'
file += '\n\treturn formatRoute(route, query)' file += '\n\treturn formatRoute(route, query)'
file += '\n}\n' file += '\n}\n'
await fs.writeFile(output, file) await fs.writeFile(output, file)
} }
/** /**
* format the path back to an url usable by the app * format the path back to an url usable by the app
* *
@ -141,7 +141,6 @@ const integration: () => AstroIntegration = () => ({
await updateRoutes(outputFile, files) await updateRoutes(outputFile, files)
}, },
'astro:server:setup': async ({ server }) => { 'astro:server:setup': async ({ server }) => {
// get the files list // get the files list
const files = (await Promise.all([ const files = (await Promise.all([
await getFiles(pagesFolder).then((ev) => ev.map((it) => formatPath(pagesFolder, it))), await getFiles(pagesFolder).then((ev) => ev.map((it) => formatPath(pagesFolder, it))),
@ -158,7 +157,7 @@ const integration: () => AstroIntegration = () => ({
let removeExtension = true let removeExtension = true
let folder = pagesFolder let folder = pagesFolder
if(path.startsWith(publicFolder)) { if (path.startsWith(publicFolder)) {
removeExtension = false removeExtension = false
folder = publicFolder folder = publicFolder
} else if (!path.startsWith(folder)) { } else if (!path.startsWith(folder)) {
@ -178,7 +177,7 @@ const integration: () => AstroIntegration = () => ({
path = path.replace(/\\/g, '/') path = path.replace(/\\/g, '/')
let removeExtension = true let removeExtension = true
let folder = pagesFolder let folder = pagesFolder
if(path.startsWith(publicFolder)) { if (path.startsWith(publicFolder)) {
removeExtension = false removeExtension = false
folder = publicFolder folder = publicFolder
} }

View File

@ -12,32 +12,39 @@ export default defineConfig({
fullyParallel: true, fullyParallel: true,
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined as any, workers: process.env.CI ? 1 : (undefined as any),
reporter: process.env.CI ? 'list' : [['html', { reporter: process.env.CI
outputFolder: './playwright/report', ? 'list'
open: 'never' : [
}]], [
'html',
{
outputFolder: './playwright/report',
open: 'never'
}
]
],
use: { use: {
baseURL: 'http://localhost:3000', baseURL: 'http://localhost:3000',
trace: 'on-first-retry', trace: 'on-first-retry'
}, },
/* Configure projects for major browsers */ /* Configure projects for major browsers */
projects: [ projects: [
{ {
name: 'chromium', name: 'chromium',
use: { ...devices['Desktop Chrome'] }, use: { ...devices['Desktop Chrome'] }
}, },
{ {
name: 'firefox', name: 'firefox',
use: { ...devices['Desktop Firefox'] }, use: { ...devices['Desktop Firefox'] }
}, },
{ {
name: 'webkit', name: 'webkit',
use: { ...devices['Desktop Safari'] }, use: { ...devices['Desktop Safari'] }
}, }
/* Test against mobile viewports. */ /* Test against mobile viewports. */
// { // {
@ -58,5 +65,5 @@ export default defineConfig({
// name: 'Google Chrome', // name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' }, // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// }, // },
], ]
}) })

View File

@ -7,12 +7,11 @@ export interface Props extends astroHTML.JSX.AnchorHTMLAttributes {
} }
const classes = [ const classes = [
"button", 'button',
{outline: Astro.props.outline}, { outline: Astro.props.outline },
{ghost: Astro.props.ghost}, { ghost: Astro.props.ghost },
Astro.props.class Astro.props.class
] ]
--- ---
{'href' in Astro.props && ( {'href' in Astro.props && (
<a class:list={classes} {...objectOmit(Astro.props, 'type') as any}> <a class:list={classes} {...objectOmit(Astro.props, 'type') as any}>

View File

@ -1,12 +1,9 @@
--- ---
import { getImage } from 'astro:assets' import { getImage } from 'astro:assets'
import AstroUtils from '../../libs/AstroUtils'
import { objectOmit } from '@dzeio/object-util' import { objectOmit } from '@dzeio/object-util'
import AstroUtils from '../../libs/AstroUtils'
const formats = [ const formats = ['avif', 'webp']
'avif',
'webp'
]
export interface Props extends Omit<astroHTML.JSX.ImgHTMLAttributes, 'src'> { export interface Props extends Omit<astroHTML.JSX.ImgHTMLAttributes, 'src'> {
src: ImageMetadata | string src: ImageMetadata | string
@ -17,7 +14,7 @@ export interface Props extends Omit<astroHTML.JSX.ImgHTMLAttributes, 'src'> {
type PictureResult = { type PictureResult = {
format: 'new' format: 'new'
formats: Array<{format: string, img: Awaited<ReturnType<typeof getImage>>}> formats: Array<{ format: string, img: Awaited<ReturnType<typeof getImage>> }>
src: Awaited<ReturnType<typeof getImage>> src: Awaited<ReturnType<typeof getImage>>
} | { } | {
format: 'raw' format: 'raw'
@ -38,14 +35,25 @@ async function resolvePicture(image: ImageMetadata | string): Promise<PictureRes
} }
} }
const imageFormats: Array<{format: string, img: Awaited<ReturnType<typeof getImage>>}> = await Promise.all( const imageFormats: Array<{ format: string, img: Awaited<ReturnType<typeof getImage>> }> =
formats.map(async (it) => ({ await Promise.all(
img: await getImage({src: Astro.props.src, format: it, width: Astro.props.width, height: Astro.props.height}), formats.map(async (it) => ({
format: it img: await getImage({
})) src: Astro.props.src,
) format: it,
width: Astro.props.width,
height: Astro.props.height
}),
format: it
}))
)
const orig = await getImage({src: Astro.props.src, format: ext, width: Astro.props.width, height: Astro.props.height}) const orig = await getImage({
src: Astro.props.src,
format: ext,
width: Astro.props.width,
height: Astro.props.height
})
return { return {
format: 'new', format: 'new',
@ -62,7 +70,6 @@ const res = await AstroUtils.wrap<Result>(async () => {
}) })
const props = objectOmit(Astro.props, 'src', 'srcDark', 'class') const props = objectOmit(Astro.props, 'src', 'srcDark', 'class')
--- ---
{res.light.format === 'new' && ( {res.light.format === 'new' && (

View File

@ -6,7 +6,6 @@ interface Props extends Omit<astroHTML.JSX.InputHTMLAttributes, 'type'> {
} }
const baseProps = objectOmit(Astro.props, 'label', 'block') const baseProps = objectOmit(Astro.props, 'label', 'block')
--- ---
<div class:list={[{parent: Astro.props.block}]}> <div class:list={[{parent: Astro.props.block}]}>

View File

@ -7,7 +7,7 @@ export interface Props extends Omit<astroHTML.JSX.InputHTMLAttributes, 'type'> {
block?: boolean block?: boolean
suffix?: string suffix?: string
prefix?: string prefix?: string
options: Array<string | number | {title: string | number, description?: string | number | null}> options: Array<string | number | { title: string | number, description?: string | number | null }>
} }
const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix', 'options') const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix', 'options')

View File

@ -87,8 +87,8 @@ export function setOnTableClick(table: HTMLTableElement, fn: (row: number, cell:
table.querySelectorAll<HTMLTableCellElement>('td').forEach((it) => { table.querySelectorAll<HTMLTableCellElement>('td').forEach((it) => {
it.addEventListener('click', () => { it.addEventListener('click', () => {
const row = it.parentElement as HTMLTableRowElement const row = it.parentElement as HTMLTableRowElement
const rowIdx = parseInt(row.dataset.row as string) const rowIdx = Number.parseInt(row.dataset.row as string)
const cellIdx = parseInt(it.dataset.cell as string) const cellIdx = Number.parseInt(it.dataset.cell as string)
fn(rowIdx, cellIdx) fn(rowIdx, cellIdx)
}) })
}) })

View File

@ -9,10 +9,9 @@ export interface Props {
if (Astro.props.icoPath !== '/favicon.ico') { if (Astro.props.icoPath !== '/favicon.ico') {
console.warn('It is recommanded that the ICO file should be located at /favicon.ico') console.warn('It is recommanded that the ICO file should be located at /favicon.ico')
} }
const appleTouch = await getImage({src: Astro.props.png, width: 180, height: 180}) const appleTouch = await getImage({ src: Astro.props.png, width: 180, height: 180 })
--- ---
<> <>

View File

@ -1,36 +0,0 @@
import { getImage } from 'astro:assets'
export default class Manifest {
static async create(baseImage: ImageMetadata, options: {
name: string
color?: string
images?: Array<number>
}) {
const [
i192,
i512
] = await Promise.all([
getImage({src: baseImage, format: 'png', width: 192, height: 192}),
getImage({src: baseImage, format: 'png', width: 512, height: 512})
])
return JSON.stringify({
name: options.name,
short_name: options.name,
icons: [
{
src: i192.src,
sizes: "192x192",
type: "image/png"
},
{
src: i512.src,
sizes: "512x512",
type: "image/png"
}
],
theme_color: options.color ?? "#fff",
background_color: options.color ?? "#fff",
display: "standalone"
})
}
}

View File

@ -2,8 +2,8 @@
const year = new Date().getFullYear() const year = new Date().getFullYear()
export interface Props { export interface Props {
links?: Array<{href: string, target?: string, display: string}> links?: Array<{ href: string, target?: string, display: string }>
socials?: Array<{href: string, target?: string, icon: any}> socials?: Array<{ href: string, target?: string, icon: any }>
} }
--- ---

View File

@ -1,8 +1,8 @@
--- ---
import Logo from 'assets/components/layouts/Header/logo.svg'
import Picture from 'components/global/Picture.astro'
import Button from 'components/global/Button.astro'
import { objectMap } from '@dzeio/object-util' import { objectMap } from '@dzeio/object-util'
import Logo from 'assets/components/layouts/Header/logo.svg'
import Button from 'components/global/Button.astro'
import Picture from 'components/global/Picture.astro'
export interface Props { export interface Props {
right?: Record<string, string> right?: Record<string, string>

View File

@ -4,24 +4,26 @@ import { defineCollection, z } from 'astro:content'
// 2. Define your collection(s) // 2. Define your collection(s)
const projectsCollection = defineCollection({ const projectsCollection = defineCollection({
type: 'content', type: 'content',
schema: ({ image }) => z.object({ schema: ({ image }) =>
title: z.string(), z.object({
description: z.string().optional(), title: z.string(),
image: image().optional(), description: z.string().optional(),
link: z.object({ image: image().optional(),
href: z.string(), link: z
rel: z.string().optional(), .object({
text: z.string().optional(), href: z.string(),
target: z.string().optional() rel: z.string().optional(),
}).optional(), text: z.string().optional(),
disabled: z.string().optional(), target: z.string().optional()
created: z.date().optional(), })
updated: z.date().optional(), .optional(),
techs: z.string().array().optional() disabled: z.string().optional(),
}) created: z.date().optional(),
updated: z.date().optional(),
techs: z.string().array().optional()
})
}) })
// 3. Export a single `collections` object to register your collection(s) // 3. Export a single `collections` object to register your collection(s)
// This key should match your collection directory name in "src/content" // This key should match your collection directory name in "src/content"
export const collections = { export const collections = {

6
src/env.d.ts vendored
View File

@ -4,14 +4,12 @@
/** /**
* Environment variables declaration * Environment variables declaration
*/ */
interface ImportMetaEnv { interface ImportMetaEnv {}
}
interface ImportMeta { interface ImportMeta {
readonly env: ImportMetaEnv; readonly env: ImportMetaEnv
} }
declare namespace App { declare namespace App {
/** /**
* Middlewares variables * Middlewares variables

View File

@ -1,7 +1,7 @@
--- ---
import Favicon from 'components/layouts/Favicon/Favicon.astro' import type IconPNG from 'assets/layouts/Head/favicon.png'
import IconSVG from 'assets/layouts/Head/favicon.svg' import IconSVG from 'assets/layouts/Head/favicon.svg'
import IconPNG from 'assets/layouts/Head/favicon.png' import Favicon from 'components/layouts/Favicon/Favicon.astro'
export interface Props { export interface Props {
/** /**
@ -29,7 +29,7 @@ export interface Props {
*/ */
twitter?: { twitter?: {
title?: string | undefined title?: string | undefined
card?: "summary" | "summary_large_image" | "app" | "player" | undefined card?: 'summary' | 'summary_large_image' | 'app' | 'player' | undefined
site?: string | undefined site?: string | undefined
creator?: string | undefined creator?: string | undefined
} | undefined } | undefined
@ -46,9 +46,14 @@ export interface Props {
const props = Astro.props const props = Astro.props
const image = props.image ? Array.isArray(props.image) ? props.image : [props.image] : undefined const image = props.image ? (Array.isArray(props.image) ? props.image : [props.image]) : undefined
const canonical = typeof Astro.props.canonical === 'string' ? Astro.props.canonical : Astro.props.canonical === false ? undefined : Astro.url.href const canonical =
typeof Astro.props.canonical === 'string'
? Astro.props.canonical
: Astro.props.canonical === false
? undefined
: Astro.url.href
--- ---
<!-- Charset --> <!-- Charset -->

View File

@ -1,9 +1,9 @@
--- ---
import Footer from 'components/layouts/Footer.astro' import Footer from 'components/layouts/Footer.astro'
import Base, { type Props as BaseProps } from './Base.astro'
import Header from 'components/layouts/Header.astro' import Header from 'components/layouts/Header.astro'
import { Mail, Phone } from 'lucide-astro' import { Mail, Phone } from 'lucide-astro'
import { Github, Linkedin } from 'simple-icons-astro' import { Github, Linkedin } from 'simple-icons-astro'
import Base, { type Props as BaseProps } from './Base.astro'
export interface Props extends BaseProps { export interface Props extends BaseProps {
/** /**

View File

@ -4,9 +4,7 @@ type Fn<T extends HTMLElement> = (el: Component<T>) => void | Promise<void>
* Component client side initialisation class * Component client side initialisation class
*/ */
export default class Component<T extends HTMLElement> { export default class Component<T extends HTMLElement> {
private constructor( private constructor(public element: T) {}
public element: T
) {}
public handled(value: boolean): this public handled(value: boolean): this
public handled(): boolean public handled(): boolean

View File

@ -4,7 +4,6 @@
* Following https://developer.mozilla.org/en-US/docs/Web/HTTP/Status an extension of the RFC9110 * Following https://developer.mozilla.org/en-US/docs/Web/HTTP/Status an extension of the RFC9110
*/ */
enum StatusCode { enum StatusCode {
/**************** /****************
* 1xx Requests * * 1xx Requests *
****************/ ****************/
@ -283,7 +282,7 @@ enum StatusCode {
/** /**
* Indicates that the client needs to authenticate to gain network access. * Indicates that the client needs to authenticate to gain network access.
*/ */
NETWORK_AUTHENTIFICATION_REQUIRED, NETWORK_AUTHENTIFICATION_REQUIRED
} }
export default StatusCode export default StatusCode

View File

@ -58,9 +58,13 @@ export default interface RFC7807 {
* @param error the error (base items are type, status, title details and instance) * @param error the error (base items are type, status, title details and instance)
* @returns * @returns
*/ */
export function buildRFC7807(error: RFC7807 & Record<string, any>, response: ResponseBuilder = new ResponseBuilder()): Response { export function buildRFC7807(
response.addHeader('Content-Type', 'application/problem+json') error: RFC7807 & Record<string, any>,
response: ResponseBuilder = new ResponseBuilder()
): Response {
return response
.addHeader('Content-Type', 'application/problem+json')
.body(JSON.stringify(error)) .body(JSON.stringify(error))
.status(error.status ?? 500) .status(error.status ?? 500)
return response.build() .build()
} }

View File

@ -5,7 +5,6 @@ import StatusCode from './HTTP/StatusCode'
* Simple builde to create a new Response object * Simple builde to create a new Response object
*/ */
export default class ResponseBuilder { export default class ResponseBuilder {
public static redirect(location: string, statusCode: number = StatusCode.FOUND) { public static redirect(location: string, statusCode: number = StatusCode.FOUND) {
const resp = new ResponseBuilder() const resp = new ResponseBuilder()
resp.addHeader('Location', location) resp.addHeader('Location', location)

View File

@ -1,4 +1,4 @@
import { sequence } from "astro/middleware" import { sequence } from 'astro/middleware'
import logger from './logger' import logger from './logger'

View File

@ -1,4 +1,4 @@
import { defineMiddleware } from "astro/middleware" import { defineMiddleware } from 'astro/middleware'
import ResponseBuilder from 'libs/ResponseBuilder' import ResponseBuilder from 'libs/ResponseBuilder'
/** /**
@ -7,7 +7,10 @@ import ResponseBuilder from 'libs/ResponseBuilder'
export default defineMiddleware(async ({ request, url }, next) => { export default defineMiddleware(async ({ request, url }, next) => {
const now = new Date() const now = new Date()
// Date of request User-Agent 32 first chars request Method // Date of request User-Agent 32 first chars request Method
let prefix = `\x1b[2m${now.toISOString()}\x1b[22m ${request.headers.get('user-agent')?.slice(0, 32).padEnd(32)} ${request.method.padEnd(7)}` let prefix = `\x1b[2m${now.toISOString()}\x1b[22m ${request.headers
.get('user-agent')
?.slice(0, 32)
.padEnd(32)} ${request.method.padEnd(7)}`
const fullURL = url.toString() const fullURL = url.toString()
const path = fullURL.slice(fullURL.indexOf(url.pathname, fullURL.indexOf(url.host))) const path = fullURL.slice(fullURL.indexOf(url.pathname, fullURL.indexOf(url.host)))
@ -26,14 +29,22 @@ export default defineMiddleware(async ({ request, url }, next) => {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {
// HTTP Status time to execute path of request // HTTP Status time to execute path of request
console.log(`${prefix} \x1b[34m[${res.status}]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime()).toFixed(0).padStart(5, ' ')}ms\x1b[22m ${path}`) console.log(
`${prefix} \x1b[34m[${res.status}]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime())
.toFixed(0)
.padStart(5, ' ')}ms\x1b[22m ${path}`
)
} }
return res return res
} catch (e) { } catch (e) {
if (import.meta.env.PROD) { if (import.meta.env.PROD) {
// time to execute path of request // time to execute path of request
console.log(`${prefix} \x1b[34m[500]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime()).toFixed(0).padStart(5, ' ')}ms\x1b[22m ${path}`) console.log(
`${prefix} \x1b[34m[500]\x1b[0m \x1b[2m${(new Date().getTime() - now.getTime())
.toFixed(0)
.padStart(5, ' ')}ms\x1b[22m ${path}`
)
} }
// add a full line dash to not miss it // add a full line dash to not miss it

View File

@ -4,7 +4,6 @@
* you MUST call it through the `DaoFactory` file * you MUST call it through the `DaoFactory` file
*/ */
export default abstract class Dao<Object extends { id: any } = { id: any }> { export default abstract class Dao<Object extends { id: any } = { id: any }> {
/** /**
* insert a new object into the source * insert a new object into the source
* *
@ -19,7 +18,8 @@ export default abstract class Dao<Object extends { id: any } = { id: any }> {
* @param obj the object to create * @param obj the object to create
* @returns the object with it's id filled if create or null otherwise * @returns the object with it's id filled if create or null otherwise
*/ */
public insert: Dao<Object>['create'] = (obj: Parameters<Dao<Object>['create']>[0]) => this.create(obj) public insert: Dao<Object>['create'] = (obj: Parameters<Dao<Object>['create']>[0]) =>
this.create(obj)
/** /**
* find the list of objects having elements from the query * find the list of objects having elements from the query
@ -35,7 +35,8 @@ export default abstract class Dao<Object extends { id: any } = { id: any }> {
* @param query a partial object which filter depending on the elements, if not set it will fetch everything * @param query a partial object which filter depending on the elements, if not set it will fetch everything
* @returns an array containing the list of elements that match with the query * @returns an array containing the list of elements that match with the query
*/ */
public find: Dao<Object>['findAll'] = (query: Parameters<Dao<Object>['findAll']>[0]) => this.findAll(query) public find: Dao<Object>['findAll'] = (query: Parameters<Dao<Object>['findAll']>[0]) =>
this.findAll(query)
/** /**
* find an object by it's id * find an object by it's id
@ -46,7 +47,7 @@ export default abstract class Dao<Object extends { id: any } = { id: any }> {
* @returns * @returns
*/ */
public findById(id: Object['id']): Promise<Object | null> { public findById(id: Object['id']): Promise<Object | null> {
return this.findOne({id: id} as Partial<Object>) return this.findOne({ id: id } as Partial<Object>)
} }
/** /**
@ -91,14 +92,16 @@ export default abstract class Dao<Object extends { id: any } = { id: any }> {
if (!query) { if (!query) {
return null return null
} }
return await this.update({...query, ...changes}) return await this.update({ ...query, ...changes })
} }
/** /**
* update the remote reference of the object or create it if not found * update the remote reference of the object or create it if not found
* @param obj the object to update/insert * @param obj the object to update/insert
* @returns the object is updated/inserted or null otherwise * @returns the object is updated/inserted or null otherwise
*/ */
public async upsert(object: Object | Omit<Object, 'id' | 'created' | 'updated'>): Promise<Object | null> { public async upsert(
object: Object | Omit<Object, 'id' | 'created' | 'updated'>
): Promise<Object | null> {
if ('id' in object) { if ('id' in object) {
return this.update(object) return this.update(object)
} }

View File

@ -9,8 +9,7 @@
* *
* Touch this interface to define which key is linked to which Dao * Touch this interface to define which key is linked to which Dao
*/ */
interface DaoItem { interface DaoItem {}
}
/** /**
* Class to get any DAO * Class to get any DAO
@ -48,7 +47,8 @@ export default class DaoFactory {
*/ */
private static initDao(item: keyof DaoItem): any | undefined { private static initDao(item: keyof DaoItem): any | undefined {
switch (item) { switch (item) {
default: return undefined default:
return undefined
} }
} }
} }

View File

@ -1,9 +1,9 @@
--- ---
import MainLayout from 'layouts/MainLayout.astro'
import I404 from 'assets/pages/404/404.svg'
import I404Light from 'assets/pages/404/404.light.svg' import I404Light from 'assets/pages/404/404.light.svg'
import I404 from 'assets/pages/404/404.svg'
import Button from 'components/global/Button.astro' import Button from 'components/global/Button.astro'
import Picture from 'components/global/Picture.astro' import Picture from 'components/global/Picture.astro'
import MainLayout from 'layouts/MainLayout.astro'
--- ---
<MainLayout> <MainLayout>

View File

@ -7,7 +7,7 @@ module.exports = {
theme: { theme: {
fontFamily: { fontFamily: {
// add your default font below // add your default font below
'sans': ['Font Name', ...defaultTheme.fontFamily.sans] sans: ['Font Name', ...defaultTheme.fontFamily.sans]
}, },
extend: { extend: {
colors: { colors: {
@ -21,7 +21,7 @@ module.exports = {
// add a default padding to the container // add a default padding to the container
DEFAULT: '1rem' DEFAULT: '1rem'
} }
}, }
}, }
} }
} }

View File

@ -3,7 +3,7 @@ import { expect, test } from 'vitest'
// Edit an assertion and save to see HMR in action // Edit an assertion and save to see HMR in action
test('Math.sqrt()', () => { test('Math.sqrt()', () => {
expect(Math.sqrt(4)).toBe(2); expect(Math.sqrt(4)).toBe(2)
expect(Math.sqrt(144)).toBe(12); expect(Math.sqrt(144)).toBe(12)
expect(Math.sqrt(2)).toBe(Math.SQRT2); expect(Math.sqrt(2)).toBe(Math.SQRT2)
}); })

View File

@ -4,10 +4,8 @@ import { getViteConfig } from 'astro/config'
export default getViteConfig({ export default getViteConfig({
test: { test: {
include: [ include: ['./tests/**.ts']
'./tests/**.ts'
]
/* for example, use global to avoid globals imports (describe, test, expect): */ /* for example, use global to avoid globals imports (describe, test, expect): */
// globals: true, // globals: true,
}, }
}); })