mirror of
https://github.com/SrIzan10/mainwebsite.git
synced 2026-06-06 00:56:58 +00:00
feat: switch to nextjs (#7)
* feat: switch to nextjs * chore: move everyting to main dir * fix: blogpostgen * chore: dot? * chore: remove blogposts * fix: everything * chore: why * chore: move to public * chore: fetch * chore: remove slash * chore: aedfa * feat: defenitive nextjs move
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
module.exports = {
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': 'warn',
|
||||
},
|
||||
}
|
||||
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
55
.gitignore
vendored
55
.gitignore
vendored
@@ -1,29 +1,38 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# various stuff
|
||||
blogPosts.json
|
||||
public/blog/*.xml
|
||||
public/blog/feed.json
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
blogPosts.json
|
||||
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
7
.idea/discord.xml
generated
Normal file
7
.idea/discord.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/mainwebsite.iml
generated
Normal file
12
.idea/mainwebsite.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/mainwebsite.iml" filepath="$PROJECT_DIR$/.idea/mainwebsite.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"javascript.preferences.importModuleSpecifierEnding": "minimal"
|
||||
}
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
My public face on the wild internet.
|
||||
|
||||
Hosted on Vercel. Made with React, Typescript, Markdown and Material UI.
|
||||
Hosted on Vercel. Made with Next.js, Typescript, Markdown and Material UI.
|
||||
|
||||
URL: https://srizan.dev
|
||||
|
||||
14
index.html
14
index.html
@@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/pfp.webp" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sr Izan's front page for the web:tm:</title>
|
||||
<script async src="https://analytics.srizan.dev/ua.js" data-website-id="54ccb44c-b03c-4790-8262-3e1a82241a24"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
2
next.config.js
Normal file
2
next.config.js
Normal file
@@ -0,0 +1,2 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
export const nextConfig = {}
|
||||
58
package.json
58
package.json
@@ -1,52 +1,38 @@
|
||||
{
|
||||
"name": "mainwebsite",
|
||||
"name": "njs-move",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node src/blogPostGenerator.js && vite",
|
||||
"build": "node src/blogPostGenerator.js && tsc && vite build",
|
||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
"dev": "node src/blogPostGenerator.js && next dev",
|
||||
"build": "next build",
|
||||
"start": "node src/blogPostGenerator.js;next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@mui/icons-material": "^5.14.12",
|
||||
"@mui/material": "^5.14.5",
|
||||
"dayjs": "^1.11.9",
|
||||
"@mui/material": "^5.14.18",
|
||||
"dayjs": "^1.11.10",
|
||||
"feed": "^4.2.2",
|
||||
"glob": "^10.3.3",
|
||||
"glob": "^10.3.10",
|
||||
"gray-matter": "^4.0.3",
|
||||
"marked": "^8.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"marked": "^10.0.0",
|
||||
"next": "^14.0.3",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"remark-gfm": "^3.0.1"
|
||||
"remark-gfm": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/mdx": "^2.0.6",
|
||||
"@types/node": "^20.5.1",
|
||||
"@types/react": "^18.0.37",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-helmet": "^6.1.9",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-syntax-highlighter": "^15.5.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.3.4",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.3.9"
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.0.2",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
198
public/blog/atom.xml
Normal file
198
public/blog/atom.xml
Normal file
@@ -0,0 +1,198 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<id>https://srizan.dev/blog</id>
|
||||
<title>Sr Izan's Blog</title>
|
||||
<updated>2023-11-17T18:19:59.603Z</updated>
|
||||
<generator>https://github.com/jpmonette/feed</generator>
|
||||
<author>
|
||||
<name>Sr Izan</name>
|
||||
<email>izan@srizan.dev</email>
|
||||
<uri>https://srizan.dev</uri>
|
||||
</author>
|
||||
<link rel="alternate" href="https://srizan.dev/blog"/>
|
||||
<link rel="self" href="https://srizan.dev/blog/atom.xml"/>
|
||||
<subtitle>My little donowall place on the net</subtitle>
|
||||
<logo>https://srizan.dev/pfp.png</logo>
|
||||
<icon>https://srizan.dev/pfp.png</icon>
|
||||
<rights>Copyleft 2023, Sr Izan</rights>
|
||||
<entry>
|
||||
<title type="html"><![CDATA[My tales of MongoDB migration]]></title>
|
||||
<id>https://srizan.dev/blog/2</id>
|
||||
<link href="https://srizan.dev/blog/2"/>
|
||||
<updated>2023-11-11T23:00:00.000Z</updated>
|
||||
<summary type="html"><![CDATA[Here I ramble about the last service migration I did, MongoDB, and all the difficulties that came with it.]]></summary>
|
||||
<content type="html"><![CDATA[<h2>Introduction</h2>
|
||||
<p>So, the last few months I've been migrating services from my good old Raspberry Pi into my new HP server and the last service I migrated was MongoDB.</p>
|
||||
<p>I've been using MongoDB for a while now and I've been using it for a few things, like my discord bots, <a href="https://github.com/SrIzan10/webhooks-ui">webhooks-ui</a> and probably other projects I don't remember right now.</p>
|
||||
<p>So, let's get started!</p>
|
||||
<h2>Testing the plan</h2>
|
||||
<p>My database instance is on Docker with a replica set of 1 node (itself) so <a href="https://www.prisma.io/">Prisma</a> works.</p>
|
||||
<p>My idea is to add the HP server as a secondary replica and then promote it to be the primary one, but I don't know if that will work, so we need to test some stuff.</p>
|
||||
<p>I first created 2 docker containers on my <a href="https://gist.github.com/SrIzan10/50bc2ba689a4cc43bcbac2799cc733c9">main Ryzen machine</a>'s WSL Ubuntu instance.</p>
|
||||
<p>I created a <code>docker-compose.yml</code> file with the following content:</p>
|
||||
<pre><code class="language-yml">version: "3.8"
|
||||
services:
|
||||
mongo1:
|
||||
image: mongo:4.4.17-rc0-focal
|
||||
container_name: mongo1
|
||||
restart: always
|
||||
ports:
|
||||
- 27017:27017
|
||||
volumes:
|
||||
- ./mongo1:/data/db
|
||||
command: mongod --replSet mongoset
|
||||
networks:
|
||||
- mongo
|
||||
mongo2:
|
||||
image: mongo:4.4.17-rc0-focal
|
||||
container_name: mongo2
|
||||
restart: always
|
||||
ports:
|
||||
- 27018:27017
|
||||
volumes:
|
||||
- ./mongo2:/data/db
|
||||
command: mongod --replSet mongoset
|
||||
networks:
|
||||
- mongo
|
||||
networks:
|
||||
mongo:
|
||||
</code></pre>
|
||||
<p>and ran it with <code>docker compose up -d</code>.</p>
|
||||
<p>I went to connect with MongoDB Compass and it didn't work for some reason. I asked GPT and nothing. It looks like it accepted the connection but it won't connect, so I installed <code>mongosh</code> and tried to connect with that.</p>
|
||||
<pre><code class="language-bash">$ mongosh mongodb://localhost:27017
|
||||
</code></pre>
|
||||
<p>...and it worked! That didn't make any sense, but okay, we can work with it.</p>
|
||||
<p>I then connected to the <code>mongo1</code> instance and ran the following commands:</p>
|
||||
<pre><code class="language-bash">> rs.initiate()
|
||||
</code></pre>
|
||||
<p>and it worked, but only that same database connected. Before adding the second database to the replica, I went ahead and pinged it from the first container (just to check if the network configuration worked):</p>
|
||||
<pre><code class="language-bash">docker exec mongo1 sh -c "rm /bin/ping;apt update;apt install inetutils-ping -y;ping mongo2"
|
||||
</code></pre>
|
||||
<p>I removed /bin/ping because I tried to transfer the binary from WSL to the container but it still needed some libraries and I didn't want to bother, so I just installed the package.</p>
|
||||
<p>It worked, so I went ahead and added the second database to the replica set:</p>
|
||||
<pre><code class="language-bash">> rs.add("mongo2")
|
||||
</code></pre>
|
||||
<p>After waiting for it, the second database connected and everything was working fine. Let's create a collection and some documents on the primary replica (mongo1):</p>
|
||||
<pre><code class="language-bash">> use test
|
||||
> db.createCollection("test")
|
||||
> db.test.insertOne({ name: "test" })
|
||||
</code></pre>
|
||||
<p>and then, let's check if it's on the second replica (mongo2):</p>
|
||||
<pre><code class="language-bash">$ mongosh mongodb://localhost:27017
|
||||
</code></pre>
|
||||
<pre><code class="language-bash">> use test
|
||||
> db.getMongo().setReadPref("secondaryPreferred")
|
||||
> db.test.find()
|
||||
</code></pre>
|
||||
<p>and, yeah, that worked.</p>
|
||||
<p>I don't really know if ORMs will read when connecting to the second replica, but for now it's fine as the main plan is on track.<br>So, to promote I connected to the primary replica (mongo1) and ran the following command:</p>
|
||||
<pre><code class="language-bash">> rs.stepDown()
|
||||
</code></pre>
|
||||
<p>And that worked! Woo! The second replica is now the primary one. We can now start <em>drum rolls please</em>:</p>
|
||||
<h2>The migration</h2>
|
||||
<p>This is it. We're doing it.</p>
|
||||
<p>I went ahead and created a new docker-compose file on my server with the following content:</p>
|
||||
<pre><code class="language-yml">version: "3.8"
|
||||
services:
|
||||
mongo:
|
||||
image: mongo:4.4.17-rc0-focal
|
||||
container_name: mongodb
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 27017:27017
|
||||
volumes:
|
||||
- ./mongo:/data/db
|
||||
command: mongod --replSet rs0
|
||||
</code></pre>
|
||||
<p>After deploying the stack, I connected using mongosh to the primary db and ran the following command:</p>
|
||||
<pre><code class="language-bash">> rs.add("ip")
|
||||
</code></pre>
|
||||
<p>and after waiting for a while it looked like it worked. I then connected to the new database and ran the following command to check if the replica cloned fine:</p>
|
||||
<pre><code class="language-bash">> db.getMongo().setReadPref("secondaryPreferred")
|
||||
</code></pre>
|
||||
<p>and let's just let the results speak for themselves:</p>
|
||||
<pre><code class="language-bash">rs0 [direct: secondary] test> show dbs
|
||||
# author's note: some dbs are redacted for privacy reasons
|
||||
admin 80.00 KiB
|
||||
api 80.00 KiB
|
||||
ava 40.00 KiB
|
||||
bask 168.00 KiB
|
||||
config 144.00 KiB
|
||||
local 348.00 KiB
|
||||
vinci 428.00 KiB
|
||||
rs0 [direct: secondary] test> use vinci
|
||||
switched to db vinci
|
||||
rs0 [direct: secondary] vinci> show tables
|
||||
afk
|
||||
birthdays
|
||||
chatgpt
|
||||
giveaways-enters
|
||||
giveaways-message
|
||||
padyama
|
||||
suggestions
|
||||
twitter
|
||||
warns
|
||||
youtube
|
||||
rs0 [direct: secondary] vinci> db.afk.find()
|
||||
[
|
||||
{
|
||||
_id: ObjectId("sadfsad fsadfsdf"),
|
||||
id: 'redacted',
|
||||
reason: 'redacted',
|
||||
__v: 0
|
||||
},
|
||||
{
|
||||
_id: ObjectId("asdfsadfadf"),
|
||||
id: 'redacted',
|
||||
reason: 'readacted',
|
||||
__v: 0
|
||||
}
|
||||
]
|
||||
rs0 [direct: secondary] vinci>
|
||||
</code></pre>
|
||||
<p>Nice. let's now try to write something to the database from Vinci:<br><img src="https://img.srizan.dev/Discord_a2iXkWYxwn.png" alt=""><br>That just worked and we can see it on the secondary replica:</p>
|
||||
<pre><code class="language-bash">rs0 [direct: secondary] vinci> db.afk.find({ id: '703974042700611634' })
|
||||
[
|
||||
{
|
||||
_id: ObjectId("6550eccc6154a8c9030fe76a"),
|
||||
id: '703974042700611634',
|
||||
reason: 'test',
|
||||
__v: 0
|
||||
}
|
||||
]
|
||||
</code></pre>
|
||||
<p>Let's now edit all .envs and change the database url to the new secondary one. For this I checked all dbs that I have and then go from top to bottom editing the secrets.</p>
|
||||
<p>After that was done I needed to deploy all changes. I went ahead and created too many tabs on my terminal and ran the all deployment commands on each tab. At the same time.<br>I really hope that doesn't make my server run out of ram, because I'm really short on that.</p>
|
||||
<p>After executing all the commands I <code>rs.stepDown()</code>'ed the primary Raspberry Pi replica and, as expected, the HP Server took over.</p>
|
||||
<p>The last command of the day:</p>
|
||||
<pre><code class="language-bash">> rs.remove("ip")
|
||||
</code></pre>
|
||||
<p>...SIKE! I needed to check the logs of the containers to see if everything was working fine. The <code>api</code> and <code>vinci</code> to be exact.<br>This is because <code>api</code> runs Prisma and <code>vinci</code> runs the now defunct in my stack, <a href="https://mongoosejs.com/">mongoose</a>.</p>
|
||||
<p>Luckily enough, both were fine, so I was free. Yay!</p>
|
||||
<h2>Conclusion</h2>
|
||||
<p>Welp, that was a lot of work. I'm glad it's over. I got my HP server on July and it's now November and I just finished migrating.<br>Could I have done it in less time? Yes.<br>Was I lazy? Also yes.</p>
|
||||
<p>So that answers all your questions.</p>
|
||||
<p>I hope you enjoyed this my first blog post, and thankfully it was a big one.<br>This took 3 hours in total, but at the end of the day, it was worth it.</p>
|
||||
<p>I'll see you in the next one!</p>
|
||||
]]></content>
|
||||
<author>
|
||||
<name>Sr Izan</name>
|
||||
<uri>https://srizan.dev</uri>
|
||||
</author>
|
||||
</entry>
|
||||
<entry>
|
||||
<title type="html"><![CDATA[Welcome to my new blog!]]></title>
|
||||
<id>https://srizan.dev/blog/1</id>
|
||||
<link href="https://srizan.dev/blog/1"/>
|
||||
<updated>2023-08-19T22:00:00.000Z</updated>
|
||||
<summary type="html"><![CDATA[This post welcomes you to my new blog]]></summary>
|
||||
<content type="html"><![CDATA[<h1>Hey!</h1>
|
||||
<p>This is probably the last time I'm going to make a blog. I've made a few in the past, but I've never really stuck to them. I'm hoping that this time will be different.<br>This one was made entirely from scratch using React and Markdown, initially trying to use MDX, but it was a pain to set up, and it didn't end up working in the end.<br>I'm hoping to post about my projects, and maybe some other stuff too. I'm not sure yet, but I'll figure it out as I go along.<br>Anyways, thank you for reading. I hope you enjoyed my UX/UI for this one!</p>
|
||||
<p>PD: I need some help for making the blog text look good and readable, so hit me up on my Discord if you have any ideas.</p>
|
||||
]]></content>
|
||||
<author>
|
||||
<name>Sr Izan</name>
|
||||
<uri>https://srizan.dev</uri>
|
||||
</author>
|
||||
</entry>
|
||||
</feed>
|
||||
38
public/blog/feed.json
Normal file
38
public/blog/feed.json
Normal file
File diff suppressed because one or more lines are too long
191
public/blog/rss.xml
Normal file
191
public/blog/rss.xml
Normal file
@@ -0,0 +1,191 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>Sr Izan's Blog</title>
|
||||
<link>https://srizan.dev/blog</link>
|
||||
<description>My little donowall place on the net</description>
|
||||
<lastBuildDate>Fri, 17 Nov 2023 18:19:59 GMT</lastBuildDate>
|
||||
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
||||
<generator>https://github.com/jpmonette/feed</generator>
|
||||
<language>en</language>
|
||||
<image>
|
||||
<title>Sr Izan's Blog</title>
|
||||
<url>https://srizan.dev/pfp.png</url>
|
||||
<link>https://srizan.dev/blog</link>
|
||||
</image>
|
||||
<copyright>Copyleft 2023, Sr Izan</copyright>
|
||||
<atom:link href="https://srizan.dev/blog/rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<title><![CDATA[My tales of MongoDB migration]]></title>
|
||||
<link>https://srizan.dev/blog/2</link>
|
||||
<guid>https://srizan.dev/blog/2</guid>
|
||||
<pubDate>Sat, 11 Nov 2023 23:00:00 GMT</pubDate>
|
||||
<description><![CDATA[Here I ramble about the last service migration I did, MongoDB, and all the difficulties that came with it.]]></description>
|
||||
<content:encoded><![CDATA[<h2>Introduction</h2>
|
||||
<p>So, the last few months I've been migrating services from my good old Raspberry Pi into my new HP server and the last service I migrated was MongoDB.</p>
|
||||
<p>I've been using MongoDB for a while now and I've been using it for a few things, like my discord bots, <a href="https://github.com/SrIzan10/webhooks-ui">webhooks-ui</a> and probably other projects I don't remember right now.</p>
|
||||
<p>So, let's get started!</p>
|
||||
<h2>Testing the plan</h2>
|
||||
<p>My database instance is on Docker with a replica set of 1 node (itself) so <a href="https://www.prisma.io/">Prisma</a> works.</p>
|
||||
<p>My idea is to add the HP server as a secondary replica and then promote it to be the primary one, but I don't know if that will work, so we need to test some stuff.</p>
|
||||
<p>I first created 2 docker containers on my <a href="https://gist.github.com/SrIzan10/50bc2ba689a4cc43bcbac2799cc733c9">main Ryzen machine</a>'s WSL Ubuntu instance.</p>
|
||||
<p>I created a <code>docker-compose.yml</code> file with the following content:</p>
|
||||
<pre><code class="language-yml">version: "3.8"
|
||||
services:
|
||||
mongo1:
|
||||
image: mongo:4.4.17-rc0-focal
|
||||
container_name: mongo1
|
||||
restart: always
|
||||
ports:
|
||||
- 27017:27017
|
||||
volumes:
|
||||
- ./mongo1:/data/db
|
||||
command: mongod --replSet mongoset
|
||||
networks:
|
||||
- mongo
|
||||
mongo2:
|
||||
image: mongo:4.4.17-rc0-focal
|
||||
container_name: mongo2
|
||||
restart: always
|
||||
ports:
|
||||
- 27018:27017
|
||||
volumes:
|
||||
- ./mongo2:/data/db
|
||||
command: mongod --replSet mongoset
|
||||
networks:
|
||||
- mongo
|
||||
networks:
|
||||
mongo:
|
||||
</code></pre>
|
||||
<p>and ran it with <code>docker compose up -d</code>.</p>
|
||||
<p>I went to connect with MongoDB Compass and it didn't work for some reason. I asked GPT and nothing. It looks like it accepted the connection but it won't connect, so I installed <code>mongosh</code> and tried to connect with that.</p>
|
||||
<pre><code class="language-bash">$ mongosh mongodb://localhost:27017
|
||||
</code></pre>
|
||||
<p>...and it worked! That didn't make any sense, but okay, we can work with it.</p>
|
||||
<p>I then connected to the <code>mongo1</code> instance and ran the following commands:</p>
|
||||
<pre><code class="language-bash">> rs.initiate()
|
||||
</code></pre>
|
||||
<p>and it worked, but only that same database connected. Before adding the second database to the replica, I went ahead and pinged it from the first container (just to check if the network configuration worked):</p>
|
||||
<pre><code class="language-bash">docker exec mongo1 sh -c "rm /bin/ping;apt update;apt install inetutils-ping -y;ping mongo2"
|
||||
</code></pre>
|
||||
<p>I removed /bin/ping because I tried to transfer the binary from WSL to the container but it still needed some libraries and I didn't want to bother, so I just installed the package.</p>
|
||||
<p>It worked, so I went ahead and added the second database to the replica set:</p>
|
||||
<pre><code class="language-bash">> rs.add("mongo2")
|
||||
</code></pre>
|
||||
<p>After waiting for it, the second database connected and everything was working fine. Let's create a collection and some documents on the primary replica (mongo1):</p>
|
||||
<pre><code class="language-bash">> use test
|
||||
> db.createCollection("test")
|
||||
> db.test.insertOne({ name: "test" })
|
||||
</code></pre>
|
||||
<p>and then, let's check if it's on the second replica (mongo2):</p>
|
||||
<pre><code class="language-bash">$ mongosh mongodb://localhost:27017
|
||||
</code></pre>
|
||||
<pre><code class="language-bash">> use test
|
||||
> db.getMongo().setReadPref("secondaryPreferred")
|
||||
> db.test.find()
|
||||
</code></pre>
|
||||
<p>and, yeah, that worked.</p>
|
||||
<p>I don't really know if ORMs will read when connecting to the second replica, but for now it's fine as the main plan is on track.<br>So, to promote I connected to the primary replica (mongo1) and ran the following command:</p>
|
||||
<pre><code class="language-bash">> rs.stepDown()
|
||||
</code></pre>
|
||||
<p>And that worked! Woo! The second replica is now the primary one. We can now start <em>drum rolls please</em>:</p>
|
||||
<h2>The migration</h2>
|
||||
<p>This is it. We're doing it.</p>
|
||||
<p>I went ahead and created a new docker-compose file on my server with the following content:</p>
|
||||
<pre><code class="language-yml">version: "3.8"
|
||||
services:
|
||||
mongo:
|
||||
image: mongo:4.4.17-rc0-focal
|
||||
container_name: mongodb
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 27017:27017
|
||||
volumes:
|
||||
- ./mongo:/data/db
|
||||
command: mongod --replSet rs0
|
||||
</code></pre>
|
||||
<p>After deploying the stack, I connected using mongosh to the primary db and ran the following command:</p>
|
||||
<pre><code class="language-bash">> rs.add("ip")
|
||||
</code></pre>
|
||||
<p>and after waiting for a while it looked like it worked. I then connected to the new database and ran the following command to check if the replica cloned fine:</p>
|
||||
<pre><code class="language-bash">> db.getMongo().setReadPref("secondaryPreferred")
|
||||
</code></pre>
|
||||
<p>and let's just let the results speak for themselves:</p>
|
||||
<pre><code class="language-bash">rs0 [direct: secondary] test> show dbs
|
||||
# author's note: some dbs are redacted for privacy reasons
|
||||
admin 80.00 KiB
|
||||
api 80.00 KiB
|
||||
ava 40.00 KiB
|
||||
bask 168.00 KiB
|
||||
config 144.00 KiB
|
||||
local 348.00 KiB
|
||||
vinci 428.00 KiB
|
||||
rs0 [direct: secondary] test> use vinci
|
||||
switched to db vinci
|
||||
rs0 [direct: secondary] vinci> show tables
|
||||
afk
|
||||
birthdays
|
||||
chatgpt
|
||||
giveaways-enters
|
||||
giveaways-message
|
||||
padyama
|
||||
suggestions
|
||||
twitter
|
||||
warns
|
||||
youtube
|
||||
rs0 [direct: secondary] vinci> db.afk.find()
|
||||
[
|
||||
{
|
||||
_id: ObjectId("sadfsad fsadfsdf"),
|
||||
id: 'redacted',
|
||||
reason: 'redacted',
|
||||
__v: 0
|
||||
},
|
||||
{
|
||||
_id: ObjectId("asdfsadfadf"),
|
||||
id: 'redacted',
|
||||
reason: 'readacted',
|
||||
__v: 0
|
||||
}
|
||||
]
|
||||
rs0 [direct: secondary] vinci>
|
||||
</code></pre>
|
||||
<p>Nice. let's now try to write something to the database from Vinci:<br><img src="https://img.srizan.dev/Discord_a2iXkWYxwn.png" alt=""><br>That just worked and we can see it on the secondary replica:</p>
|
||||
<pre><code class="language-bash">rs0 [direct: secondary] vinci> db.afk.find({ id: '703974042700611634' })
|
||||
[
|
||||
{
|
||||
_id: ObjectId("6550eccc6154a8c9030fe76a"),
|
||||
id: '703974042700611634',
|
||||
reason: 'test',
|
||||
__v: 0
|
||||
}
|
||||
]
|
||||
</code></pre>
|
||||
<p>Let's now edit all .envs and change the database url to the new secondary one. For this I checked all dbs that I have and then go from top to bottom editing the secrets.</p>
|
||||
<p>After that was done I needed to deploy all changes. I went ahead and created too many tabs on my terminal and ran the all deployment commands on each tab. At the same time.<br>I really hope that doesn't make my server run out of ram, because I'm really short on that.</p>
|
||||
<p>After executing all the commands I <code>rs.stepDown()</code>'ed the primary Raspberry Pi replica and, as expected, the HP Server took over.</p>
|
||||
<p>The last command of the day:</p>
|
||||
<pre><code class="language-bash">> rs.remove("ip")
|
||||
</code></pre>
|
||||
<p>...SIKE! I needed to check the logs of the containers to see if everything was working fine. The <code>api</code> and <code>vinci</code> to be exact.<br>This is because <code>api</code> runs Prisma and <code>vinci</code> runs the now defunct in my stack, <a href="https://mongoosejs.com/">mongoose</a>.</p>
|
||||
<p>Luckily enough, both were fine, so I was free. Yay!</p>
|
||||
<h2>Conclusion</h2>
|
||||
<p>Welp, that was a lot of work. I'm glad it's over. I got my HP server on July and it's now November and I just finished migrating.<br>Could I have done it in less time? Yes.<br>Was I lazy? Also yes.</p>
|
||||
<p>So that answers all your questions.</p>
|
||||
<p>I hope you enjoyed this my first blog post, and thankfully it was a big one.<br>This took 3 hours in total, but at the end of the day, it was worth it.</p>
|
||||
<p>I'll see you in the next one!</p>
|
||||
]]></content:encoded>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Welcome to my new blog!]]></title>
|
||||
<link>https://srizan.dev/blog/1</link>
|
||||
<guid>https://srizan.dev/blog/1</guid>
|
||||
<pubDate>Sat, 19 Aug 2023 22:00:00 GMT</pubDate>
|
||||
<description><![CDATA[This post welcomes you to my new blog]]></description>
|
||||
<content:encoded><![CDATA[<h1>Hey!</h1>
|
||||
<p>This is probably the last time I'm going to make a blog. I've made a few in the past, but I've never really stuck to them. I'm hoping that this time will be different.<br>This one was made entirely from scratch using React and Markdown, initially trying to use MDX, but it was a pain to set up, and it didn't end up working in the end.<br>I'm hoping to post about my projects, and maybe some other stuff too. I'm not sure yet, but I'll figure it out as I go along.<br>Anyways, thank you for reading. I hope you enjoyed my UX/UI for this one!</p>
|
||||
<p>PD: I need some help for making the blog text look good and readable, so hit me up on my Discord if you have any ideas.</p>
|
||||
]]></content:encoded>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 MiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="31.88" height="32" aria-hidden="true" class="iconify iconify--logos" preserveAspectRatio="xMidYMid meet" role="img" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"/><stop offset="100%" stop-color="#BD34FE"/></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"/><stop offset="8.333%" stop-color="#FFDD35"/><stop offset="100%" stop-color="#FFA800"/></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"/><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
27
src/app/(pages)/(root)/page.tsx
Normal file
27
src/app/(pages)/(root)/page.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import '../../_css/Root.css';
|
||||
import React from 'react';
|
||||
import { SiOsu } from 'react-icons/si';
|
||||
import { FaDiscord, FaGithub, FaMastodon, FaTwitter, FaBlog } from 'react-icons/fa6';
|
||||
import Link from "next/link";
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<div className='aboutMeBox'>
|
||||
{/* style={{ borderRadius: '70px' }} */}
|
||||
<Link href='/collab'><Image src='/pfp.webp' alt='main profile picture' width='200' height='200' /></Link>
|
||||
<p>A spanish hobbyist developer and osu! player</p>
|
||||
<p>Stalk me on social media:</p>
|
||||
<div className='icons'>
|
||||
<Link href='https://github.com/SrIzan10'><FaGithub /></Link>
|
||||
<Link href='/blog'><FaBlog /></Link>
|
||||
<Link href='https://discord.com/users/703974042700611634'><FaDiscord /></Link>
|
||||
<Link href='https://social.srizan.dev'><FaMastodon /></Link>
|
||||
<Link href='https://twitter.com/itssrizan'><FaTwitter /></Link>
|
||||
<Link href='https://osu.ppy.sh/users/25350735'><SiOsu /></Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,47 +1,36 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import * as fs from 'node:fs/promises'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
||||
import { BlogNavBar } from "./BlogNavBar.tsx";
|
||||
import '../css/BlogPost.css';
|
||||
import BlogNavBar from "../../../_components/BlogNavBar";
|
||||
import '../../../_css/BlogPost.css';
|
||||
import React from "react";
|
||||
import jsonDataArray from '../../../../../public/blogPosts.json';
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export function BlogPost() {
|
||||
const id = Number(useParams().id);
|
||||
|
||||
if (isNaN(id)) {
|
||||
// if it's not a number, redirect to the blog page
|
||||
window.location.href = '/blog';
|
||||
}
|
||||
|
||||
const [jsonData, setJsonData] = useState<BlogPostJSONResponse>({
|
||||
export default async function Page({ params }: { params: { id: string } }) {
|
||||
const id = parseInt(params.id);
|
||||
if (Number.isNaN(id)) redirect('/blog')
|
||||
let jsonData = {
|
||||
id: 0,
|
||||
title: '',
|
||||
description: '',
|
||||
date: '',
|
||||
fileName: '',
|
||||
fileContent: ''
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const jsonFetch = await import('../../blogPosts.json');
|
||||
const jsonDataArray = jsonFetch.default;
|
||||
const filteredPost = jsonDataArray.filter((post) => post.id === id)[0];
|
||||
if (filteredPost) {
|
||||
setJsonData(filteredPost);
|
||||
} else {
|
||||
document.location.href = '/blog';
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [id]);
|
||||
const filteredPost = jsonDataArray.filter((post) => post.id === id)[0];
|
||||
if (filteredPost) {
|
||||
jsonData = filteredPost;
|
||||
} else {
|
||||
redirect('/blog')
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<Head>
|
||||
<title>{jsonData.title}</title>
|
||||
<meta name="description" content={jsonData.description} />
|
||||
<meta name="og:title" content={jsonData.title} />
|
||||
@@ -49,21 +38,19 @@ export function BlogPost() {
|
||||
<meta name="og:type" content="article" />
|
||||
<meta name="og:url" content={`https://srizan.dev/blog/${jsonData.id}`} />
|
||||
<meta name="og:article:author" content="Sr Izan" />
|
||||
</Helmet>
|
||||
</Head>
|
||||
<BlogNavBar title={jsonData.title} />
|
||||
<div className={'blogPostContent'}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={{
|
||||
code(props) {
|
||||
const { children, className, ...rest } = props
|
||||
code({node, className, children, ...props}) {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
return match ? (
|
||||
<SyntaxHighlighter
|
||||
{...rest}
|
||||
style={atomDark}
|
||||
customStyle={{ backgroundColor: '#171717', outline: 'solid' }}
|
||||
codeTagProps={{ className: 'codeHighlighter' }}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
// eslint-disable-next-line react/no-children-prop
|
||||
children={String(children).replace(/\n$/, '')}
|
||||
/>
|
||||
) : (
|
||||
@@ -77,6 +64,7 @@ export function BlogPost() {
|
||||
)
|
||||
},
|
||||
img(props) {
|
||||
// eslint-disable-next-line jsx-a11y/alt-text
|
||||
return <img {...props} style={{ maxWidth: '100%' }} />
|
||||
}
|
||||
}}>
|
||||
30
src/app/(pages)/blog/page.tsx
Normal file
30
src/app/(pages)/blog/page.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import '../../../app/_css/Blog.css';
|
||||
import Head from 'next/head';
|
||||
import blogPosts from '../../../../public/blogPosts.json'
|
||||
import BlogPostCard from '../../../app/_components/BlogPostCard';
|
||||
import BlogNavBar from '../../../app/_components/BlogNavBar';
|
||||
import BlogRssDial from '@/app/_components/BlogRssDial';
|
||||
|
||||
function Blog() {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Blog</title>
|
||||
<meta name="theme-color" content="#0d0d0d" />
|
||||
</Head>
|
||||
<BlogNavBar />
|
||||
<div className="blogPosts">
|
||||
{blogPosts.map((post) => {
|
||||
return (
|
||||
<BlogPostCard {...post} key={post.id} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="bottomRight">
|
||||
<BlogRssDial />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Blog;
|
||||
@@ -1,7 +1,9 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Collab() {
|
||||
return (
|
||||
<div>
|
||||
<img src="/collab.webp" useMap="#image-map" />
|
||||
<Image src="/collab.webp" useMap="#image-map" alt="Collab image" />
|
||||
|
||||
<map name="image-map">
|
||||
<area target="_blank" alt="Sr Izan" title="Sr Izan" href="https://srizan.dev" coords="0,0,280,316" shape="rect" />
|
||||
@@ -1,8 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import '../css/BlogNavBar.css'
|
||||
import { Link } from 'react-router-dom';
|
||||
// sadge
|
||||
'use client';
|
||||
|
||||
export function BlogNavBar(props: Props) {
|
||||
import { useEffect, useState } from 'react';
|
||||
import '../_css/BlogNavBar.css'
|
||||
import Link from 'next/link';
|
||||
import Pfp from '../../../public/pfp.webp';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function BlogNavBar(props: Props) {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -26,7 +31,7 @@ export function BlogNavBar(props: Props) {
|
||||
<img src="/pfp.webp" alt="main profile picture" height="50vh" />
|
||||
<p>{props.title || 'Sr Izan\'s blog'}</p>
|
||||
</div>
|
||||
<Link to={props.title ? '/blog' : '/'} className="backHomeLink">Go back {props.title ? 'to posts' : 'home'}</Link>
|
||||
<Link href={props.title ? '/blog' : '/'} className="backHomeLink">Go back {props.title ? 'to posts' : 'home'}</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
"use client"
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import dayjs from 'dayjs'
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
|
||||
import '../css/BlogPostCard.css'
|
||||
import '../_css/BlogPostCard.css'
|
||||
import { Button, CardActions } from "@mui/material";
|
||||
import {Link} from "react-router-dom";
|
||||
import Link from "next/link";
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
export default function BlogPostCard(props: Props) {
|
||||
@@ -25,7 +27,7 @@ export default function BlogPostCard(props: Props) {
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions className={'actions'}>
|
||||
<Link to={`/blog/${props.id}`}><Button size="small">Read</Button></Link>
|
||||
<Link href={`/blog/${props.id}`}><Button size="small">Read</Button></Link>
|
||||
</CardActions>
|
||||
</Box>
|
||||
</Card>
|
||||
44
src/app/_components/BlogRssDial.tsx
Normal file
44
src/app/_components/BlogRssDial.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
'use client'
|
||||
|
||||
import SpeedDial from "@mui/material/SpeedDial";
|
||||
import SpeedDialAction from "@mui/material/SpeedDialAction";
|
||||
import { FaRss, FaAtom } from "react-icons/fa6";
|
||||
import { MdDataObject } from 'react-icons/md'
|
||||
|
||||
const actions = [
|
||||
{ icon: <FaRss />, name: 'RSS' },
|
||||
{ icon: <MdDataObject />, name: 'JSON' },
|
||||
{ icon: <FaAtom />, name: 'Atom' }
|
||||
];
|
||||
|
||||
export default function BlogRssDial() {
|
||||
const handleChange = (event: string) => {
|
||||
switch (event) {
|
||||
case 'RSS':
|
||||
window.location.href = '/blog/rss.xml'
|
||||
break;
|
||||
case 'JSON':
|
||||
window.location.href = '/blog/feed.json'
|
||||
break;
|
||||
case 'Atom':
|
||||
window.location.href = '/blog/atom.xml'
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<SpeedDial
|
||||
ariaLabel="SpeedDial basic example"
|
||||
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
||||
icon={<FaRss />}
|
||||
>
|
||||
{actions.map((action) => (
|
||||
<SpeedDialAction
|
||||
key={action.name}
|
||||
icon={action.icon}
|
||||
tooltipTitle={action.name}
|
||||
onClick={() => handleChange(action.name)}
|
||||
/>
|
||||
))}
|
||||
</SpeedDial>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
import { CacheProvider } from "@emotion/react";
|
||||
import createCache from "@emotion/cache";
|
||||
import { useServerInsertedHTML } from "next/navigation";
|
||||
import { ReactNode, useState } from "react";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import theme from "./theme";
|
||||
|
||||
export function RootStyleRegistry({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [{ cache, flush }] = useState(() => {
|
||||
const cache = createCache({ key: "my" });
|
||||
cache.compat = true;
|
||||
const prevInsert = cache.insert;
|
||||
let inserted: string[] = [];
|
||||
cache.insert = (...args) => {
|
||||
const serialized = args[1];
|
||||
if (cache.inserted[serialized.name] === undefined) {
|
||||
inserted.push(serialized.name);
|
||||
}
|
||||
return prevInsert(...args);
|
||||
};
|
||||
const flush = () => {
|
||||
const prevInserted = inserted;
|
||||
inserted = [];
|
||||
return prevInserted;
|
||||
};
|
||||
return { cache, flush };
|
||||
});
|
||||
|
||||
useServerInsertedHTML(() => {
|
||||
const names = flush();
|
||||
if (names.length === 0) return null;
|
||||
let styles = "";
|
||||
for (const name of names) {
|
||||
styles += cache.inserted[name];
|
||||
}
|
||||
return (
|
||||
<style
|
||||
data-emotion={`${cache.key} ${names.join(" ")}`}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: styles,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<CacheProvider value={cache}>
|
||||
<ThemeProvider theme={theme}>{children}</ThemeProvider>
|
||||
</CacheProvider>
|
||||
);
|
||||
}
|
||||
36
src/app/_components/ThemeRegistry/theme.ts
Normal file
36
src/app/_components/ThemeRegistry/theme.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Roboto } from 'next/font/google';
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
|
||||
const roboto = Roboto({
|
||||
weight: ['300', '400', '500', '700'],
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
// Check if window is defined before accessing its properties
|
||||
const prefersDarkMode = typeof window !== 'undefined' ? window.matchMedia('(prefers-color-scheme: dark)') : null;
|
||||
|
||||
// Add event listener only if prefersDarkMode is defined
|
||||
if (prefersDarkMode) {
|
||||
prefersDarkMode.addEventListener('change', () => {
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
mode: prefersDarkMode && prefersDarkMode.matches ? 'dark' : 'light',
|
||||
background: {
|
||||
default: prefersDarkMode && prefersDarkMode.matches ? '#0d0d0d' : '#fafafa',
|
||||
paper: prefersDarkMode && prefersDarkMode.matches ? '#0d0d0d' : '#fafafa',
|
||||
},
|
||||
primary: {
|
||||
main: '#646cff',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: roboto.style.fontFamily,
|
||||
},
|
||||
});
|
||||
|
||||
export default theme;
|
||||
@@ -7,16 +7,18 @@
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.codeHighlighter {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
shadow: 0;
|
||||
code .codeHighlighter {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.blogPostContent {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.codeHighlighter {
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
.aboutMeBox {
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
@@ -17,6 +16,11 @@
|
||||
.icons * {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.icons svg {
|
||||
/* change from 1em */
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
.icons *:last-child {
|
||||
margin-right: 0;
|
||||
@@ -26,4 +30,4 @@
|
||||
.aboutMeBox {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,9 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
--font-mono: ui-monospace, 'JetBrains Mono', Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
|
||||
'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
|
||||
'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
a {
|
||||
28
src/app/layout.tsx
Normal file
28
src/app/layout.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import './globals.css'
|
||||
import { RootStyleRegistry } from './_components/ThemeRegistry/EmotionRootStyleRegistry'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Sr Izan\'s corner for the net',
|
||||
icons: { icon: '/pfp.webp' }
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<RootStyleRegistry>
|
||||
|
||||
{children}
|
||||
</RootStyleRegistry>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
3
src/blog/.idea/.gitignore
generated
vendored
Normal file
3
src/blog/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
8
src/blog/.idea/blog.iml
generated
Normal file
8
src/blog/.idea/blog.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="EMPTY_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
7
src/blog/.idea/discord.xml
generated
Normal file
7
src/blog/.idea/discord.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
8
src/blog/.idea/modules.xml
generated
Normal file
8
src/blog/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/blog.iml" filepath="$PROJECT_DIR$/.idea/blog.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
src/blog/.idea/vcs.xml
generated
Normal file
6
src/blog/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -57,7 +57,7 @@ await glob('./src/blog/**/*.md').then(async (files) => {
|
||||
}
|
||||
})
|
||||
|
||||
fs.writeFileSync('./blogPosts.json', JSON.stringify(data))
|
||||
fs.writeFileSync('./public/blogPosts.json', JSON.stringify(data))
|
||||
fs.writeFileSync('./public/blog/feed.json', feed.json1())
|
||||
fs.writeFileSync('./public/blog/rss.xml', feed.rss2())
|
||||
fs.writeFileSync('./public/blog/atom.xml', feed.atom1())
|
||||
@@ -1,84 +0,0 @@
|
||||
import React from 'react';
|
||||
import MuiAlert, { AlertProps } from "@mui/material/Alert";
|
||||
import Snackbar from "@mui/material/Snackbar";
|
||||
import Button from '@mui/material/Button';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import Modal from '@mui/material/Modal';
|
||||
import Box from '@mui/material/Box';
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
const modalStyle = {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 400,
|
||||
bgcolor: 'background.paper',
|
||||
border: '2px solid #000',
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
};
|
||||
|
||||
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(function Alert(
|
||||
props,
|
||||
ref,
|
||||
) {
|
||||
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
|
||||
});
|
||||
export default function AnalyticsNotice() {
|
||||
const [open, setOpen] = React.useState(window.localStorage.getItem('analyticsNotice') !== 'false');
|
||||
|
||||
const [modalOpen, setModalOpen] = React.useState(false);
|
||||
const handleModalOpen = () => setModalOpen(true);
|
||||
const handleClose = (_event?: React.SyntheticEvent | Event, reason?: string) => {
|
||||
if (reason === 'clickaway') {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
setModalOpen(false);
|
||||
window.localStorage.setItem('analyticsNotice', 'false');
|
||||
};
|
||||
|
||||
const action = (
|
||||
<React.Fragment>
|
||||
<Button color="secondary" size="small" onClick={handleModalOpen} sx={{ color: 'white' }}>
|
||||
WAIT WHAT?
|
||||
</Button>
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label="close"
|
||||
color="inherit"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<CloseIcon fontSize="small" sx={{ color: 'white' }}/>
|
||||
</IconButton>
|
||||
</React.Fragment>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<Snackbar open={open} onClose={handleClose} action={action}>
|
||||
<Alert severity="info" action={action}>hi this website uses <a href='https://umami.is' style={{ color: "white" }}><u>umami</u></a> for analytics</Alert>
|
||||
</Snackbar>
|
||||
<Modal
|
||||
open={modalOpen}
|
||||
onClose={() => handleClose()}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box sx={modalStyle}>
|
||||
<Typography id="modal-modal-title" variant="h6" component="h2">
|
||||
Analytics
|
||||
</Typography>
|
||||
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
|
||||
Umami is used to track page visits. These aren't sold to anyone and are just to see what you guys like.
|
||||
|
||||
Endpoint is https://analytics.srizan.dev
|
||||
</Typography>
|
||||
</Box>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
53
src/main.tsx
53
src/main.tsx
@@ -1,53 +0,0 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './pages/App.tsx'
|
||||
import './css/index.css'
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import Blog from "./pages/Blog.tsx";
|
||||
import { BlogPost } from "./components/BlogPost.tsx";
|
||||
import AnalyticsNotice from "./components/AnalyticsNotice.tsx";
|
||||
import Collab from './pages/Collab.tsx';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <App />,
|
||||
},
|
||||
{
|
||||
path: "/blog",
|
||||
element: <Blog />,
|
||||
},
|
||||
{
|
||||
path: "/blog/:id",
|
||||
element: <BlogPost />
|
||||
},
|
||||
{
|
||||
path: '/collab',
|
||||
element: <Collab />
|
||||
}
|
||||
]);
|
||||
|
||||
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const darkTheme = createTheme({
|
||||
palette: {
|
||||
mode: prefersDarkMode.matches ? 'dark' : 'light',
|
||||
background: {
|
||||
default: prefersDarkMode.matches ? '#0d0d0d' : '#fafafa',
|
||||
paper: prefersDarkMode.matches ? '#0d0d0d' : '#fafafa',
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
prefersDarkMode.addEventListener('change', () => {
|
||||
location.reload()
|
||||
});
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<AnalyticsNotice />
|
||||
<RouterProvider router={router} />
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
@@ -1,30 +0,0 @@
|
||||
import '../css/App.css'
|
||||
import React from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faDiscord, faGithub, faMastodon, faTwitter } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faBlog, faCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
const [osuBeat, setOsuBeat] = React.useState(false)
|
||||
return (
|
||||
<div>
|
||||
<div className='aboutMeBox'>
|
||||
{/* style={{ borderRadius: '70px' }} */}
|
||||
<Link to='/collab'><img src='/pfp.webp' alt='main profile picture' height='200px' /></Link>
|
||||
<p>I'm a hobbyist developer and osu! player based on Spain who loves to open-source and to work on teams.</p>
|
||||
<p>Stalk me on social media:</p>
|
||||
<div className='icons'>
|
||||
<a href='https://github.com/SrIzan10'><FontAwesomeIcon icon={faGithub} /></a>
|
||||
<Link to='/blog'><FontAwesomeIcon icon={faBlog} /></Link>
|
||||
<a href='https://discord.com/users/703974042700611634'><FontAwesomeIcon icon={faDiscord} /></a>
|
||||
<a href='https://social.srizan.dev'><FontAwesomeIcon icon={faMastodon} /></a>
|
||||
<a href='https://twitter.com/itssrizan'><FontAwesomeIcon icon={faTwitter} /></a>
|
||||
<a href='https://osu.ppy.sh/users/25350735'><FontAwesomeIcon icon={faCircle} onMouseEnter={() => setOsuBeat(true)} onMouseLeave={() => setOsuBeat(false)} beatFade={osuBeat} /></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -1,66 +0,0 @@
|
||||
import '../css/Blog.css';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import blogPosts from '../../blogPosts.json'
|
||||
import BlogPostCard from "../components/BlogPostCard.tsx";
|
||||
import { BlogNavBar } from "../components/BlogNavBar.tsx";
|
||||
import { faRss, faAtom } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import SpeedDial from '@mui/material/SpeedDial';
|
||||
import SpeedDialAction from '@mui/material/SpeedDialAction';
|
||||
import DataObject from "@mui/icons-material/DataObject";
|
||||
|
||||
const actions = [
|
||||
{ icon: <FontAwesomeIcon icon={faRss} />, name: 'RSS' },
|
||||
{ icon: <DataObject />, name: 'JSON' },
|
||||
{ icon: <FontAwesomeIcon icon={faAtom} />, name: 'Atom' }
|
||||
];
|
||||
|
||||
function Blog() {
|
||||
const handleChange = (event: string) => {
|
||||
switch (event) {
|
||||
case 'RSS':
|
||||
window.location.href = '/blog/rss.xml'
|
||||
break;
|
||||
case 'JSON':
|
||||
window.location.href = '/blog/feed.json'
|
||||
break;
|
||||
case 'Atom':
|
||||
window.location.href = '/blog/atom.xml'
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>Blog | Sr Izan's website</title>
|
||||
<meta name="theme-color" content="#0d0d0d" />
|
||||
</Helmet>
|
||||
<BlogNavBar />
|
||||
<div className="blogPosts">
|
||||
{blogPosts.map((post) => {
|
||||
return (
|
||||
<BlogPostCard {...post} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="bottomRight">
|
||||
<SpeedDial
|
||||
ariaLabel="SpeedDial basic example"
|
||||
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
||||
icon={<FontAwesomeIcon icon={faRss} />}
|
||||
>
|
||||
{actions.map((action) => (
|
||||
<SpeedDialAction
|
||||
key={action.name}
|
||||
icon={action.icon}
|
||||
tooltipTitle={action.name}
|
||||
onClick={() => handleChange(action.name)}
|
||||
/>
|
||||
))}
|
||||
</SpeedDial>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Blog;
|
||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -1,26 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"types": ["vite/client"]
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "src/**/*.d.ts"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"rewrites": [
|
||||
{"source": "/(.*)", "destination": "/"}
|
||||
]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 3000
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user