diff --git a/.gitignore b/.gitignore index a67d3a6..e7a4449 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ dist-ssr # Project-specific data.db +uploads \ No newline at end of file diff --git a/package.json b/package.json index f54c59e..f7c6c70 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,17 @@ "@fontsource/raleway": "5.2.8", "@mui/icons-material": "^9.1.1", "@mui/material": "^9.1.1", + "@mui/system": "^9.1.1", "@mui/x-date-pickers": "^9.6.0", "@tanstack/react-query": "^5.101.0", "@tanstack/react-router": "^1.170.16", "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/router-core": "^1.170.0", "better-sqlite3": "^12.11.1", "date-fns": "^4.4.0", "express": "^5.1.0", "kysely": "^0.29.2", + "multer": "^2.2.0", "react": "^19.1.0", "react-dom": "^19.1.0", "tsx": "^4.19.3", @@ -34,6 +37,7 @@ "@biomejs/biome": "2.5.0", "@tanstack/router-plugin": "^1.168.18", "@types/express": "^5.0.1", + "@types/multer": "^2.1.0", "@types/node": "^22.15.2", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", diff --git a/src/client/routes/add/index.tsx b/src/client/routes/add/index.tsx index dcb0741..4dc1d37 100644 --- a/src/client/routes/add/index.tsx +++ b/src/client/routes/add/index.tsx @@ -1,6 +1,27 @@ -import { Box, FormLabel, TextareaAutosize, TextField } from "@mui/material"; +import CloudUploadIcon from "@mui/icons-material/CloudUpload"; +import { + Box, + Button, + FormLabel, + styled, + TextareaAutosize, + TextField, +} from "@mui/material"; import { DatePicker } from "@mui/x-date-pickers"; import { createFileRoute } from "@tanstack/react-router"; +import type { ChangeEvent } from "react"; + +const VisuallyHiddenInput = styled("input")({ + clip: "rect(0 0 0 0)", + clipPath: "inset(50%)", + height: 1, + overflow: "hidden", + position: "absolute", + bottom: 0, + left: 0, + whiteSpace: "nowrap", + width: 1, +}); export const Route = createFileRoute("/add/")({ component: RouteComponent, @@ -10,6 +31,9 @@ function RouteComponent() { return ( - + Text: - - + + + + ); } diff --git a/src/server/db/types.ts b/src/server/db/types.ts index b2d5684..404c05a 100644 --- a/src/server/db/types.ts +++ b/src/server/db/types.ts @@ -9,7 +9,7 @@ export interface AnnouncementTable { title: string; text: string; image: string; - publication_date: Date; + publication_date: string; } export type Announcement = Selectable; diff --git a/src/server/main.ts b/src/server/main.ts index b6ac040..5c0a36c 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -1,13 +1,44 @@ import express from "express"; +import multer from "multer"; import ViteExpress from "vite-express"; +import z from "zod"; import { db } from "./db/database.js"; +const upload = multer({ + dest: "uploads/", +}); + const app = express(); +const AddInput = z.object({ + file: z.object({ filename: z.string() }), + body: z.object({ + publication_date: z.coerce + .date() + .transform((x) => new Date(x).toISOString()), + title: z.string().nonempty(), + text: z.string().nonempty(), + }), +}); + app.get("/hello", (_, res) => { res.send("Hello Vite + React + TypeScript!"); }); +app.post("/api/announcements/add", upload.single("image"), async (req, res) => { + const addInput = AddInput.parse(req); + await db + .insertInto("announcements") + .values({ + title: addInput.body.title, + text: addInput.body.text, + publication_date: addInput.body.publication_date, + image: addInput.file.filename, + }) + .execute(); + res.redirect("/"); +}); + app.get("/api/announcements", async (_, res) => { // await new Promise((resolve) => setTimeout(resolve, 1000)); const announcements = await db diff --git a/yarn.lock b/yarn.lock index 9a729d8..3f88e36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -926,7 +926,7 @@ "@tanstack/store" "0.9.3" use-sync-external-store "^1.6.0" -"@tanstack/router-core@1.171.13": +"@tanstack/router-core@1.171.13", "@tanstack/router-core@^1.170.0": version "1.171.13" resolved "https://registry.yarnpkg.com/@tanstack/router-core/-/router-core-1.171.13.tgz#49bee672c8401693d7bf902389b3dcb799de1429" integrity sha512-+NOwEj1kO/6IGmpHRIZHasYxYWpyBQGNIZAST9aNrk9Q3YlU9SgqVnl1pbLa9qAKfeNdXQIRve0RQb/0kyDeDA== @@ -1060,7 +1060,7 @@ "@types/range-parser" "*" "@types/send" "*" -"@types/express@^5.0.1": +"@types/express@*", "@types/express@^5.0.1": version "5.0.6" resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== @@ -1074,6 +1074,13 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== +"@types/multer@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/multer/-/multer-2.1.0.tgz#c6ae866b9a3d9310b477d021a78b33feda092358" + integrity sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA== + dependencies: + "@types/express" "*" + "@types/node@*": version "26.0.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-26.0.0.tgz#d4aece9e9412e9f2008d59bc2d74f5279316b665" @@ -1173,6 +1180,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== + babel-dead-code-elimination@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz#9471fc492fdfb7a0f7348aeacdaff3f1a9ecc6d3" @@ -1276,6 +1288,11 @@ browserslist@^4.24.0: node-releases "^2.0.36" update-browserslist-db "^1.2.3" +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -1284,6 +1301,13 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +busboy@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + bytes@^3.1.2, bytes@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -1347,6 +1371,16 @@ clsx@^2.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== +concat-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + content-disposition@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.1.0.tgz#f3db789c752d45564cc7e9e1e0b31790d4a38e17" @@ -1950,6 +1984,11 @@ math-intrinsics@^1.1.0: resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + media-typer@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" @@ -1960,6 +1999,11 @@ merge-descriptors@^2.0.0: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + mime-db@^1.54.0: version "1.54.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" @@ -1972,6 +2016,13 @@ mime-types@^3.0.0, mime-types@^3.0.2: dependencies: mime-db "^1.54.0" +mime-types@~2.1.24: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -2009,6 +2060,16 @@ ms@2.1.3, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +multer@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/multer/-/multer-2.2.0.tgz#f005268ca816324ba7335e3b48166896a3fa5d79" + integrity sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ== + dependencies: + append-field "^1.0.0" + busboy "^1.6.0" + concat-stream "^2.0.0" + type-is "^1.6.18" + nanoid@^3.3.12: version "3.3.13" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.13.tgz#d9666aa3eb9cebba69b0f8f9b84e509d46933deb" @@ -2269,7 +2330,7 @@ react@^19.1.0: resolved "https://registry.yarnpkg.com/react/-/react-19.2.7.tgz#1f47a1bfc06f8ec885752c6f4af14369a9f8260b" integrity sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ== -readable-stream@^3.1.1, readable-stream@^3.4.0: +readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -2527,6 +2588,11 @@ statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -2618,6 +2684,14 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +type-is@^1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + type-is@^2.0.1, type-is@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.1.0.tgz#71d1a7053293582e16ac9f3ebaf1ab9aa49e5570" @@ -2627,6 +2701,11 @@ type-is@^2.0.1, type-is@^2.1.0: media-typer "^1.1.0" mime-types "^3.0.0" +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + typescript@^5.8.3: version "5.9.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"