diff --git a/.dockerignore b/.dockerignore index a0dfb30..d8e7c4d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,8 +4,13 @@ build *.log *.sqlite *.db -.env.local +.env* .cache .git +.gitignore +.drone.yml +docker-compose.yml +README.md +bun.lock coverage -*.tsbuildinfo \ No newline at end of file +*.tsbuildinfo diff --git a/.drone.yml b/.drone.yml index a2099c9..bad1fd4 100644 --- a/.drone.yml +++ b/.drone.yml @@ -39,8 +39,8 @@ volumes: path: /var/run/docker.sock - name: appdir host: - path: /tmp/drone-me-deploy + path: /opt/drone/me trigger: branch: - - main \ No newline at end of file + - main diff --git a/app/page.js b/app/page.js index ee6ef54..9572648 100644 --- a/app/page.js +++ b/app/page.js @@ -18,7 +18,7 @@ export default function Main() { const handleToggleLanguage = () => { const nextLocale = locale === "en" ? "ru" : "en"; - document.cookie = `locale=${nextLocale}; path=/; max-age=31536000`; + document.cookie = `locale=${nextLocale}; path=/; max-age=31536000; SameSite=Lax`; window.location.reload(); }; @@ -110,7 +110,7 @@ export default function Main() { {locale == "it" && (
-

Cari italiani, mi scuso, ma il sito non è completamente tradotto in italiano. Sarebbe meglio usare la versione in inglese.

+

Cari italiani, mi scuso, ma il sito non è completamente tradotto in italiano. Un giorno lo tradurrerò! Per ora, sarebbe meglio usare la versione in inglese. ^^

)}
diff --git a/components/projectDialog.jsx b/components/projectDialog.jsx index a771589..5f4e697 100644 --- a/components/projectDialog.jsx +++ b/components/projectDialog.jsx @@ -67,7 +67,7 @@ export default function ProjectDialog({ children, project }) { ) : null; })} -

{project.text}

+

{project.text}

diff --git a/dictionary/en.json b/dictionary/en.json index 0b98a5b..d5a2d21 100644 --- a/dictionary/en.json +++ b/dictionary/en.json @@ -35,7 +35,7 @@ }, "projects": { "title": "My projects", - "intro": "Okay, I'll be honest — almost all of them are basically for a virtual state project called the Duspean Republic, named after my cat. Yes, I built a couple of governmental sites for an imaginary country and for the organisation where it is. It's kinda embarrassing, but also... I learned a ton doing it? So here they are:", + "intro": "A lot of my projects are related to a virtual state called the Duspean Republic, named after my cat. Yes, I built a couple of governmental sites for an imaginary country and for the organisation where it is. It's kinda embarrassing, but also... I learned a ton doing it? Well, due to the lack of interest in the project, most of them are no longer available online. But there're also some other sites. So here they are:", "items": [ { "logoAlt": "Dusiburg ID logo", @@ -72,6 +72,12 @@ "title": "Notes", "description": "A simple note-taking desktop application built with Wails (Go) and React because I wanted something simple and light for storing some things I have to do.", "text": "A simple note-taking desktop application built with Wails (Go) and React. I wanted something lightweight for keeping my tasks. I didn't really like Notion because I had to turn on my VPN every time I visited it, and if I forgot, I'd get automatically logged out. Obsidian felt a bit too complicated for my needs, so I decided to create my own app." + }, + { + "logoAlt": "LXDB logo", + "title": "LXDB", + "description": "LXDB is a service built for managing official documents of two virtual countries: the Duspean Republic and the Country of Reality.", + "text": "LXDB (LX comes from Latin word lex which means law) is built with Next.js (frontend) and Go (backend) for managing official documents of two virtual countries: the Duspean Republic and the Country of Reality. It uses Postgres for database storage and Minio for file storage. I decided to create this service because I wanted to have a single place to store all the official documents of the countries in order to unify the process of working with them. Actually, You can find the source code of the project on Github." } ] }, @@ -93,12 +99,12 @@ { "name": "Italiano", "level": "A2-B1", - "text": "My passion for Italian was born right after my first trip to Italy in 2019. I've been learning it since 2022, completely self-taught. I think I have a pretty good level for someone who learned it by himself, but there's always so much more to learn." + "text": "My passion for Italian was born right after my first trip to Italy in 2019. I've been learning it since 2022, completely self-taught. I think I have a pretty good level for someone who learned it by himself, at least Italian sellers were able to understand me and I got 81 points on the regional step of olympiad, but there's always so much more to learn." }, { "name": "Suomi", "level": "A1", - "text": "I learned Finnish for 6 months, also by myself. It was interesting - the structure is really unique. But I found it too difficult to learn alone, so I've abandoned this idea for a while..." + "text": "I learned Finnish for 6 months, also by myself. It was interesting - the structure is really unique. But I found it too difficult to learn alone, so I've abandoned this idea for a while... I only remember some words and phrases in Finnish now." } ] }, diff --git a/dictionary/it.json b/dictionary/it.json index c541c68..0113e27 100644 --- a/dictionary/it.json +++ b/dictionary/it.json @@ -35,13 +35,13 @@ }, "projects": { "title": "My projects", - "intro": "Okay, I'll be honest — almost all of them are basically for a virtual state project called the Duspean Republic, named after my cat. Yes, I built a couple of governmental sites for an imaginary country and for the organisation where it is. It's kinda embarrassing, but also... I learned a ton doing it? So here they are:", + "intro": "A lot of my projects are related to a virtual state called the Duspean Republic, named after my cat. Yes, I built a couple of governmental sites for an imaginary country and for the organisation where it is. It's kinda embarrassing, but also... I learned a ton doing it? Well, due to the lack of interest in the project, most of them are no longer available online. But there're also some other sites. So here they are:", "items": [ { "logoAlt": "Dusiburg ID logo", "title": "Dusiburg ID", "description": "The main e-government platform of the Duspean Republic with everything a virtual state needs.", - "text": "For a long time, Dusiburg relied on what VK could offer — groups, chats, and discussions where all information was published. However, over time it became clear that these things was too inflexible, so I decided to create own website for the country. The site has various forms, a bank module, and automated system for registering bills in the Parliament of the Duspean Republic." + "text": "For a long time, Dusiburg relied on what VK could offer — groups, chats, and discussions where all information was published. However, over time it became clear that these things was too inflexible, so I decided to create own website for the country. The site has various forms, a bank module, and automated system for registering bills in the Parliament of the Duspean Republic." }, { "logoAlt": "Map logo", @@ -72,6 +72,12 @@ "title": "Notes", "description": "A simple note-taking desktop application built with Wails (Go) and React because I wanted something simple and light for storing some things I have to do.", "text": "A simple note-taking desktop application built with Wails (Go) and React. I wanted something lightweight for keeping my tasks. I didn't really like Notion because I had to turn on my VPN every time I visited it, and if I forgot, I'd get automatically logged out. Obsidian felt a bit too complicated for my needs, so I decided to create my own app." + }, + { + "logoAlt": "LXDB logo", + "title": "LXDB", + "description": "LXDB is a service built for managing official documents of two virtual countries: the Duspean Republic and the Country of Reality.", + "text": "LXDB (LX comes from Latin word lex which means law) is built with Next.js (frontend) and Go (backend) for managing official documents of two virtual countries: the Duspean Republic and the Country of Reality. It uses Postgres for database storage and Minio for file storage. I decided to create this service because I wanted to have a single place to store all the official documents of the countries in order to unify the process of working with them. Actually, You can find the source code of the project on Github." } ] }, @@ -93,12 +99,12 @@ { "name": "Italiano", "level": "A2-B1", - "text": "My passion for Italian was born right after my first trip to Italy in 2019. I've been learning it since 2022, completely self-taught. I think I have a pretty good level for someone who learned it by himself, but there's always so much more to learn." + "text": "My passion for Italian was born right after my first trip to Italy in 2019. I've been learning it since 2022, completely self-taught. I think I have a pretty good level for someone who learned it by himself, at least Italian sellers were able to understand me and I got 81 points on the regional step of olympiad, but there's always so much more to learn." }, { "name": "Suomi", "level": "A1", - "text": "I learned Finnish for 6 months, also by myself. It was interesting - the structure is really unique. But I found it too difficult to learn alone, so I've abandoned this idea for a while..." + "text": "I learned Finnish for 6 months, also by myself. It was interesting - the structure is really unique. But I found it too difficult to learn alone, so I've abandoned this idea for a while... I only remember some words and phrases in Finnish now." } ] }, diff --git a/dictionary/ru.json b/dictionary/ru.json index 258258c..de64a64 100644 --- a/dictionary/ru.json +++ b/dictionary/ru.json @@ -35,7 +35,7 @@ }, "projects": { "title": "Мои проекты", - "intro": "По факту — сейчас все мои реализованные проекты связаны с виртуальным государством под названием Дусибурская Республика, названное в честь моего кота. Да, мною создано несколько правительственных сайтов для моей воображаемой страны и сообщества, в которой она состоит. Это может показаться смешным, но... Я многому научился, благодаря этому проекту... Так вот:", + "intro": "Многие из моих реализованных проектов связаны с виртуальным государством под названием Дусибурская Республика, названное в честь моего кота. Да, мною создано несколько правительственных сайтов для моей воображаемой страны и сообщества, в которой она состоит. Это может показаться смешным, но... Я многому научился, благодаря этому проекту... Однако сайты, связанные с Дусибургом отключены, так как потеряли популярность. Но, есть и другие сайты. Так вот:", "items": [ { "logoAlt": "Логотип Dusiburg ID", @@ -64,14 +64,20 @@ { "logoAlt": "Логотип Fastlink", "title": "Fastlink", - "description": "Это — над чем я работаю прямо сейчас (и, удивительно, не связано с Дусибургом). Это попытка создать подобие мессенджера. Делаю я это с целью попрактиковать мои знания в Go. Конечно, пока Fastlink ещё не готов...", + "description": "Это — над чем я работаю сейчас. Это попытка создать подобие мессенджера. Делаю я это с целью попрактиковать мои знания в Go. Конечно, пока Fastlink ещё не готов...", "text": "Сейчас я работаю над Fastlink — попыткой сделать небольшой мессенджер с бекэндом на Go. Проект ещё в разработке. Надеюсь однажды довести его до состояния, когда его можно будет выложить на GitHub. По большому счёту, это проект, цель которого — помочь мне лучше понять Go." }, { "logoAlt": "Логотип Notes", "title": "Notes", - "description": "Простое приложение для компьютера для ведения заметок, созданное с помощью Wails (Go) и React. Я хотел что-то лёгкое и простое для ведения списка дел или быстрых заметок", + "description": "Простое приложение для компьютера для ведения заметок, созданное с помощью Wails (Go) и React. Я хотел что-то лёгкое и простое для ведения списка дел или быстрых заметок.", "text": "Простое приложение для компьютера для ведения заметок, созданное с помощью Wails (Go) и React. Я хотел что-то лёгкое и простое для ведения списка дел или быстрых заметок. Мне не очень нравился Notion, потому что каждый раз нужно было включать VPN, а если я забывал — меня автоматически выкидывало из аккаунта. Obsidian показался слишком сложным для простых заметок, поэтому я решил сделать что-то своё." + }, + { + "logoAlt": "LXDB логотип", + "title": "LXDB", + "description": "LXDB — это сервис, созданный для просмотра официальных документов двух виртуальных стран: Дусибурской Республики и Страны Реальности.", + "text": "Сайт LXDB (LX — от латинского слова lex, что значит закон) создан с помощью Next.js (для frontend) и Go (для backend), чтобы управлять официальными документами двух виртуальных стран: Дусибурской Республики и Страны Реальности. Он использует Postgres и Redis для хранения данных и Minio для хранения файлов. Я решил создать этот сервис, потому что хотел иметь единое место для официальных документов, чтобы можно было унифицировать процесс работы с ними. Вы можете найти исходный код проекта на GitHub." } ] }, @@ -93,12 +99,12 @@ { "name": "Italiano", "level": "A2-B1", - "text": "Моя страсть к итальянскому появилась вскоре после моей первой поездки в Италию в 2019 году. Учить его сам я начал с 2022 года. Думаю, я имею достаточно хороший уровень для человека, который учил его полностью сам. Но конечно, мой итальянский ещё далёк от идеала." + "text": "Моя страсть к итальянскому появилась вскоре после моей первой поездки в Италию в 2019 году. Учить его сам я начал с 2022 года. Думаю, я имею достаточно хороший уровень для человека, который учил его полностью сам. По крайней мере продавцы в Италии меня понимали, а языка хватило, чтобы получить 81 балл на региональном этапе олимпиады." }, { "name": "Suomi", "level": "A1", - "text": "Я учил финский язык около 6 месяцев, также сам. Язык интересный. У него действительно уникальная структура. Однако, он показался мне весьма тяжёлым для самостоятельного обучения, поэтому я забросил идею его изучения..." + "text": "Я учил финский язык около 6 месяцев, также сам. Язык интересный. У него действительно уникальная структура. Однако, он показался мне весьма тяжёлым для самостоятельного обучения, поэтому я забросил идею его изучения... Сейчас помню только отдельные слова и фразы на нём." } ] }, diff --git a/docker-compose.yml b/docker-compose.yml index f0b50ee..1152f9f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,17 @@ services: - LETSENCRYPT_HOST=${LETSENCRYPT_HOST} - LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL} restart: unless-stopped + read_only: true + security_opt: + - no-new-privileges:true + deploy: + resources: + limits: + memory: 512M + cpus: '0.50' + tmpfs: + - /tmp + - /app/.next/cache healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:80/"] interval: 30s @@ -21,4 +32,4 @@ services: networks: proxy: - external: true \ No newline at end of file + external: true diff --git a/dockerfile b/dockerfile index bd3e0cb..5242e83 100644 --- a/dockerfile +++ b/dockerfile @@ -10,9 +10,12 @@ WORKDIR /app COPY package*.json ./ RUN npm install --omit=dev COPY --from=builder /app/.next ./.next +COPY --from=builder --chown=node:node /app/.next/cache ./.next/cache +COPY --from=builder /app/next.config.mjs ./ COPY --from=builder /app/dictionary ./dictionary COPY --from=builder /app/public ./public +USER app EXPOSE 80 CMD ["npm", "start", "--", "-p", "80", "-H", "0.0.0.0"] HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD wget -q --spider http://localhost:80/ || exit 1 \ No newline at end of file + CMD wget -q --spider http://localhost:80/ || exit 1 diff --git a/lib/projects.json b/lib/projects.json index f70d23d..672f29f 100644 --- a/lib/projects.json +++ b/lib/projects.json @@ -101,5 +101,20 @@ "text": "projects.items.5.text", "description": "projects.items.5.description", "skills": ["Go", "React"] + }, + { + "id": 7, + "screenshots": [ + "/images/screenshots/lxdb/1.png" + ], + "screenshotBg": "bg-white", + "logo": "/images/logos/lxdb.png", + "logoAlt": "projects.items.6.logoAlt", + "title": "projects.items.6.title", + "url": "https://github.com/dusiburg/lxdb", + "hostname": "@dusiburg/lxdb", + "text": "projects.items.6.text", + "description": "projects.items.6.description", + "skills": ["Next.js", "Tailwind", "Shadcn", "Go", "Postgres", "Redis"] } ] \ No newline at end of file diff --git a/lib/skillsList.js b/lib/skillsList.js index 8d0cf8f..322c9ca 100644 --- a/lib/skillsList.js +++ b/lib/skillsList.js @@ -1,4 +1,4 @@ -import { SiNextdotjs, SiTailwindcss, SiPostgresql, SiSupabase, SiReact, SiDocker, SiLinux, SiGo, SiPython, SiHtml5, SiJavascript, SiPhp, SiPrisma, SiMapbox, SiShadcnui } from "react-icons/si"; +import { SiNextdotjs, SiTailwindcss, SiPostgresql, SiSupabase, SiReact, SiDocker, SiLinux, SiGo, SiRedis, SiPython, SiHtml5, SiJavascript, SiPhp, SiPrisma, SiMapbox, SiShadcnui } from "react-icons/si"; export const skillsList = [ { name: "Next.js", icon: }, @@ -13,6 +13,7 @@ export const skillsList = [ { name: "HTML", icon: }, { name: "JS", icon: }, { name: "Go", icon: }, + { name: "Redis", icon: }, { name: "PHP", icon: }, { name: "Python", icon: }, { name: "Mapbox", icon: }, diff --git a/next.config.mjs b/next.config.mjs index 58dbb8d..6d92947 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,19 @@ import createNextIntlPlugin from "next-intl/plugin"; -const nextConfig = {}; +const nextConfig = { + headers: async () => [{ + source: "/(.*)", + headers: [ + { key: "X-Frame-Options", value: "DENY" }, + { key: "X-Content-Type-Options", value: "nosniff" }, + { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, + { key: "X-XSS-Protection", value: "1; mode=block" }, + { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" }, + { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" }, + { key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'" }, + ], + }], +}; const withNextIntl = createNextIntlPlugin(); export default withNextIntl(nextConfig); \ No newline at end of file diff --git a/public/images/logos/lxdb.png b/public/images/logos/lxdb.png new file mode 100644 index 0000000..7952b17 Binary files /dev/null and b/public/images/logos/lxdb.png differ diff --git a/public/images/screenshots/lxdb/1.png b/public/images/screenshots/lxdb/1.png new file mode 100644 index 0000000..6cc47cd Binary files /dev/null and b/public/images/screenshots/lxdb/1.png differ