Compare commits
25 Commits
8794bd6aea
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7865fa2c2d | |||
| 29bb0e860a | |||
| c1af1e8ebb | |||
| 798f2c1e44 | |||
| 22772ad436 | |||
| cb027a6e92 | |||
| c1e39bbf86 | |||
| aa58dfe58b | |||
| ce2eabf1f5 | |||
| e934d4893a | |||
| dad55dab39 | |||
| fc7cd5a60a | |||
| e71acd2ce1 | |||
| 1fdf07cdf7 | |||
| 4f3eee5a49 | |||
| 8b4051e473 | |||
| 3f61caf04f | |||
| e28b6a053e | |||
| ef72a95372 | |||
| 03d8c2ff74 | |||
| cf5801689f | |||
| 6bde837330 | |||
| 760b2feb16 | |||
| 64a4baf38c | |||
| ea173dcc52 |
@@ -1,13 +1,16 @@
|
|||||||
FROM node:lts AS runtime
|
FROM node:lts AS runtime
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
VOLUME [ "/data" ]
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ENV ASTRO_DB_REMOTE_URL=libsql
|
ENV DATABASE_URL=postgresql://memento:test@localhost:5432/memento
|
||||||
|
ENV UPLOAD_DIR=/data/images
|
||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
RUN npx astro db push --remote
|
RUN npx drizzle-kit push
|
||||||
RUN npx astro build --remote
|
RUN npx astro build
|
||||||
|
|
||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV PORT=4321
|
ENV PORT=4321
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { defineConfig } from 'astro/config';
|
|||||||
|
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
|
|
||||||
import db from '@astrojs/db';
|
|
||||||
|
|
||||||
import node from '@astrojs/node';
|
import node from '@astrojs/node';
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
@@ -14,8 +12,6 @@ export default defineConfig({
|
|||||||
plugins: [tailwindcss()]
|
plugins: [tailwindcss()]
|
||||||
},
|
},
|
||||||
|
|
||||||
integrations: [db()],
|
|
||||||
|
|
||||||
adapter: node({
|
adapter: node({
|
||||||
mode: 'standalone'
|
mode: 'standalone'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
services:
|
services:
|
||||||
memento:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- 4321:4321
|
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:17
|
image: postgres:17
|
||||||
ports:
|
ports:
|
||||||
@@ -14,3 +9,8 @@ services:
|
|||||||
POSTGRES_PASSWORD: test
|
POSTGRES_PASSWORD: test
|
||||||
POSTGRES_USER: memento
|
POSTGRES_USER: memento
|
||||||
|
|
||||||
|
memento:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- 4321:4321
|
||||||
|
|
||||||
|
|||||||
11
drizzle.config.ts
Normal file
11
drizzle.config.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
|
import { defineConfig } from 'drizzle-kit';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
out: './drizzle',
|
||||||
|
schema: './src/db/schema.ts',
|
||||||
|
dialect: 'postgresql',
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env.DATABASE_URL!,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -9,8 +9,13 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/db": "^0.18.3",
|
|
||||||
"@astrojs/node": "^9.5.1",
|
"@astrojs/node": "^9.5.1",
|
||||||
|
"@fullcalendar/core": "^6.1.20",
|
||||||
|
"@fullcalendar/daygrid": "^6.1.20",
|
||||||
|
"@fullcalendar/interaction": "^6.1.20",
|
||||||
|
"@fullcalendar/list": "^6.1.20",
|
||||||
|
"@fullcalendar/resource": "^6.1.20",
|
||||||
|
"@fullcalendar/scrollgrid": "^6.1.20",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"astro": "^5.16.8",
|
"astro": "^5.16.8",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
|
|||||||
277
pnpm-lock.yaml
generated
277
pnpm-lock.yaml
generated
@@ -8,12 +8,27 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/db':
|
|
||||||
specifier: ^0.18.3
|
|
||||||
version: 0.18.3(@types/pg@8.16.0)(pg@8.16.3)
|
|
||||||
'@astrojs/node':
|
'@astrojs/node':
|
||||||
specifier: ^9.5.1
|
specifier: ^9.5.1
|
||||||
version: 9.5.1(astro@5.16.8(@types/node@25.0.6)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.55.1)(tsx@4.21.0)(typescript@5.9.3))
|
version: 9.5.1(astro@5.16.8(@types/node@25.0.6)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.55.1)(tsx@4.21.0)(typescript@5.9.3))
|
||||||
|
'@fullcalendar/core':
|
||||||
|
specifier: ^6.1.20
|
||||||
|
version: 6.1.20
|
||||||
|
'@fullcalendar/daygrid':
|
||||||
|
specifier: ^6.1.20
|
||||||
|
version: 6.1.20(@fullcalendar/core@6.1.20)
|
||||||
|
'@fullcalendar/interaction':
|
||||||
|
specifier: ^6.1.20
|
||||||
|
version: 6.1.20(@fullcalendar/core@6.1.20)
|
||||||
|
'@fullcalendar/list':
|
||||||
|
specifier: ^6.1.20
|
||||||
|
version: 6.1.20(@fullcalendar/core@6.1.20)
|
||||||
|
'@fullcalendar/resource':
|
||||||
|
specifier: ^6.1.20
|
||||||
|
version: 6.1.20(@fullcalendar/core@6.1.20)
|
||||||
|
'@fullcalendar/scrollgrid':
|
||||||
|
specifier: ^6.1.20
|
||||||
|
version: 6.1.20(@fullcalendar/core@6.1.20)
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.1.18
|
specifier: ^4.1.18
|
||||||
version: 4.1.18(vite@6.4.1(@types/node@25.0.6)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
|
version: 4.1.18(vite@6.4.1(@types/node@25.0.6)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
|
||||||
@@ -51,9 +66,6 @@ packages:
|
|||||||
'@astrojs/compiler@2.13.0':
|
'@astrojs/compiler@2.13.0':
|
||||||
resolution: {integrity: sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==}
|
resolution: {integrity: sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==}
|
||||||
|
|
||||||
'@astrojs/db@0.18.3':
|
|
||||||
resolution: {integrity: sha512-iTK50jUgyj25oa/JiXSN1/IVp5kTmPuioLlve06LE8/HzWGv3JpVgCKIV9HHf3kOVi1HV/uauXnyWzkB+yHLSQ==}
|
|
||||||
|
|
||||||
'@astrojs/internal-helpers@0.7.5':
|
'@astrojs/internal-helpers@0.7.5':
|
||||||
resolution: {integrity: sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==}
|
resolution: {integrity: sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==}
|
||||||
|
|
||||||
@@ -552,6 +564,39 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@fullcalendar/core@6.1.20':
|
||||||
|
resolution: {integrity: sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==}
|
||||||
|
|
||||||
|
'@fullcalendar/daygrid@6.1.20':
|
||||||
|
resolution: {integrity: sha512-AO9vqhkLP77EesmJzuU+IGXgxNulsA8mgQHynclJ8U70vSwAVnbcLG9qftiTAFSlZjiY/NvhE7sflve6cJelyQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fullcalendar/core': ~6.1.20
|
||||||
|
|
||||||
|
'@fullcalendar/interaction@6.1.20':
|
||||||
|
resolution: {integrity: sha512-p6txmc5txL0bMiPaJxe2ip6o0T384TyoD2KGdsU6UjZ5yoBlaY+dg7kxfnYKpYMzEJLG58n+URrHr2PgNL2fyA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fullcalendar/core': ~6.1.20
|
||||||
|
|
||||||
|
'@fullcalendar/list@6.1.20':
|
||||||
|
resolution: {integrity: sha512-7Hzkbb7uuSqrXwTyD0Ld/7SwWNxPD6SlU548vtkIpH55rZ4qquwtwYdMPgorHos5OynHA4OUrZNcH51CjrCf2g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fullcalendar/core': ~6.1.20
|
||||||
|
|
||||||
|
'@fullcalendar/premium-common@6.1.20':
|
||||||
|
resolution: {integrity: sha512-rT+AitNnRyZuFEtYvsB1OJ2g1Bq2jmTR6qdn/dEU6LwkIj/4L499goLtMOena/JyJ31VBztdHrccX//36QrY3w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fullcalendar/core': ~6.1.20
|
||||||
|
|
||||||
|
'@fullcalendar/resource@6.1.20':
|
||||||
|
resolution: {integrity: sha512-vpQs1eYJbc1zGOzF3obVVr+XsHTMTG7STKVQBEGy3AeFgfosRkUz+3DUawmy98vSjJUYOAQHO+pWW0ek0n5g0w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fullcalendar/core': ~6.1.20
|
||||||
|
|
||||||
|
'@fullcalendar/scrollgrid@6.1.20':
|
||||||
|
resolution: {integrity: sha512-M55m0hxpou4IPObto5f0nVcXvIj3rkSTba0ypclSFDwBz3JxuCPS6l8kaUznqlZCr2Ld/HFJr+jwyvY070AafQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fullcalendar/core': ~6.1.20
|
||||||
|
|
||||||
'@img/colour@1.0.0':
|
'@img/colour@1.0.0':
|
||||||
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
|
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -1205,10 +1250,6 @@ packages:
|
|||||||
decode-named-character-reference@1.2.0:
|
decode-named-character-reference@1.2.0:
|
||||||
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
|
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
|
||||||
|
|
||||||
deep-diff@1.0.2:
|
|
||||||
resolution: {integrity: sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==}
|
|
||||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
|
||||||
|
|
||||||
defu@6.1.4:
|
defu@6.1.4:
|
||||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||||
|
|
||||||
@@ -1269,95 +1310,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==}
|
resolution: {integrity: sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
drizzle-orm@0.42.0:
|
|
||||||
resolution: {integrity: sha512-pS8nNJm2kBNZwrOjTHJfdKkaU+KuUQmV/vk5D57NojDq4FG+0uAYGMulXtYT///HfgsMF0hnFFvu1ezI3OwOkg==}
|
|
||||||
peerDependencies:
|
|
||||||
'@aws-sdk/client-rds-data': '>=3'
|
|
||||||
'@cloudflare/workers-types': '>=4'
|
|
||||||
'@electric-sql/pglite': '>=0.2.0'
|
|
||||||
'@libsql/client': '>=0.10.0'
|
|
||||||
'@libsql/client-wasm': '>=0.10.0'
|
|
||||||
'@neondatabase/serverless': '>=0.10.0'
|
|
||||||
'@op-engineering/op-sqlite': '>=2'
|
|
||||||
'@opentelemetry/api': ^1.4.1
|
|
||||||
'@planetscale/database': '>=1.13'
|
|
||||||
'@prisma/client': '*'
|
|
||||||
'@tidbcloud/serverless': '*'
|
|
||||||
'@types/better-sqlite3': '*'
|
|
||||||
'@types/pg': '*'
|
|
||||||
'@types/sql.js': '*'
|
|
||||||
'@vercel/postgres': '>=0.8.0'
|
|
||||||
'@xata.io/client': '*'
|
|
||||||
better-sqlite3: '>=7'
|
|
||||||
bun-types: '*'
|
|
||||||
expo-sqlite: '>=14.0.0'
|
|
||||||
gel: '>=2'
|
|
||||||
knex: '*'
|
|
||||||
kysely: '*'
|
|
||||||
mysql2: '>=2'
|
|
||||||
pg: '>=8'
|
|
||||||
postgres: '>=3'
|
|
||||||
prisma: '*'
|
|
||||||
sql.js: '>=1'
|
|
||||||
sqlite3: '>=5'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@aws-sdk/client-rds-data':
|
|
||||||
optional: true
|
|
||||||
'@cloudflare/workers-types':
|
|
||||||
optional: true
|
|
||||||
'@electric-sql/pglite':
|
|
||||||
optional: true
|
|
||||||
'@libsql/client':
|
|
||||||
optional: true
|
|
||||||
'@libsql/client-wasm':
|
|
||||||
optional: true
|
|
||||||
'@neondatabase/serverless':
|
|
||||||
optional: true
|
|
||||||
'@op-engineering/op-sqlite':
|
|
||||||
optional: true
|
|
||||||
'@opentelemetry/api':
|
|
||||||
optional: true
|
|
||||||
'@planetscale/database':
|
|
||||||
optional: true
|
|
||||||
'@prisma/client':
|
|
||||||
optional: true
|
|
||||||
'@tidbcloud/serverless':
|
|
||||||
optional: true
|
|
||||||
'@types/better-sqlite3':
|
|
||||||
optional: true
|
|
||||||
'@types/pg':
|
|
||||||
optional: true
|
|
||||||
'@types/sql.js':
|
|
||||||
optional: true
|
|
||||||
'@vercel/postgres':
|
|
||||||
optional: true
|
|
||||||
'@xata.io/client':
|
|
||||||
optional: true
|
|
||||||
better-sqlite3:
|
|
||||||
optional: true
|
|
||||||
bun-types:
|
|
||||||
optional: true
|
|
||||||
expo-sqlite:
|
|
||||||
optional: true
|
|
||||||
gel:
|
|
||||||
optional: true
|
|
||||||
knex:
|
|
||||||
optional: true
|
|
||||||
kysely:
|
|
||||||
optional: true
|
|
||||||
mysql2:
|
|
||||||
optional: true
|
|
||||||
pg:
|
|
||||||
optional: true
|
|
||||||
postgres:
|
|
||||||
optional: true
|
|
||||||
prisma:
|
|
||||||
optional: true
|
|
||||||
sql.js:
|
|
||||||
optional: true
|
|
||||||
sqlite3:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
drizzle-orm@0.45.1:
|
drizzle-orm@0.45.1:
|
||||||
resolution: {integrity: sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==}
|
resolution: {integrity: sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1919,11 +1871,6 @@ packages:
|
|||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
nanoid@5.1.6:
|
|
||||||
resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}
|
|
||||||
engines: {node: ^18 || >=20}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
neotraverse@0.6.18:
|
neotraverse@0.6.18:
|
||||||
resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==}
|
resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
@@ -2061,6 +2008,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
|
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
preact@10.12.1:
|
||||||
|
resolution: {integrity: sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==}
|
||||||
|
|
||||||
prismjs@1.30.0:
|
prismjs@1.30.0:
|
||||||
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -2524,47 +2474,6 @@ snapshots:
|
|||||||
|
|
||||||
'@astrojs/compiler@2.13.0': {}
|
'@astrojs/compiler@2.13.0': {}
|
||||||
|
|
||||||
'@astrojs/db@0.18.3(@types/pg@8.16.0)(pg@8.16.3)':
|
|
||||||
dependencies:
|
|
||||||
'@libsql/client': 0.15.15
|
|
||||||
deep-diff: 1.0.2
|
|
||||||
drizzle-orm: 0.42.0(@libsql/client@0.15.15)(@types/pg@8.16.0)(pg@8.16.3)
|
|
||||||
nanoid: 5.1.6
|
|
||||||
piccolore: 0.1.3
|
|
||||||
prompts: 2.4.2
|
|
||||||
yargs-parser: 21.1.1
|
|
||||||
zod: 3.25.76
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@aws-sdk/client-rds-data'
|
|
||||||
- '@cloudflare/workers-types'
|
|
||||||
- '@electric-sql/pglite'
|
|
||||||
- '@libsql/client-wasm'
|
|
||||||
- '@neondatabase/serverless'
|
|
||||||
- '@op-engineering/op-sqlite'
|
|
||||||
- '@opentelemetry/api'
|
|
||||||
- '@planetscale/database'
|
|
||||||
- '@prisma/client'
|
|
||||||
- '@tidbcloud/serverless'
|
|
||||||
- '@types/better-sqlite3'
|
|
||||||
- '@types/pg'
|
|
||||||
- '@types/sql.js'
|
|
||||||
- '@vercel/postgres'
|
|
||||||
- '@xata.io/client'
|
|
||||||
- better-sqlite3
|
|
||||||
- bufferutil
|
|
||||||
- bun-types
|
|
||||||
- expo-sqlite
|
|
||||||
- gel
|
|
||||||
- knex
|
|
||||||
- kysely
|
|
||||||
- mysql2
|
|
||||||
- pg
|
|
||||||
- postgres
|
|
||||||
- prisma
|
|
||||||
- sql.js
|
|
||||||
- sqlite3
|
|
||||||
- utf-8-validate
|
|
||||||
|
|
||||||
'@astrojs/internal-helpers@0.7.5': {}
|
'@astrojs/internal-helpers@0.7.5': {}
|
||||||
|
|
||||||
'@astrojs/markdown-remark@6.3.10':
|
'@astrojs/markdown-remark@6.3.10':
|
||||||
@@ -2874,6 +2783,36 @@ snapshots:
|
|||||||
'@esbuild/win32-x64@0.27.2':
|
'@esbuild/win32-x64@0.27.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@fullcalendar/core@6.1.20':
|
||||||
|
dependencies:
|
||||||
|
preact: 10.12.1
|
||||||
|
|
||||||
|
'@fullcalendar/daygrid@6.1.20(@fullcalendar/core@6.1.20)':
|
||||||
|
dependencies:
|
||||||
|
'@fullcalendar/core': 6.1.20
|
||||||
|
|
||||||
|
'@fullcalendar/interaction@6.1.20(@fullcalendar/core@6.1.20)':
|
||||||
|
dependencies:
|
||||||
|
'@fullcalendar/core': 6.1.20
|
||||||
|
|
||||||
|
'@fullcalendar/list@6.1.20(@fullcalendar/core@6.1.20)':
|
||||||
|
dependencies:
|
||||||
|
'@fullcalendar/core': 6.1.20
|
||||||
|
|
||||||
|
'@fullcalendar/premium-common@6.1.20(@fullcalendar/core@6.1.20)':
|
||||||
|
dependencies:
|
||||||
|
'@fullcalendar/core': 6.1.20
|
||||||
|
|
||||||
|
'@fullcalendar/resource@6.1.20(@fullcalendar/core@6.1.20)':
|
||||||
|
dependencies:
|
||||||
|
'@fullcalendar/core': 6.1.20
|
||||||
|
'@fullcalendar/premium-common': 6.1.20(@fullcalendar/core@6.1.20)
|
||||||
|
|
||||||
|
'@fullcalendar/scrollgrid@6.1.20(@fullcalendar/core@6.1.20)':
|
||||||
|
dependencies:
|
||||||
|
'@fullcalendar/core': 6.1.20
|
||||||
|
'@fullcalendar/premium-common': 6.1.20(@fullcalendar/core@6.1.20)
|
||||||
|
|
||||||
'@img/colour@1.0.0':
|
'@img/colour@1.0.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -3000,10 +2939,12 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@libsql/core@0.15.15':
|
'@libsql/core@0.15.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
js-base64: 3.7.8
|
js-base64: 3.7.8
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@libsql/darwin-arm64@0.5.22':
|
'@libsql/darwin-arm64@0.5.22':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -3020,8 +2961,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@libsql/isomorphic-fetch@0.3.1': {}
|
'@libsql/isomorphic-fetch@0.3.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@libsql/isomorphic-ws@0.1.5':
|
'@libsql/isomorphic-ws@0.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3030,6 +2973,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@libsql/linux-arm-gnueabihf@0.5.22':
|
'@libsql/linux-arm-gnueabihf@0.5.22':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -3052,7 +2996,8 @@ snapshots:
|
|||||||
'@libsql/win32-x64-msvc@0.5.22':
|
'@libsql/win32-x64-msvc@0.5.22':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@neon-rs/load@0.0.4': {}
|
'@neon-rs/load@0.0.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@oslojs/encoding@1.1.0': {}
|
'@oslojs/encoding@1.1.0': {}
|
||||||
|
|
||||||
@@ -3275,6 +3220,7 @@ snapshots:
|
|||||||
'@types/ws@8.18.1':
|
'@types/ws@8.18.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.0.6
|
'@types/node': 25.0.6
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
|
|
||||||
@@ -3486,7 +3432,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
css-tree: 2.2.1
|
css-tree: 2.2.1
|
||||||
|
|
||||||
data-uri-to-buffer@4.0.1: {}
|
data-uri-to-buffer@4.0.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3496,8 +3443,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
character-entities: 2.0.2
|
character-entities: 2.0.2
|
||||||
|
|
||||||
deep-diff@1.0.2: {}
|
|
||||||
|
|
||||||
defu@6.1.4: {}
|
defu@6.1.4: {}
|
||||||
|
|
||||||
depd@2.0.0: {}
|
depd@2.0.0: {}
|
||||||
@@ -3506,7 +3451,8 @@ snapshots:
|
|||||||
|
|
||||||
destr@2.0.5: {}
|
destr@2.0.5: {}
|
||||||
|
|
||||||
detect-libc@2.0.2: {}
|
detect-libc@2.0.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
detect-libc@2.1.2: {}
|
detect-libc@2.1.2: {}
|
||||||
|
|
||||||
@@ -3553,12 +3499,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
drizzle-orm@0.42.0(@libsql/client@0.15.15)(@types/pg@8.16.0)(pg@8.16.3):
|
|
||||||
optionalDependencies:
|
|
||||||
'@libsql/client': 0.15.15
|
|
||||||
'@types/pg': 8.16.0
|
|
||||||
pg: 8.16.3
|
|
||||||
|
|
||||||
drizzle-orm@0.45.1(@libsql/client@0.15.15)(@types/pg@8.16.0)(pg@8.16.3):
|
drizzle-orm@0.45.1(@libsql/client@0.15.15)(@types/pg@8.16.0)(pg@8.16.3):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@libsql/client': 0.15.15
|
'@libsql/client': 0.15.15
|
||||||
@@ -3702,6 +3642,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
node-domexception: 1.0.0
|
node-domexception: 1.0.0
|
||||||
web-streams-polyfill: 3.3.3
|
web-streams-polyfill: 3.3.3
|
||||||
|
optional: true
|
||||||
|
|
||||||
flattie@1.1.1: {}
|
flattie@1.1.1: {}
|
||||||
|
|
||||||
@@ -3716,6 +3657,7 @@ snapshots:
|
|||||||
formdata-polyfill@4.0.10:
|
formdata-polyfill@4.0.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
fetch-blob: 3.2.0
|
fetch-blob: 3.2.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
fresh@2.0.0: {}
|
fresh@2.0.0: {}
|
||||||
|
|
||||||
@@ -3867,7 +3809,8 @@ snapshots:
|
|||||||
|
|
||||||
jiti@2.6.1: {}
|
jiti@2.6.1: {}
|
||||||
|
|
||||||
js-base64@3.7.8: {}
|
js-base64@3.7.8:
|
||||||
|
optional: true
|
||||||
|
|
||||||
js-yaml@4.1.1:
|
js-yaml@4.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3889,6 +3832,7 @@ snapshots:
|
|||||||
'@libsql/linux-x64-gnu': 0.5.22
|
'@libsql/linux-x64-gnu': 0.5.22
|
||||||
'@libsql/linux-x64-musl': 0.5.22
|
'@libsql/linux-x64-musl': 0.5.22
|
||||||
'@libsql/win32-x64-msvc': 0.5.22
|
'@libsql/win32-x64-msvc': 0.5.22
|
||||||
|
optional: true
|
||||||
|
|
||||||
lightningcss-android-arm64@1.30.2:
|
lightningcss-android-arm64@1.30.2:
|
||||||
optional: true
|
optional: true
|
||||||
@@ -4288,15 +4232,14 @@ snapshots:
|
|||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
nanoid@5.1.6: {}
|
|
||||||
|
|
||||||
neotraverse@0.6.18: {}
|
neotraverse@0.6.18: {}
|
||||||
|
|
||||||
nlcst-to-string@4.0.0:
|
nlcst-to-string@4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/nlcst': 2.0.3
|
'@types/nlcst': 2.0.3
|
||||||
|
|
||||||
node-domexception@1.0.0: {}
|
node-domexception@1.0.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
node-fetch-native@1.6.7: {}
|
node-fetch-native@1.6.7: {}
|
||||||
|
|
||||||
@@ -4305,6 +4248,7 @@ snapshots:
|
|||||||
data-uri-to-buffer: 4.0.1
|
data-uri-to-buffer: 4.0.1
|
||||||
fetch-blob: 3.2.0
|
fetch-blob: 3.2.0
|
||||||
formdata-polyfill: 4.0.10
|
formdata-polyfill: 4.0.10
|
||||||
|
optional: true
|
||||||
|
|
||||||
node-mock-http@1.0.4: {}
|
node-mock-http@1.0.4: {}
|
||||||
|
|
||||||
@@ -4421,9 +4365,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
xtend: 4.0.2
|
xtend: 4.0.2
|
||||||
|
|
||||||
|
preact@10.12.1: {}
|
||||||
|
|
||||||
prismjs@1.30.0: {}
|
prismjs@1.30.0: {}
|
||||||
|
|
||||||
promise-limit@2.7.0: {}
|
promise-limit@2.7.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
prompts@2.4.2:
|
prompts@2.4.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4856,7 +4803,8 @@ snapshots:
|
|||||||
|
|
||||||
web-namespaces@2.0.1: {}
|
web-namespaces@2.0.1: {}
|
||||||
|
|
||||||
web-streams-polyfill@3.3.3: {}
|
web-streams-polyfill@3.3.3:
|
||||||
|
optional: true
|
||||||
|
|
||||||
which-pm-runs@1.1.0: {}
|
which-pm-runs@1.1.0: {}
|
||||||
|
|
||||||
@@ -4870,7 +4818,8 @@ snapshots:
|
|||||||
string-width: 7.2.0
|
string-width: 7.2.0
|
||||||
strip-ansi: 7.1.2
|
strip-ansi: 7.1.2
|
||||||
|
|
||||||
ws@8.19.0: {}
|
ws@8.19.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
xtend@4.0.2: {}
|
xtend@4.0.2: {}
|
||||||
|
|
||||||
|
|||||||
33
src/component/calendar.astro
Normal file
33
src/component/calendar.astro
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<div id="calendar" class="bg-[#009FB75f] p-4 rounded-xl"/>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Calendar } from '@fullcalendar/core';
|
||||||
|
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||||
|
import listPlugin from '@fullcalendar/list';
|
||||||
|
import interactionPlugin from '@fullcalendar/interaction';
|
||||||
|
import scrollgridPlugin from '@fullcalendar/scrollgrid';
|
||||||
|
import resourcePlugin from '@fullcalendar/resource';
|
||||||
|
|
||||||
|
let calendarEl = document.getElementById('calendar')!;
|
||||||
|
let calendar = new Calendar(calendarEl, {
|
||||||
|
plugins: [ dayGridPlugin, listPlugin, interactionPlugin, scrollgridPlugin, resourcePlugin ],
|
||||||
|
initialView: 'dayGridMonth',
|
||||||
|
schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives',
|
||||||
|
headerToolbar: {
|
||||||
|
left: 'prev,next',
|
||||||
|
center: 'title',
|
||||||
|
right: 'today'
|
||||||
|
},
|
||||||
|
selectable: true,
|
||||||
|
|
||||||
|
select: function(info) {
|
||||||
|
window.location.href = `/?editor=${info.startStr}`
|
||||||
|
},
|
||||||
|
datesSet: function(info) {
|
||||||
|
// Called when the date range changes (e.g., when navigating months)
|
||||||
|
// console.log(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
calendar.render();
|
||||||
|
|
||||||
|
</script>
|
||||||
15
src/component/core/head.astro
Normal file
15
src/component/core/head.astro
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
const {
|
||||||
|
title
|
||||||
|
} = Astro.props
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<title>{title}</title>
|
||||||
|
</head>
|
||||||
21
src/component/core/layout.astro
Normal file
21
src/component/core/layout.astro
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
import Head from "./head.astro";
|
||||||
|
import Navbar from "./navbar.astro"
|
||||||
|
|
||||||
|
const {
|
||||||
|
title = "Memento"
|
||||||
|
} = Astro.props
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<Head
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
src/component/core/navbar.astro
Normal file
3
src/component/core/navbar.astro
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="flex w-full h-14 px-2 text-2xl rounded-xl mb-4 bg-[#009FB7] place-items-center">
|
||||||
|
<p>Memento</p>
|
||||||
|
</div>
|
||||||
52
src/component/editor.astro
Normal file
52
src/component/editor.astro
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
const {
|
||||||
|
entry,
|
||||||
|
editMode
|
||||||
|
} = Astro.props
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Share the data from the server -->
|
||||||
|
<div id="data-entry" hidden>{entry}</div>
|
||||||
|
<div id="data-editMode" hidden>{editMode}</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Quill from "quill";
|
||||||
|
import { uploadEntry } from '../utils/quill'
|
||||||
|
import type { Entry } from "../utils/quill";
|
||||||
|
|
||||||
|
const entry = JSON.parse(document.getElementById('data-entry')!.innerText)
|
||||||
|
const editMode = document.getElementById('data-editMode')!.innerText === 'true' ? true : false
|
||||||
|
|
||||||
|
console.log(editMode)
|
||||||
|
|
||||||
|
const quill = new Quill('#editor', {
|
||||||
|
modules: {
|
||||||
|
toolbar: [
|
||||||
|
['bold', 'italic', 'underline'],
|
||||||
|
['image']
|
||||||
|
],
|
||||||
|
},
|
||||||
|
placeholder: 'Compose an epic...',
|
||||||
|
theme: 'bubble',
|
||||||
|
readOnly: !editMode
|
||||||
|
});
|
||||||
|
|
||||||
|
quill.setContents(entry.content)
|
||||||
|
|
||||||
|
document.querySelector("#upload")?.addEventListener('click', async () => {
|
||||||
|
const contents = quill.getContents()
|
||||||
|
|
||||||
|
let entry: Entry = {
|
||||||
|
content: contents,
|
||||||
|
date: '2026-01-13T10:49:43Z',
|
||||||
|
location: null
|
||||||
|
}
|
||||||
|
|
||||||
|
await uploadEntry(entry)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="bg-[#009FB75f] rounded-xl">
|
||||||
|
<div id="editor" />
|
||||||
|
</div>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { integer, pgTable, varchar, date, json } from "drizzle-orm/pg-core";
|
import { integer, pgTable, json, timestamp } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const usersTable = pgTable("users", {
|
export const entryTable = pgTable("entries", {
|
||||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||||
data: date().defaultNow(),
|
date: timestamp({ mode: 'date', withTimezone: true }).notNull().defaultNow(),
|
||||||
location: json(),
|
location: json(),
|
||||||
content: json(),
|
content: json(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { db, Entry } from "astro:db";
|
import { db } from "../../../utils/db"
|
||||||
|
import { entryTable } from "../../../db/schema"
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const entries = await db.select().from(Entry)
|
const entries = await db.select().from(entryTable)
|
||||||
|
|
||||||
return new Response(JSON.stringify(entries))
|
return new Response(JSON.stringify(entries))
|
||||||
}
|
}
|
||||||
72
src/pages/api/entry/index.ts
Normal file
72
src/pages/api/entry/index.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import type { APIContext } from "astro";
|
||||||
|
import { httpResponse } from "@util/response";
|
||||||
|
import { getParams } from "@util/http";
|
||||||
|
import { eq, like, sql } from 'drizzle-orm';
|
||||||
|
import { db } from '@util/db';
|
||||||
|
import { entryTable } from '@db/schema';
|
||||||
|
|
||||||
|
export async function GET({ request }: APIContext) {
|
||||||
|
const { id, date, month } = getParams(request)
|
||||||
|
|
||||||
|
if (id && !isNaN(Number(id))) {
|
||||||
|
return getEntryByID(Number(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date) {
|
||||||
|
return getEntryByDate(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (month) {
|
||||||
|
return getEntryByMonth(month)
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpResponse({ error: 'Failed to retrieve entry' }, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEntryByID(id: number) {
|
||||||
|
try {
|
||||||
|
const entry = await db.select().from(entryTable).where(eq(entryTable.id, id))
|
||||||
|
if (entry.length == 0) {
|
||||||
|
return httpResponse({'error': 'entry not found'}, 404)
|
||||||
|
}
|
||||||
|
return httpResponse(entry[0], 200)
|
||||||
|
} catch {
|
||||||
|
return httpResponse({'error': 'bad request'}, 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEntryByDate(dateString: string) {
|
||||||
|
try {
|
||||||
|
// timezones suck
|
||||||
|
const startDate = new Date(dateString)
|
||||||
|
startDate.setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
|
const endDate = new Date(dateString)
|
||||||
|
endDate.setHours(23, 59, 59, 999)
|
||||||
|
|
||||||
|
const entry = await db.select().from(entryTable).where(
|
||||||
|
sql`${entryTable.date} >= ${startDate.toISOString()}::timestamp AND ${entryTable.date} <= ${endDate.toISOString()}::timestamp`
|
||||||
|
)
|
||||||
|
|
||||||
|
return httpResponse(entry, 200)
|
||||||
|
} catch(error) {
|
||||||
|
return httpResponse({'error': error}, 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEntryByMonth(monthString: string) {
|
||||||
|
try {
|
||||||
|
const [year, month] = monthString.split('-').map(Number)
|
||||||
|
|
||||||
|
const startDate = new Date(year, month - 1, 1, 0, 0, 0, 0)
|
||||||
|
const endDate = new Date(year, month, 1, 0, 0, 0, 0)
|
||||||
|
|
||||||
|
const entries = await db.select().from(entryTable).where(
|
||||||
|
sql`${entryTable.date} >= ${startDate.toISOString()}::timestamp AND ${entryTable.date} < ${endDate.toISOString()}::timestamp`
|
||||||
|
)
|
||||||
|
|
||||||
|
return httpResponse(entries, 200)
|
||||||
|
} catch(error) {
|
||||||
|
return httpResponse({'error': error}, 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
import { db, Entry } from "astro:db";
|
import { db } from "../../../utils/db"
|
||||||
|
import { entryTable } from "../../../db/schema"
|
||||||
|
import { httpResponse } from "../../../utils/response";
|
||||||
|
import type { APIContext } from "astro";
|
||||||
|
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }: APIContext) {
|
||||||
|
|
||||||
if (request.headers.get("Content-Type") === "application/json") {
|
if (request.headers.get("Content-Type") === "application/json") {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
const entry: typeof entryTable.$inferInsert = body
|
||||||
await db.insert(Entry).values(body)
|
entry.date = new Date(body.date)
|
||||||
} catch(e) {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
"error": `Malformed JSON (${e})`
|
|
||||||
}),{ status: 400 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(null, { status: 200 });
|
await db.insert(entryTable).values(entry)
|
||||||
|
|
||||||
|
return httpResponse({ "success": entry }, 200);
|
||||||
|
} catch(e) {
|
||||||
|
return httpResponse({ "error": `Malformed JSON (${e})` }, 400)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(null, { status: 400 });
|
return httpResponse(null, 400);
|
||||||
}
|
}
|
||||||
37
src/pages/api/image/[id].ts
Normal file
37
src/pages/api/image/[id].ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type { APIContext } from 'astro';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
import 'dotenv/config'
|
||||||
|
import { httpResponse } from '../../../utils/response';
|
||||||
|
const uploadDir = process.env.UPLOAD_DIR!;
|
||||||
|
|
||||||
|
export async function GET({ params }: APIContext) {
|
||||||
|
try {
|
||||||
|
const { id } = params
|
||||||
|
return readImage(id!)
|
||||||
|
} catch (error) {
|
||||||
|
return httpResponse({ error: `Failed to retrieve image: ${error}` }, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readImage(id: string) {
|
||||||
|
|
||||||
|
const filepath = join(uploadDir, id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.access(filepath);
|
||||||
|
} catch {
|
||||||
|
return httpResponse({ error: 'Image not found' }, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = await fs.readFile(filepath);
|
||||||
|
const ext = id.split('.').pop()?.toLowerCase();
|
||||||
|
const contentType = ext === 'png' ? 'image/png' : 'image/jpeg';
|
||||||
|
|
||||||
|
return new Response(image, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': contentType,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
41
src/pages/api/image/index.ts
Normal file
41
src/pages/api/image/index.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type { APIContext } from 'astro';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { randomBytes } from 'crypto';
|
||||||
|
import { httpResponse } from '../../../utils/response';
|
||||||
|
import 'dotenv/config'
|
||||||
|
|
||||||
|
const uploadDir = process.env.UPLOAD_DIR!;
|
||||||
|
|
||||||
|
|
||||||
|
export async function POST({ request }: APIContext) {
|
||||||
|
try {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const file = formData.get('image');
|
||||||
|
|
||||||
|
return await writeImage(file)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return httpResponse({ error: `Failed to upload image, ${error}` }, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeImage(file) {
|
||||||
|
if (!file || !(file instanceof File)) {
|
||||||
|
return httpResponse({ error: 'No image provided' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.type.match(/^image\/(jpeg|jpg|png)$/)) {
|
||||||
|
return httpResponse({ error: 'Only JPG and PNG allowed' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = file.name.split('.').pop();
|
||||||
|
const filename = `${randomBytes(16).toString('hex')}.${ext}`;
|
||||||
|
const filepath = join(uploadDir, filename);
|
||||||
|
|
||||||
|
// Save file
|
||||||
|
const buffer = Buffer.from(await file.arrayBuffer());
|
||||||
|
await fs.writeFile(filepath, buffer);
|
||||||
|
|
||||||
|
return httpResponse({ url: `/api/image/${filename}`}, 201);
|
||||||
|
}
|
||||||
@@ -1,56 +1,24 @@
|
|||||||
---
|
---
|
||||||
import "../styles/global.css"
|
import "../styles/global.css"
|
||||||
|
import Layout from "../component/core/layout.astro"
|
||||||
|
import Calendar from "../component/calendar.astro"
|
||||||
|
import Editor from "../component/editor.astro"
|
||||||
|
|
||||||
|
|
||||||
|
const res = await fetch(new URL('/api/entry?id=5', Astro.url))
|
||||||
|
const en = await res.json()
|
||||||
---
|
---
|
||||||
|
|
||||||
<script>
|
|
||||||
import Quill from "quill";
|
|
||||||
|
|
||||||
const quill = new Quill('#editor', {
|
<Layout>
|
||||||
modules: {
|
<div class="flex flex-col md:flex-row md:space-x-4 space-y-4 w-full">
|
||||||
toolbar: [
|
<div class="md:w-1/2">
|
||||||
// [{ header: [1, 2, false] }],
|
<Calendar />
|
||||||
['bold', 'italic', 'underline'],
|
<div id="entry-list" />
|
||||||
['image'],
|
</div>
|
||||||
],
|
|
||||||
},
|
<div id="right" class="md:w-1/2">
|
||||||
placeholder: 'Compose an epic...',
|
<Editor entry={JSON.stringify(en)} editMode={false} />
|
||||||
theme: 'snow', // or 'bubble'
|
</div>
|
||||||
});
|
</div>
|
||||||
|
</Layout>
|
||||||
document.querySelector("#render")?.addEventListener('click', () => {
|
|
||||||
const contents = JSON.stringify(quill.getContents())
|
|
||||||
|
|
||||||
const el = document.getElementById("result")
|
|
||||||
|
|
||||||
el!.innerText = contents
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width" />
|
|
||||||
<meta name="generator" content={Astro.generator} />
|
|
||||||
<title>Astro</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Astro</h1>
|
|
||||||
<div id="editor">
|
|
||||||
<p>Hello World!</p>
|
|
||||||
<p>Some initial <strong>bold</strong> text</p>
|
|
||||||
<p><br /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="render" class="mt-2">
|
|
||||||
render
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div id="result">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -4,9 +4,15 @@
|
|||||||
@import 'quill/dist/quill.snow.css';
|
@import 'quill/dist/quill.snow.css';
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@apply p-6;
|
@apply py-3 px-4;
|
||||||
|
@apply bg-[#00171F] text-white
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html body {
|
||||||
|
@apply font-[AmericanTypewriter];
|
||||||
|
/* @apply w-full md:w-[68%] mx-auto h-full; */
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@apply py-2 px-3 bg-green-400 rounded-xl;
|
@apply py-2 px-3 bg-green-400 rounded-xl;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
|
|
||||||
const db = drizzle(process.env.DATABASE_URL!);
|
export const db = drizzle(process.env.DATABASE_URL!);
|
||||||
|
|||||||
6
src/utils/http.ts
Normal file
6
src/utils/http.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export function getParams(request: Request) {
|
||||||
|
const params = request.url.split("?")[1]
|
||||||
|
const searchParams = new URLSearchParams(params);
|
||||||
|
const paramsDict = Object.fromEntries(searchParams.entries());
|
||||||
|
return paramsDict
|
||||||
|
}
|
||||||
66
src/utils/quill.ts
Normal file
66
src/utils/quill.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import type { Delta } from "quill";
|
||||||
|
|
||||||
|
export interface Entry {
|
||||||
|
date: string,
|
||||||
|
location: { lat: number, long: number } | null,
|
||||||
|
content: Delta
|
||||||
|
}
|
||||||
|
|
||||||
|
// ty https://stackoverflow.com/questions/35940290
|
||||||
|
function dataURLtoFile(dataurl: string, filename: string) {
|
||||||
|
var arr = dataurl.split(','),
|
||||||
|
mime = arr[0].match(/:(.*?);/)[1],
|
||||||
|
bstr = atob(arr[arr.length - 1]),
|
||||||
|
n = bstr.length,
|
||||||
|
u8arr = new Uint8Array(n);
|
||||||
|
while(n--){
|
||||||
|
u8arr[n] = bstr.charCodeAt(n);
|
||||||
|
}
|
||||||
|
return new File([u8arr], filename, {type:mime});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadImage(b64: string) {
|
||||||
|
const data = new FormData()
|
||||||
|
|
||||||
|
// unable to tell from the b64 whether it's a jpg or png but defaulting to jpg seems to work fine enough
|
||||||
|
const file = dataURLtoFile(b64, 'image.jpg')
|
||||||
|
data.append('image', file)
|
||||||
|
|
||||||
|
const r = await fetch('/api/image', {
|
||||||
|
method: 'POST',
|
||||||
|
body: data
|
||||||
|
})
|
||||||
|
|
||||||
|
const url = (await r.json()).url
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadAllImages(delta: Delta) {
|
||||||
|
let newDelta = delta
|
||||||
|
|
||||||
|
for (const val of newDelta.ops) {
|
||||||
|
if (val.insert?.image != null) {
|
||||||
|
const imgUrl = await uploadImage(val.insert!.image)
|
||||||
|
val.insert!.image = imgUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDelta
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function uploadEntry(entry: Entry) {
|
||||||
|
// first upload all the images seperately
|
||||||
|
const delta = await uploadAllImages(entry.content)
|
||||||
|
|
||||||
|
const finalEntry: Entry = {
|
||||||
|
date: entry.date,
|
||||||
|
location: entry.location,
|
||||||
|
content: delta
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = await fetch('/api/entry/new', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'content-type': 'application/json'},
|
||||||
|
body: JSON.stringify(finalEntry)
|
||||||
|
})
|
||||||
|
}
|
||||||
5
src/utils/response.ts
Normal file
5
src/utils/response.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export function httpResponse(data: object | null, code: number) {
|
||||||
|
return new Response(JSON.stringify(data), {
|
||||||
|
status: code
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,5 +1,14 @@
|
|||||||
{
|
{
|
||||||
"extends": "astro/tsconfigs/strict",
|
"extends": "astro/tsconfigs/strict",
|
||||||
"include": [".astro/types.d.ts", "**/*"],
|
"include": [".astro/types.d.ts", "**/*"],
|
||||||
"exclude": ["dist"]
|
"exclude": ["dist"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@component/*": ["src/components/*.astro"],
|
||||||
|
"@layout/*": ["src/layout/*.astro"],
|
||||||
|
"@util/*": ["src/utils/*"],
|
||||||
|
"@db/*": ["src/db/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user