diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 87f0035..3a1c9ec 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,4 +1,4 @@ -{ - "recommendations": ["astro-build.astro-vscode"], - "unwantedRecommendations": [] -} +{ + "recommendations": ["astro-build.astro-vscode"], + "unwantedRecommendations": [] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index c639b08..230708d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,11 +1,11 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "command": "./node_modules/.bin/astro dev", - "name": "Development server", - "request": "launch", - "type": "node-terminal" - } - ] -} +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/e2e/README.md b/e2e/README.md index e54ba9e..bb3a949 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -1,5 +1,3 @@ # e2e Hold End 2 End tests - -currently WIP diff --git a/e2e/example.spec.ts b/e2e/example.spec.ts index 2bddfcd..86f27e9 100644 --- a/e2e/example.spec.ts +++ b/e2e/example.spec.ts @@ -4,5 +4,5 @@ test('has title', async ({ page }) => { await page.goto('/'); // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Astro/); + await expect(page).toHaveTitle(/Dzeio/); }) diff --git a/package.json b/package.json index 968d957..ad3d8d8 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "@dzeio/url-manager": "^1", "astro": "^3", "lucide-astro": "^0", - "sharp": "^0.32.6", + "sharp": "^0", + "simple-icons-astro": "^9", "tailwindcss": "^3" }, "devDependencies": { @@ -30,7 +31,7 @@ "@playwright/test": "^1", "@types/node": "^20", "@vitest/coverage-v8": "^0", - "typescript": "^5.2.2", + "typescript": "^5", "vitest": "^0" } } diff --git a/src/assets/components/layouts/Header/Logo.svg b/src/assets/components/layouts/Header/Logo.svg new file mode 100644 index 0000000..f157bd1 --- /dev/null +++ b/src/assets/components/layouts/Header/Logo.svg @@ -0,0 +1,9 @@ + + + + diff --git a/src/assets/layouts/Base/favicon.png b/src/assets/layouts/Head/favicon.png similarity index 100% rename from src/assets/layouts/Base/favicon.png rename to src/assets/layouts/Head/favicon.png diff --git a/src/assets/layouts/Base/favicon.svg b/src/assets/layouts/Head/favicon.svg similarity index 98% rename from src/assets/layouts/Base/favicon.svg rename to src/assets/layouts/Head/favicon.svg index 4684adf..f157bd1 100644 --- a/src/assets/layouts/Base/favicon.svg +++ b/src/assets/layouts/Head/favicon.svg @@ -1,9 +1,9 @@ - - - - + + + + diff --git a/src/assets/pages/404/404.light.svg b/src/assets/pages/404/404.light.svg new file mode 100644 index 0000000..d06fd45 --- /dev/null +++ b/src/assets/pages/404/404.light.svg @@ -0,0 +1 @@ + diff --git a/src/assets/pages/404/404.svg b/src/assets/pages/404/404.svg new file mode 100644 index 0000000..9b5bfee --- /dev/null +++ b/src/assets/pages/404/404.svg @@ -0,0 +1 @@ + diff --git a/src/components/global/Breadcrumb.astro b/src/components/global/Breadcrumb.astro new file mode 100644 index 0000000..ea59782 --- /dev/null +++ b/src/components/global/Breadcrumb.astro @@ -0,0 +1,28 @@ +--- +interface Props { + items: Array<{ + text: string + href?: string | undefined + }> +} +--- + + + + {Astro.props.items.map((el, index) => ( + + {index > 0 && ( + / + )} + {el.href ? ( + + {el.text} + + ) : ( + {el.text} + )} + + + ))} + + diff --git a/src/components/global/Button.astro b/src/components/global/Button.astro new file mode 100644 index 0000000..5f7a38b --- /dev/null +++ b/src/components/global/Button.astro @@ -0,0 +1,35 @@ +--- +import { objectOmit } from '@dzeio/object-util' + +interface Props extends Omit { + type?: 'outline' | 'ghost' +} + +const classes = [ + "button", + Astro.props.type, + Astro.props.class +] + +--- +{'href' in Astro.props && ( + + + +) || ( + + + +)} + + diff --git a/src/components/global/Input.astro b/src/components/global/Input.astro new file mode 100644 index 0000000..41fc52a --- /dev/null +++ b/src/components/global/Input.astro @@ -0,0 +1,83 @@ +--- +import { objectOmit } from '@dzeio/object-util' +interface Props extends Omit { + label?: string + type?: astroHTML.JSX.InputHTMLAttributes['type'] | 'textarea' + block?: boolean + suffix?: string + prefix?: string +} + +const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix') + +if (baseProps.type === 'textarea') { + delete baseProps.type +} +--- + + + + {Astro.props.label && ( + {Astro.props.label} + )} + + + {Astro.props.prefix && ( + {Astro.props.prefix} + )} + {Astro.props.type === 'textarea' && ( + + ) || ( + + )} + {Astro.props.suffix && ( + {Astro.props.suffix} + )} + + + + + + diff --git a/src/components/Picture.astro b/src/components/global/Picture.astro similarity index 98% rename from src/components/Picture.astro rename to src/components/global/Picture.astro index 8855d84..cb919de 100644 --- a/src/components/Picture.astro +++ b/src/components/global/Picture.astro @@ -1,6 +1,6 @@ --- import { getImage } from 'astro:assets' -import AstroUtils from '../libs/AstroUtils' +import AstroUtils from '../../libs/AstroUtils' import { objectOmit } from '@dzeio/object-util' const formats = [ diff --git a/src/components/global/Range.astro b/src/components/global/Range.astro new file mode 100644 index 0000000..c0d306a --- /dev/null +++ b/src/components/global/Range.astro @@ -0,0 +1,30 @@ +--- +import { objectOmit } from '@dzeio/object-util' +interface Props extends Omit { + label?: string + block?: boolean +} + +const baseProps = objectOmit(Astro.props, 'label', 'block') + +--- + + + {Astro.props.label && ( + {Astro.props.label} + )} + + + + diff --git a/src/components/global/Select.astro b/src/components/global/Select.astro new file mode 100644 index 0000000..a77e00f --- /dev/null +++ b/src/components/global/Select.astro @@ -0,0 +1,103 @@ +--- +import { objectOmit } from '@dzeio/object-util' + +export interface Props extends Omit { + placeholder?: string + label?: string + block?: boolean + suffix?: string + prefix?: string + options: Array +} + +const baseProps = objectOmit(Astro.props, 'label', 'block', 'suffix', 'prefix', 'options') +--- + + + + {Astro.props.label && ( + {Astro.props.label} + )} + + + {Astro.props.prefix && ( + {Astro.props.prefix} + )} + + + {Astro.props.options.map((it) => { + if (typeof it !== 'object') { + it = {title: it} + } + return ( + + {it.title} + {it.description && ( + {it.description} + )} + + ) + })} + + {Astro.props.suffix && ( + {Astro.props.suffix} + )} + + + + + + diff --git a/src/components/global/Table/Table.astro b/src/components/global/Table/Table.astro new file mode 100644 index 0000000..e1c8362 --- /dev/null +++ b/src/components/global/Table/Table.astro @@ -0,0 +1,47 @@ +--- +import { objectClone } from '@dzeio/object-util' +import type TableProps from './TableProps' +export interface Props extends TableProps {} + +const props = objectClone(Astro.props) +delete props.header +delete props.data +--- + + + + {Astro.props.header?.map((it, idx) => {it})} + + + + {Astro.props.data?.map((row, rowIdx) => {row.map((it, cellIdx) => {it})})} + + + + diff --git a/src/components/global/Table/TableProps.ts b/src/components/global/Table/TableProps.ts new file mode 100644 index 0000000..195f84b --- /dev/null +++ b/src/components/global/Table/TableProps.ts @@ -0,0 +1,4 @@ +export default interface TableProps extends astroHTML.JSX.TableHTMLAttributes { + header?: Array | null | undefined + data?: Array> | null | undefined +} diff --git a/src/components/global/Table/TableUtil.ts b/src/components/global/Table/TableUtil.ts new file mode 100644 index 0000000..22c72f6 --- /dev/null +++ b/src/components/global/Table/TableUtil.ts @@ -0,0 +1,95 @@ +import type Props from './TableProps' + +export function updateTable(comp: HTMLTableElement, data: Props, options?: { + keepHeaders?: boolean + keepData?: boolean +}) { + const head = comp.querySelector('thead > tr') + const body = comp.querySelector('tbody') + + if (!head || !body) { + console.error('could not update table') + return + } + + const curHeaders = head.querySelectorAll('th') + const newHeaders = data.header ?? [] + const headersLength = Math.max(newHeaders.length, curHeaders?.length ?? 0) + + for (let headerIdx = 0; headerIdx < headersLength; headerIdx++) { + const headerHTML = curHeaders[headerIdx] + const headerContent = newHeaders[headerIdx] + // new el, add it + if (!headerHTML) { + const el = document.createElement('th') + el.innerText = headerContent as string + el.dataset.idx = headerIdx.toString() + head.appendChild(el) + // header too large, remove + } else if (!headerContent && !options?.keepHeaders) { + head.removeChild(headerHTML) + // replace content + } else if(!options?.keepHeaders) { + headerHTML.innerText = (headerContent ?? '').toString() + } + } + + const curBody = body.querySelectorAll('tr') + const newBody = data.data ?? [] + const bodyLength = Math.max(newBody.length, curBody.length ?? 0) + + for (let bodyRowIdx = 0; bodyRowIdx < bodyLength; bodyRowIdx++) { + const bodyRowHTML = curBody[bodyRowIdx] + const bodyRowContent = newBody[bodyRowIdx] + // new el, add it + if (!bodyRowHTML) { + const row = document.createElement('tr') + row.dataset.row = bodyRowIdx.toString() + for (let cellIdx = 0; cellIdx < (bodyRowContent as Array).length; cellIdx++) { + const cellContent = (bodyRowContent as Array)[cellIdx] as string + const cell = document.createElement('td') + cell.dataset.cell = cellIdx.toString() + cell.innerText = cellContent + row.appendChild(cell) + } + body.appendChild(row) + // body too large, remove row + } else if (!bodyRowContent) { + body.removeChild(bodyRowHTML) + // replace row + } else { + const bodyRowHTML = curBody[bodyRowIdx] as HTMLTableRowElement + const cells = bodyRowHTML!.querySelectorAll('td') + const cellLength = Math.max(cells.length, bodyRowContent.length) + for (let cellIdx = 0; cellIdx < cellLength; cellIdx++) { + const currCell = cells[cellIdx]; + const newCell = bodyRowContent[cellIdx]; + // new el, add it + if (!currCell) { + const el = document.createElement('td') + el.dataset.cell = cellIdx.toString() + el.innerText = newCell as string + bodyRowHTML.appendChild(el) + // header too large, remove + } else if (!newCell && !options?.keepData) { + bodyRowHTML.removeChild(currCell) + // replace content + } else if(!options?.keepData) { + currCell.innerText = (newCell ?? '').toString() + } + } + } + } +} + +export function setOnTableClick(table: HTMLTableElement, fn: (row: number, cell: number) => void | Promise) { + table.querySelector('tbody')?.classList?.add('hover:cursor-pointer') + table.querySelectorAll('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) + fn(rowIdx, cellIdx) + }) + }) +} diff --git a/src/components/Favicon/Favicon.astro b/src/components/layouts/Favicon/Favicon.astro similarity index 100% rename from src/components/Favicon/Favicon.astro rename to src/components/layouts/Favicon/Favicon.astro diff --git a/src/components/Favicon/Manifest.ts b/src/components/layouts/Favicon/Manifest.ts similarity index 100% rename from src/components/Favicon/Manifest.ts rename to src/components/layouts/Favicon/Manifest.ts diff --git a/src/components/layouts/Footer.astro b/src/components/layouts/Footer.astro new file mode 100644 index 0000000..ecfc0be --- /dev/null +++ b/src/components/layouts/Footer.astro @@ -0,0 +1,26 @@ +--- +const year = new Date().getFullYear() + +export interface Props { + links?: Array<{href: string, target?: string, display: string}> + socials?: Array<{href: string, target?: string, icon: any}> +} +--- + + diff --git a/src/components/layouts/Header.astro b/src/components/layouts/Header.astro new file mode 100644 index 0000000..2b36b22 --- /dev/null +++ b/src/components/layouts/Header.astro @@ -0,0 +1,38 @@ +--- +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' + +export interface Props { + right?: Record + left?: Record +} +--- + + + + + + + + {objectMap(Astro.props.left ?? {}, (path, text) => ( + + + {text} + + + ))} + + + {objectMap(Astro.props.right ?? {}, (path, text) => ( + + + {text} + + + ))} + + + diff --git a/src/content/config.ts b/src/content/config.ts deleted file mode 100644 index 98dff2e..0000000 --- a/src/content/config.ts +++ /dev/null @@ -1,15 +0,0 @@ -// 1. Import utilities from `astro:content` -// import { defineCollection, z } from 'astro:content' - -// 2. Define your collection(s) -// const docsCollection = defineCollection({ -// type: 'content', -// schema: z.object({ -// title: z.string() -// }) -// }) -// 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 = { -// 'docs': docsCollection, -// }; diff --git a/src/content/config.ts.tmp b/src/content/config.ts.tmp new file mode 100644 index 0000000..ee2ea89 --- /dev/null +++ b/src/content/config.ts.tmp @@ -0,0 +1,29 @@ +// 1. Import utilities from `astro:content` +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() + }) +}) + + +// 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 = { + projects: projectsCollection +} diff --git a/src/env.d.ts b/src/env.d.ts index f938b3d..651ee79 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,23 +1,20 @@ -/// -/// -/// - -/** - * Environment variables declaration - */ -interface ImportMetaEnv { -} - -interface ImportMeta { - readonly env: ImportMetaEnv; -} - - -declare namespace App { - /** - * Middlewares variables - */ - interface Locals { - responseBuilder: ResponseBuilder - } -} +/// +/// + +/** + * Environment variables declaration + */ +interface ImportMetaEnv { +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} + + +declare namespace App { + /** + * Middlewares variables + */ + interface Locals {} +} diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index a334da3..14121e2 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -1,28 +1,18 @@ --- -export interface Props { - title: string +import Head, { type Props as HeadProps } from './Head.astro' + +export interface Props extends HeadProps { + class?: string } - -import Favicon from '../components/Favicon/Favicon.astro' -import IconSVG from '../assets/layouts/Base/favicon.svg' -import IconPNG from '../assets/layouts/Base/favicon.png' - -const { title } = Astro.props; --- - - - - - - - - {title} + + - + diff --git a/src/layouts/Head.astro b/src/layouts/Head.astro new file mode 100644 index 0000000..05ce73c --- /dev/null +++ b/src/layouts/Head.astro @@ -0,0 +1,132 @@ +--- +import Favicon from 'components/layouts/Favicon/Favicon.astro' +import IconSVG from 'assets/layouts/Head/favicon.svg' +import IconPNG from 'assets/layouts/Head/favicon.png' + +export interface Props { + /** + * Site display name + */ + siteName?: string | undefined + /** + * Page Title + */ + title?: string | undefined + /** + * Page description + */ + description?: string | undefined + /** + * define the cannonical url + */ + canonical?: string | false | undefined + /** + * OpenGraph image(s) + */ + image?: typeof IconPNG | Array | undefined + /** + * Twitter/X Specific options + */ + twitter?: { + title?: string | undefined + card?: "summary" | "summary_large_image" | "app" | "player" | undefined + site?: string | undefined + creator?: string | undefined + } | undefined + /** + * OpenGraph Specific options (override defaults set by other options) + */ + og?: { + title?: string | undefined + type?: string | undefined + description?: string | undefined + url?: string | undefined + } | undefined +} + +const props = Astro.props + +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 +--- + + + + + + + + + + + + + + + +{props.siteName && ( + +)} + + +{props.title && ( + {props.title} + // +)} + + +{props.description && ( + +)} + + +{canonical && ( + +)} + + + + + + +{props.twitter?.site && ( + +)} + + +{props.twitter?.creator && ( + +)} + + +{(props.twitter?.title ?? props.title) && ( + +)} + + + +{(props.og?.title ?? props.title) && ( + +)} + + +{(props.og?.description ?? props.description) && ( + +)} + + + + + +{(props.og?.url ?? canonical) && ( + +)} + + +{image?.map((img) => ( + + + + +))} diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro deleted file mode 100644 index 8c0007a..0000000 --- a/src/layouts/Layout.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- -import Base, { type Props as BaseProps } from './Base.astro' - -export interface Props extends BaseProps {} ---- - - - - - - diff --git a/src/layouts/MainLayout.astro b/src/layouts/MainLayout.astro new file mode 100644 index 0000000..0fbbe09 --- /dev/null +++ b/src/layouts/MainLayout.astro @@ -0,0 +1,30 @@ +--- +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' + +export interface Props extends BaseProps { + /** + * remove the default top padding top allow the content to overflow with the header + */ + hasHero?: boolean +} +--- + + + + + + + + + diff --git a/src/libs/Component.ts b/src/libs/Component.ts new file mode 100644 index 0000000..a638172 --- /dev/null +++ b/src/libs/Component.ts @@ -0,0 +1,53 @@ +type Fn = (el: Component) => void | Promise + +/** + * Component client side initialisation class + */ +export default class Component { + private constructor( + public element: T + ) {} + + public handled(value: boolean): this + public handled(): boolean + public handled(value?: boolean): this | boolean { + if (typeof value === 'undefined') { + return typeof this.element.dataset.handled === 'string' + } + this.element.dataset.handled = '' + return this + } + + public init(fn: (el: Component) => void | Promise) { + if (this.handled()) { + return + } + fn(this) + this.handled(true) + } + + public child(query: string, fn: Fn) { + this.element.querySelectorAll(query).forEach((it) => { + const cp = new Component(it) + cp.init(fn) + }) + } + + /** + * start handling an element + * @param query the query to get the element + * @param fn the function that is run ONCE per elements + */ + public static handle(query: string, fn: (el: T) => void | Promise) { + document.querySelectorAll(query).forEach((it) => { + const cp = new Component(it) + cp.init((it) => fn(it.element)) + }) + document.addEventListener('astro:page-load', () => { + document.querySelectorAll(query).forEach((it) => { + const cp = new Component(it) + cp.init((it) => fn(it.element)) + }) + }) + } +} diff --git a/src/libs/ResponseBuilder.ts b/src/libs/ResponseBuilder.ts index dd14a33..67abd2c 100644 --- a/src/libs/ResponseBuilder.ts +++ b/src/libs/ResponseBuilder.ts @@ -1,10 +1,18 @@ import { objectLoop } from '@dzeio/object-util' +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) + resp.status(statusCode) + return resp.build() + } + private _body: BodyInit | null | undefined public body(body: string | Buffer | object | null | undefined) { if (typeof body === 'object' && !(body instanceof Buffer)) { @@ -42,7 +50,7 @@ export default class ResponseBuilder { } private _status?: number - public status(status: number) { + public status(status: StatusCode | number) { this._status = status return this } diff --git a/src/middleware/index.ts b/src/middleware/index.ts index db3224e..0b7c96c 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -1,5 +1,5 @@ import { sequence } from "astro/middleware" -import responseBuilder from './responseBuilder' +import logger from './logger' -export const onRequest = sequence(responseBuilder) +export const onRequest = sequence(logger) diff --git a/src/middleware/logger.ts b/src/middleware/logger.ts new file mode 100644 index 0000000..6a0a30e --- /dev/null +++ b/src/middleware/logger.ts @@ -0,0 +1,14 @@ +import { defineMiddleware } from "astro/middleware" +import { buildRFC7807 } from '../libs/RFCs/RFC7807' +import ResponseBuilder from '../libs/ResponseBuilder' + +// `context` and `next` are automatically typed +export default defineMiddleware(async ({ request }, next) => { + const prefix = `[${new Date().toISOString()}] ${request.headers.get('user-agent')?.slice(0, 32).padEnd(32)} ${request.method.padEnd(7)}` + console.log(`${prefix} ${request.url}`) + + // can crash if response crash + const res = await next() + console.log(`${prefix} ${res.status} ${request.url}`) + return res +}) diff --git a/src/middleware/responseBuilder.ts b/src/middleware/responseBuilder.ts deleted file mode 100644 index 51b97c4..0000000 --- a/src/middleware/responseBuilder.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { defineMiddleware } from "astro/middleware" -import { buildRFC7807 } from '../libs/RFCs/RFC7807' -import ResponseBuilder from '../libs/ResponseBuilder' - -// `context` and `next` are automatically typed -export default defineMiddleware(async ({ request, locals }, next) => { - locals.responseBuilder = new ResponseBuilder() - console.log(`[${new Date().toISOString()}] ${request.headers.get('user-agent')?.slice(0, 32).padEnd(32)} ${request.method.padEnd(7)} ${request.url}`) - - try { - const res = await next() - console.log(`[${new Date().toISOString()}] ${request.headers.get('user-agent')?.slice(0, 32).padEnd(32)} ${request.method.padEnd(7)} ${res.status} ${request.url}`) - return res - } catch (e) { - console.error(e) - return buildRFC7807({ - type: '/docs/errors/global-error', - status: 500 - }) - } -}) diff --git a/src/models/Dao.ts b/src/models/Dao.ts index 3384ef5..811a20e 100644 --- a/src/models/Dao.ts +++ b/src/models/Dao.ts @@ -11,7 +11,7 @@ export default abstract class Dao { * @param obj the object to create * @returns the object with it's id filled if create or null otherwise */ - abstract create(obj: Omit): Promise + abstract create(obj: Omit): Promise /** * insert a new object into the source @@ -81,12 +81,24 @@ export default abstract class Dao { */ abstract update(obj: Object): Promise + /** + * change some elements from the object and return the object updated + * @param id the id of the object + * @param changegs the change to make + */ + public async patch(id: string, changes: Partial): Promise { + const query = await this.findById(id) + if (!query) { + return null + } + 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): Promise { + public async upsert(object: Object | Omit): Promise { if ('id' in object) { return this.update(object) } diff --git a/src/pages/404.astro b/src/pages/404.astro new file mode 100644 index 0000000..a300d60 --- /dev/null +++ b/src/pages/404.astro @@ -0,0 +1,24 @@ +--- +import MainLayout from 'layouts/MainLayout.astro' +import I404 from 'assets/pages/404/404.svg' +import I404Light from 'assets/pages/404/404.light.svg' +import Button from 'components/global/Button.astro' +import Picture from 'components/global/Picture.astro' +--- + + + + 404 La page recherché n'existe pas :( + + + Retour à la page d'accueil + Retour à la page précédente + + + + + diff --git a/src/pages/api/event.ts b/src/pages/api/event.ts new file mode 100644 index 0000000..4bcb71d --- /dev/null +++ b/src/pages/api/event.ts @@ -0,0 +1,23 @@ +import type { APIRoute } from 'astro' +import ResponseBuilder from '../../libs/ResponseBuilder' + +/** + * Plausible proxy + */ +export const POST: APIRoute = async ({ request, clientAddress }) => { + // const body = await request.json() + // console.log(body, clientAddress) + const res = await fetch('https://plausible.io/api/event', { + method: 'POST', + headers: { + 'User-Agent': request.headers.get('User-Agent') as string, + 'X-Forwarded-For': clientAddress, + 'Content-Type': 'application/json' + }, + body: await request.text() + }) + return new ResponseBuilder() + .status(res.status) + .body(await res.text()) + .build() +} diff --git a/src/pages/event.ts b/src/pages/event.ts new file mode 100644 index 0000000..4bcb71d --- /dev/null +++ b/src/pages/event.ts @@ -0,0 +1,23 @@ +import type { APIRoute } from 'astro' +import ResponseBuilder from '../../libs/ResponseBuilder' + +/** + * Plausible proxy + */ +export const POST: APIRoute = async ({ request, clientAddress }) => { + // const body = await request.json() + // console.log(body, clientAddress) + const res = await fetch('https://plausible.io/api/event', { + method: 'POST', + headers: { + 'User-Agent': request.headers.get('User-Agent') as string, + 'X-Forwarded-For': clientAddress, + 'Content-Type': 'application/json' + }, + body: await request.text() + }) + return new ResponseBuilder() + .status(res.status) + .body(await res.text()) + .build() +} diff --git a/src/pages/index.astro b/src/pages/index.astro index 86aac1f..96ace34 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,13 +1,10 @@ ---- -import Layout from '../layouts/Layout.astro' ---- - - - - Welcome to Astro - - To get started, open the directory src/pages in your project. - Code Challenge: Tweak the "Welcome to Astro" message above. - - - +--- +import MainLayout from 'layouts/MainLayout.astro' +--- + + + + Dzeio Astro Template + Start editing src/pages/index.astro to see your changes! + + diff --git a/src/pages/js/script.js.ts b/src/pages/js/script.js.ts new file mode 100644 index 0000000..51c5637 --- /dev/null +++ b/src/pages/js/script.js.ts @@ -0,0 +1,13 @@ +import type { APIRoute } from 'astro' +import ResponseBuilder from '../../libs/ResponseBuilder' + +/** + * Plausible proxy + */ +export const GET: APIRoute = async () => { + const res = await fetch('https://plausible.io/js/script.outbound-links.tagged-events.js') + return new ResponseBuilder() + .status(200) + .body(await res.text()) + .build() +} diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 903ebd7..9d92c31 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -1,4 +1,5 @@ -// const defaultTheme = require('tailwindcss/defaultTheme') +const defaultTheme = require('tailwindcss/defaultTheme') +const colors = require('tailwindcss/colors') /** @type {import('tailwindcss').Config} */ module.exports = {
{Astro.props.prefix}
{Astro.props.suffix}
{it.title}
{it.description}
- To get started, open the directory src/pages in your project. - Code Challenge: Tweak the "Welcome to Astro" message above. -
src/pages