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:
-
-
+
+
+ }
+ >
+ Upload files
+ ) =>
+ console.log(event.target.files)
+ }
+ />
+
+
);
}
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"