feat: multiple changes

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

View File

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

View File

@@ -1,12 +1,9 @@
---
import { getImage } from 'astro:assets'
import AstroUtils from '../../libs/AstroUtils'
import { objectOmit } from '@dzeio/object-util'
import AstroUtils from '../../libs/AstroUtils'
const formats = [
'avif',
'webp'
]
const formats = ['avif', 'webp']
export interface Props extends Omit<astroHTML.JSX.ImgHTMLAttributes, 'src'> {
src: ImageMetadata | string
@@ -17,7 +14,7 @@ export interface Props extends Omit<astroHTML.JSX.ImgHTMLAttributes, 'src'> {
type PictureResult = {
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>>
} | {
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(
formats.map(async (it) => ({
img: await getImage({src: Astro.props.src, format: it, width: Astro.props.width, height: Astro.props.height}),
format: it
}))
)
const imageFormats: Array<{ format: string, img: Awaited<ReturnType<typeof getImage>> }> =
await Promise.all(
formats.map(async (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 {
format: 'new',
@@ -62,7 +70,6 @@ const res = await AstroUtils.wrap<Result>(async () => {
})
const props = objectOmit(Astro.props, 'src', 'srcDark', 'class')
---
{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')
---
<div class:list={[{parent: Astro.props.block}]}>

View File

@@ -7,7 +7,7 @@ export interface Props extends Omit<astroHTML.JSX.InputHTMLAttributes, 'type'> {
block?: boolean
suffix?: 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')

View File

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

View File

@@ -9,10 +9,9 @@ export interface Props {
if (Astro.props.icoPath !== '/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()
export interface Props {
links?: Array<{href: string, target?: string, display: string}>
socials?: Array<{href: string, target?: string, icon: any}>
links?: Array<{ href: string, target?: string, display: string }>
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 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 {
right?: Record<string, string>

View File

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

6
src/env.d.ts vendored
View File

@@ -4,14 +4,12 @@
/**
* Environment variables declaration
*/
interface ImportMetaEnv {
}
interface ImportMetaEnv {}
interface ImportMeta {
readonly env: ImportMetaEnv;
readonly env: ImportMetaEnv
}
declare namespace App {
/**
* 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 IconPNG from 'assets/layouts/Head/favicon.png'
import Favicon from 'components/layouts/Favicon/Favicon.astro'
export interface Props {
/**
@@ -29,7 +29,7 @@ export interface Props {
*/
twitter?: {
title?: string | undefined
card?: "summary" | "summary_large_image" | "app" | "player" | undefined
card?: 'summary' | 'summary_large_image' | 'app' | 'player' | undefined
site?: string | undefined
creator?: string | undefined
} | undefined
@@ -46,9 +46,14 @@ export interface 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 -->

View File

@@ -1,9 +1,9 @@
---
import Footer from 'components/layouts/Footer.astro'
import Base, { type Props as BaseProps } from './Base.astro'
import Header from 'components/layouts/Header.astro'
import { Mail, Phone } from 'lucide-astro'
import { Github, Linkedin } from 'simple-icons-astro'
import Base, { type Props as BaseProps } from './Base.astro'
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
*/
export default class Component<T extends HTMLElement> {
private constructor(
public element: T
) {}
private constructor(public element: T) {}
public handled(value: boolean): this
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
*/
enum StatusCode {
/****************
* 1xx Requests *
****************/
@@ -283,7 +282,7 @@ enum StatusCode {
/**
* Indicates that the client needs to authenticate to gain network access.
*/
NETWORK_AUTHENTIFICATION_REQUIRED,
NETWORK_AUTHENTIFICATION_REQUIRED
}
export default StatusCode

View File

@@ -1,66 +1,70 @@
import ResponseBuilder from '../ResponseBuilder'
/**
* Add headers:
* Content-Type: application/problem+json
*
* following https://www.rfc-editor.org/rfc/rfc7807.html
*/
export default interface RFC7807 {
/**
* A URI reference [RFC3986] that identifies the
* problem type.
*
* This specification encourages that, when
* dereferenced, it provide human-readable documentation for the
* problem type (e.g., using HTML [W3C.REC-html5-20141028]).
*
* When
* this member is not present, its value is assumed to be
* "about:blank"
*/
type?: string
/**
* A short, human-readable summary of the problem
* type.
*
* It SHOULD NOT change from occurrence to occurrence of the
* problem, except for purposes of localization (e.g., using
* proactive content negotiation; see [RFC7231], Section 3.4).
*/
title?: string
/**
* The HTTP status code ([RFC7231], Section 6)
* generated by the origin server for this occurrence of the problem.
*/
status?: number
/**
* A human-readable explanation specific to this
* occurrence of the problem.
*/
details?: string
/**
* A URI reference that identifies the specific
* occurrence of the problem.
*
* It may or may not yield further
* information if dereferenced.
*/
instance?: string
}
/**
*
* @param error the error (base items are type, status, title details and instance)
* @returns
*/
export function buildRFC7807(error: RFC7807 & Record<string, any>, response: ResponseBuilder = new ResponseBuilder()): Response {
response.addHeader('Content-Type', 'application/problem+json')
.body(JSON.stringify(error))
.status(error.status ?? 500)
return response.build()
}
import ResponseBuilder from '../ResponseBuilder'
/**
* Add headers:
* Content-Type: application/problem+json
*
* following https://www.rfc-editor.org/rfc/rfc7807.html
*/
export default interface RFC7807 {
/**
* A URI reference [RFC3986] that identifies the
* problem type.
*
* This specification encourages that, when
* dereferenced, it provide human-readable documentation for the
* problem type (e.g., using HTML [W3C.REC-html5-20141028]).
*
* When
* this member is not present, its value is assumed to be
* "about:blank"
*/
type?: string
/**
* A short, human-readable summary of the problem
* type.
*
* It SHOULD NOT change from occurrence to occurrence of the
* problem, except for purposes of localization (e.g., using
* proactive content negotiation; see [RFC7231], Section 3.4).
*/
title?: string
/**
* The HTTP status code ([RFC7231], Section 6)
* generated by the origin server for this occurrence of the problem.
*/
status?: number
/**
* A human-readable explanation specific to this
* occurrence of the problem.
*/
details?: string
/**
* A URI reference that identifies the specific
* occurrence of the problem.
*
* It may or may not yield further
* information if dereferenced.
*/
instance?: string
}
/**
*
* @param error the error (base items are type, status, title details and instance)
* @returns
*/
export function buildRFC7807(
error: RFC7807 & Record<string, any>,
response: ResponseBuilder = new ResponseBuilder()
): Response {
return response
.addHeader('Content-Type', 'application/problem+json')
.body(JSON.stringify(error))
.status(error.status ?? 500)
.build()
}

View File

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

View File

@@ -1,4 +1,4 @@
import { sequence } from "astro/middleware"
import { sequence } from 'astro/middleware'
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'
/**
@@ -7,7 +7,10 @@ import ResponseBuilder from 'libs/ResponseBuilder'
export default defineMiddleware(async ({ request, url }, next) => {
const now = new Date()
// 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 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) {
// 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
} catch (e) {
if (import.meta.env.PROD) {
// 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

View File

@@ -4,7 +4,6 @@
* you MUST call it through the `DaoFactory` file
*/
export default abstract class Dao<Object extends { id: any } = { id: any }> {
/**
* 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
* @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
@@ -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
* @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
@@ -46,7 +47,7 @@ export default abstract class Dao<Object extends { id: any } = { id: any }> {
* @returns
*/
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) {
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
* @param obj the object to update/insert
* @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) {
return this.update(object)
}

View File

@@ -9,8 +9,7 @@
*
* Touch this interface to define which key is linked to which Dao
*/
interface DaoItem {
}
interface DaoItem {}
/**
* Class to get any DAO
@@ -48,7 +47,8 @@ export default class DaoFactory {
*/
private static initDao(item: keyof DaoItem): any | undefined {
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 I404 from 'assets/pages/404/404.svg'
import Button from 'components/global/Button.astro'
import Picture from 'components/global/Picture.astro'
import MainLayout from 'layouts/MainLayout.astro'
---
<MainLayout>