feat: move to astro (#10)
* initial commit * add shadcn ui * only main page is left! * ready to pr? * change font
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"react/no-unescaped-entities": "off"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
42
.gitignore
vendored
@@ -1,38 +1,24 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
node_modules/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
blogPosts.json
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
|
||||
11
.gitpod.yml
@@ -1,11 +0,0 @@
|
||||
# This configuration file was automatically generated by Gitpod.
|
||||
# Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml)
|
||||
# and commit this file to your remote git repository to share the goodness with others.
|
||||
|
||||
# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart
|
||||
|
||||
tasks:
|
||||
- init: yarn install && yarn run build
|
||||
command: yarn run dev
|
||||
|
||||
|
||||
5
.idea/.gitignore
generated
vendored
@@ -1,5 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
7
.idea/discord.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?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
@@ -1,12 +0,0 @@
|
||||
<?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
@@ -1,8 +0,0 @@
|
||||
<?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
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
9
.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"useTabs": false,
|
||||
"printWidth": 800,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"semi": false,
|
||||
"plugins": ["prettier-plugin-astro"]
|
||||
}
|
||||
4
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
11
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
9
.vscode/settings.json
vendored
@@ -1,3 +1,10 @@
|
||||
{
|
||||
"javascript.preferences.importModuleSpecifierEnding": "minimal"
|
||||
"css.customData": [".vscode/tailwind.json"],
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.detectIndentation": false,
|
||||
"prettier.documentSelectors": ["**/*.astro"],
|
||||
"[astro]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
55
.vscode/tailwind.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"version": 1.1,
|
||||
"atDirectives": [
|
||||
{
|
||||
"name": "@tailwind",
|
||||
"description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@apply",
|
||||
"description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#apply"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@responsive",
|
||||
"description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@screen",
|
||||
"description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#screen"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@variants",
|
||||
"description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#variants"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
nodeLinker: node-modules
|
||||
69
README.md
@@ -1,7 +1,68 @@
|
||||
# Welcome to Sr Izan's website
|
||||
# Astro Starter Kit: Blog
|
||||
|
||||
My public face on the wild internet.
|
||||
```sh
|
||||
npm create astro@latest -- --template blog
|
||||
```
|
||||
|
||||
Hosted on Vercel. Made with Next.js, Typescript, Markdown and Material UI.
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/blog)
|
||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/blog)
|
||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/blog/devcontainer.json)
|
||||
|
||||
URL: https://srizan.dev
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||

|
||||
|
||||
Features:
|
||||
|
||||
- ✅ Minimal styling (make it your own!)
|
||||
- ✅ 100/100 Lighthouse performance
|
||||
- ✅ SEO-friendly with canonical URLs and OpenGraph data
|
||||
- ✅ Sitemap support
|
||||
- ✅ RSS Feed support
|
||||
- ✅ Markdown & MDX support
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
├── public/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ ├── content/
|
||||
│ ├── layouts/
|
||||
│ └── pages/
|
||||
├── astro.config.mjs
|
||||
├── README.md
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
The `src/content/` directory contains "collections" of related Markdown and MDX documents. Use `getCollection()` to retrieve posts from `src/content/blog/`, and type-check your frontmatter using an optional schema. See [Astro's Content Collections docs](https://docs.astro.build/en/guides/content-collections/) to learn more.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
|
||||
## Credit
|
||||
|
||||
This theme is based off of the lovely [Bear Blog](https://github.com/HermanMartinus/bearblog/).
|
||||
|
||||
12
astro.config.mjs
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import react from "@astrojs/react";
|
||||
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'https://example.com',
|
||||
integrations: [mdx(), sitemap(), react(), tailwind()]
|
||||
});
|
||||
17
components.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.mjs",
|
||||
"css": "./src/styles/global.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"$schema": "https://frontmatter.codes/frontmatter.schema.json",
|
||||
"frontMatter.taxonomy.contentTypes": [
|
||||
{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"previewPath": null,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "draft"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"frontMatter.framework.id": "next",
|
||||
"frontMatter.content.publicFolder": "public",
|
||||
"frontMatter.preview.host": "http://localhost:3000",
|
||||
"frontMatter.content.pageFolders": [
|
||||
{
|
||||
"title": "blog",
|
||||
"path": "[[workspace]]/src/blog"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
export const nextConfig = {
|
||||
reactStrictMode: false,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
hostname: 'img.srizan.dev'
|
||||
},
|
||||
{
|
||||
hostname: 'res.cloudinary.com'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
66
package.json
@@ -1,40 +1,42 @@
|
||||
{
|
||||
"name": "njs-move",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"name": "mainwebsite",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "node src/blogPostGenerator.js && next dev",
|
||||
"build": "node src/blogPostGenerator.js && next build",
|
||||
"start": "node src/blogPostGenerator.js;next start",
|
||||
"lint": "next lint"
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro check && astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/lab": "^5.0.0-alpha.153",
|
||||
"@mui/material": "^5.14.18",
|
||||
"dayjs": "^1.11.10",
|
||||
"feed": "^4.2.2",
|
||||
"glob": "^10.3.10",
|
||||
"gray-matter": "^4.0.3",
|
||||
"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": "^4.0.0"
|
||||
"@astrojs/check": "^0.7.0",
|
||||
"@astrojs/mdx": "^3.0.1",
|
||||
"@astrojs/react": "^3.3.4",
|
||||
"@astrojs/rss": "^4.0.6",
|
||||
"@astrojs/sitemap": "^3.1.5",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@tryghost/content-api": "^1.11.21",
|
||||
"@types/react": "^18.3.2",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"astro": "^4.8.7",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.379.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.2.1",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-syntax-highlighter": "^15.5.10",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.0.2",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"packageManager": "yarn@4.1.1"
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@types/tryghost__content-api": "^1.3.16",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-astro": "^0.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/blog-placeholder-1.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/blog-placeholder-2.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/blog-placeholder-3.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/blog-placeholder-4.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/blog-placeholder-5.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
public/blog-placeholder-about.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
@@ -1,361 +0,0 @@
|
||||
<?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>2024-05-08T19:45:59.407Z</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-12T00: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[Install osu! on Endeavour OS]]></title>
|
||||
<id>https://srizan.dev/blog/4</id>
|
||||
<link href="https://srizan.dev/blog/4"/>
|
||||
<updated>2023-12-17T00:00:00.000Z</updated>
|
||||
<summary type="html"><![CDATA[A guide on how to install osu! on Endeavour OS]]></summary>
|
||||
<content type="html"><![CDATA[<p>Alright, so you want to install osu! on Endeavour OS. I just reinstalled my system. Two birds with one stone!<br>Based on <a href="https://wiki.archlinux.org/title/User:Katoumegumi">https://wiki.archlinux.org/title/User:Katoumegumi</a></p>
|
||||
<h1>Backstory</h1>
|
||||
<p>My Windows installation has been unstable since year 1 of my computer, and it even went to the point that when I open osu! sometimes it would just not work, black out my screen and play the last sound it was playing (see audio when a BSOD happens)<br>Yesterday I went to install <a href="https://osu.ppy.sh/home/changelog/stable40/20231217.1">the latest update</a>, click on restart, yadiyadiyada and unfortunately my computer CRASHED. I didn't feel nervous until I opened up the game to play a bit and find out that my skin was the default one.</p>
|
||||
<p>Okay, strange, lemme open up the game...</p>
|
||||
<p>It starts importing all beatmaps.</p>
|
||||
<p>Okay then, a bit understandable because the update is all about difficulty calculator.</p>
|
||||
<p>When it finishes, I go to my most recent plays.<br>Nothing.<br>Never played.<br>What?</p>
|
||||
<p>I go into my collections.<br>They're still there.<br>Okay, but are the scores there?<br>NO!</p>
|
||||
<p>Thus, after recovering my replays using a batch scripts that opens all <code>.osr</code> files in my <code>Data\r</code> folder, I just moved to linux, and this is a writeup on how I've been installing the game since my first time in April.</p>
|
||||
<p>Enjoy!</p>
|
||||
<h1>Installation</h1>
|
||||
<h2>Installing Wine</h2>
|
||||
<p>We first need to install wine and winetricks, to do so run:</p>
|
||||
<pre><code class="language-sh">sudo pacman -S wine
|
||||
wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
|
||||
chmod +x winetricks
|
||||
sudo mv ./winetricks /usr/local/bin/winetricks
|
||||
</code></pre>
|
||||
<h2>Preparing wineprefix</h2>
|
||||
<p>We now need to install dependencies like fonts or dotnet to make osu! run correctly</p>
|
||||
<pre><code class="language-sh">WINEARCH=win32 WINEPREFIX=~/.wineosu winetricks dotnet40 dotnet45 cjkfonts gdiplus
|
||||
</code></pre>
|
||||
<p><strong>(update 12/19/2023)</strong>: added <code>dotnet45</code> to make it work with <a href="https://github.com/l3lackShark/gosumemory/issues/140#issuecomment-1179663744">gosumemory</a></p>
|
||||
<h2>Installing osu!</h2>
|
||||
<p>Skipping this part, but basically download the installer and run it.<br>I already have my own 3-year-old installation with all my everything on it, so I'm bind-mounting it.</p>
|
||||
<p>Create a directory:</p>
|
||||
<pre><code class="language-sh">mkdir ~/.wineosu/osu
|
||||
</code></pre>
|
||||
<p>And then I added the following fstab line:</p>
|
||||
<pre><code class="language-sh">/mnt/HDD/osu! /home/srizan/.wineosu/osu none defaults,bind 0 0
|
||||
</code></pre>
|
||||
<h2>Trying to open it up</h2>
|
||||
<p>Running <code>WINEARCH=win32 WINEPREFIX=~/.wineosu wine ~/.wineosu/osu/osu\!.exe</code> twice leads us in the osu! installer with no network connection found.</p>
|
||||
<p>This is a bit easy to fix, as Wine itself yields this error if you scroll up a bit:</p>
|
||||
<pre><code class="language-sh">01b0:err:winediag:process_attach Failed to load libgnutls, secure connections will not be available.
|
||||
</code></pre>
|
||||
<p>Looking the package up in the <a href="https://archlinux.org/packages/?q=libgnutls">Arch Linux repos</a>, a package called <code>lib32-gnutls</code> shows up which looks to be exactly what we want.</p>
|
||||
<p>After installing it, the SSL connection worked and the game is going to sta- too bad!
|
||||
<img src="/blog/img/osu-eOS/graphicsContext.png" alt=""><br>You thought that was gonna be IT!</p>
|
||||
<p>It wants a GL context, which we don't have, so installing <a href="https://archlinux.org/packages/multilib-testing/x86_64/lib32-mesa/"><code>lib32-mesa</code></a> fixes it. Easy!</p>
|
||||
<p>Now, the window is... dark? WHEN ARE WE DONE MAN?<br>Welp, when going to almost the top of the file, we can see this:</p>
|
||||
<pre><code class="language-sh">0024:err:winediag:create_gl_drawable XComposite is not available, using GLXPixmap hack.
|
||||
</code></pre>
|
||||
<p>"GLXPixmap hack"?<br>The game is, according to peppy, held with duct tape, so no hacks are really going to work.</p>
|
||||
<p>Here we go, <a href="https://archlinux.org/packages/multilib/x86_64/lib32-libxcomposite/">yet ANOTHER lib32 package</a> should fix it.</p>
|
||||
<p>It opens up!<br><img src="/blog/img/osu-eOS/noaudio.mp4" alt=""></p>
|
||||
<p>Yeah. Nice. No audio.<br>Browsing through the Arch forums I found <img src="https://bbs.archlinux.org/viewtopic.php?id=135032" alt="this post">, and I installed the three <code>lib32-alsa-plugins lib32-libpulse lib32-openal</code> packages.</p>
|
||||
<p>And it started up! (too lazy to screenshot)</p>
|
||||
<h2>Setting up the start script</h2>
|
||||
<p>edit a file in <code>~/.wineosu/osu/start.sh</code> with the following contents:</p>
|
||||
<pre><code class="language-sh">#!/usr/bin/env bash
|
||||
|
||||
# props to Katoumegumi for the original script, this is exactly the same one and it works wonders.
|
||||
#export PATH="$HOME/.wineosu/osuwine/bin:$PATH" #Use custom WINE version to run osu!
|
||||
export WINEARCH=win32
|
||||
export WINEPREFIX="$HOME/.wineosu"
|
||||
#export WINEFSYNC=1
|
||||
|
||||
#VSync. For some reason, some people had been getting input latency issues and for some reason, the fix is to set VSync to off.
|
||||
export vblank_mode=0 #For AMD, Intel and others
|
||||
export __GL_SYNC_TO_VBLANK=0 #For NVIDIA proprietary and open source >=500
|
||||
|
||||
#export STAGING_AUDIO_PERIOD=10000
|
||||
|
||||
#start osu!
|
||||
wine osu\!.exe
|
||||
</code></pre>
|
||||
<h2>Setting up the freedesktop entry</h2>
|
||||
<p>Download the osu! logo:</p>
|
||||
<pre><code class="language-sh">wget --output-document ~/.wineosu/osu/icon.png https://github.com/ppy/osu-wiki/raw/master/wiki/Brand_identity_guidelines/img/usage-full-colour.png
|
||||
</code></pre>
|
||||
<p>and finally edit a new file in the path <code>~/.local/share/applications/osu.desktop</code></p>
|
||||
<pre><code>[Desktop Entry]
|
||||
Type=Application
|
||||
Comment=A free-to-play rhythm game inspired in Osu! Tataekae! Ouendan!
|
||||
Icon=/home/<username>/.wineosu/osu/icon.png
|
||||
Exec=/home/<username>/.wineosu/osu/start.sh
|
||||
Path=/home/<username>/.wineosu/osu
|
||||
GenericName=osu!
|
||||
Name=osu!
|
||||
StartupNotify=true
|
||||
</code></pre>
|
||||
<h1>that's it</h1>
|
||||
<p>Way harder than I thought. If you want the command with all dependencies, type in:</p>
|
||||
<pre><code class="language-sh">sudo pacman -S lib32-gnutls lib32-mesa lib32-alsa-plugins lib32-libpulse lib32-openal
|
||||
</code></pre>
|
||||
]]></content>
|
||||
<author>
|
||||
<name>Sr Izan</name>
|
||||
<uri>https://srizan.dev</uri>
|
||||
</author>
|
||||
</entry>
|
||||
<entry>
|
||||
<title type="html"><![CDATA[How to change the user password in Arch if you forgot it]]></title>
|
||||
<id>https://srizan.dev/blog/3</id>
|
||||
<link href="https://srizan.dev/blog/3"/>
|
||||
<updated>2023-11-23T00:00:00.000Z</updated>
|
||||
<summary type="html"><![CDATA[This post was made for a certain person who loves to lose passwords]]></summary>
|
||||
<content type="html"><![CDATA[<p>Alright, let's do this. Fast.<br>Disclaimer: this only works when the /home directory is on the same partition, which is the default option if you don't specify.</p>
|
||||
<h1>Step 1: Boot up a live environment.</h1>
|
||||
<p>For the sake of simplicity, I'll be using the Endeavour OS Galileo installation media, but <a href="https://command-not-found.com/arch-chroot">any linux distro should work</a></p>
|
||||
<p>When you're in, open the terminal:<br><img src="https://img.srizan.dev/vmware_zCwt9ac9KE.png" alt=""></p>
|
||||
<h1>Step 2: Mounting the linux distro</h1>
|
||||
<p>Type in <code>lsblk</code>. This will show all mounted drives.<br><img src="https://img.srizan.dev/vmware_LPBNlTo9BI.png" alt=""></p>
|
||||
<p>Locate the drive and partition where your installation is.<br>It's usually the partition with the most space. The space is on the size row (duh)<br>If you have multiple drives with the same size and want more info about the volumes, type in <code>fdisk -l</code>.</p>
|
||||
<p>In my case it's <code>/dev/sda1</code>.</p>
|
||||
<p>So let's mount the partition to the <code>/mnt</code> directory with <code>sudo mount /dev/sda1 /mnt</code>. </p>
|
||||
<h1>Step 3: Chrootin'</h1>
|
||||
<p>Chroot is a linux tool which basically changes the root directory to whatever directory you specify. This will be used to run the <code>passwd</code> command inside your installation's context.</p>
|
||||
<p>Arch Linux has it's own chroot command which does some magic in the background to make it useable on this distro's environments.</p>
|
||||
<pre><code class="language-sh">sudo arch-chroot /mnt
|
||||
</code></pre>
|
||||
<p>should chroot into your installation and after a few seconds a shell will show up!<br><img src="https://img.srizan.dev/vmware_nyyqOA9ELo.png" alt=""></p>
|
||||
<p>And now one last command, the one that actually changes the password:</p>
|
||||
<pre><code class="language-sh">passwd yourusername
|
||||
</code></pre>
|
||||
<p>and boom! that's it! impressive, right? <code>exit</code> off the console and then reboot.</p>
|
||||
<h1>The end</h1>
|
||||
<p>That was quick.</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>2024-08-08T00: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>
|
||||
<entry>
|
||||
<title type="html"><![CDATA[Releasing sern Automata v2]]></title>
|
||||
<id>https://srizan.dev/blog/5</id>
|
||||
<link href="https://srizan.dev/blog/5"/>
|
||||
<updated>2024-05-08T18:46:01.405Z</updated>
|
||||
<content type="html"><![CDATA[<p>As once our lord and savior Terry A. Davis said, <a href="https://www.goodreads.com/quotes/10480697-an-idiot-admires-complexity-a-genius-admires-simplicity-a-physicist">"An idiot admires complexity, a genius admires simplicity."</a><br>And, welp, that's what I just did with my github bot Automata.</p>
|
||||
<h1>v1</h1>
|
||||
<p>Automata v1 was a real mess. We were supposed to publish a next.js website, a jobs system to make our lives easier, and a large etc.</p>
|
||||
<p>Now, I've been working on this for quite a while (about a year) and I felt like I wasn't going anywhere. So I just thought about it we could run the automation scripts on Github Actions, right?</p>
|
||||
<h1>So I got to work</h1>
|
||||
<p>And in 4 days I got the whole thing working. I'm really proud of it.</p>
|
||||
<p>We send API requests to Github whenever we want to trigger a workflow, on demand, by using the webhook system and handling everything from there.<br>As a bonus I also added a command, so we could merge on a more controlled way, specially on larger PRs.</p>
|
||||
<h2>Unexpected bugs on prod just after the PR merge</h2>
|
||||
<p>There were some bugs on launch.</p>
|
||||
<ul>
|
||||
<li>I didn't have a start script nor a license <a href="https://github.com/sern-handler/automata/commit/05c6ac3b81740ab9dcf86155b16afc84e7c850c8">(commit)</a></li>
|
||||
<li>I forgot to change some environment variable names on index.ts <a href="https://github.com/sern-handler/automata/commit/6e5d21ce548db04e9926ef7dfc3c1a5945d61aed">(commit)</a></li>
|
||||
<li>A day later, forgot to listen on the <code>PORT</code> environment variable <a href="https://github.com/sern-handler/automata/commit/622d6e72ded4330e06e802432677855a5397cedc">(commit)</a></li>
|
||||
<li>Squash and merge by default (ended up regretting it, see below) <a href="https://github.com/sern-handler/automata/commit/65ccede477f509c2e2de27be40a78281b79cda18">(commit)</a></li>
|
||||
<li>I FORGOT TO CHECK IF THE COMMAND MENTIONED @SERNBOT <a href="https://github.com/sern-handler/automata/commit/a5e58a415423eace3a8b0d6fbc4fe43e050c0624">(commit)</a></li>
|
||||
</ul>
|
||||
<p>That last one was a real pain, as I detected the bug in production, on the most important PR of the week, if not the month: the website's move to starlight docs.
|
||||
<img src="/blog/img/sernAutomataV2/prMerge1.png" alt=""><br><img src="/blog/img/sernAutomataV2/prMerge2.png" alt=""></p>
|
||||
<h1>Conclusion</h1>
|
||||
<p>Wow, a project with good DevEx? SIGN ME UP!</p>
|
||||
<p>PD: I created a next.js template with my preferred tech stack, make sure to check it out! <a href="https://stack.srizan.dev">link</a></p>
|
||||
]]></content>
|
||||
<author>
|
||||
<name>Sr Izan</name>
|
||||
<uri>https://srizan.dev</uri>
|
||||
</author>
|
||||
</entry>
|
||||
</feed>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 72 KiB |
@@ -1,342 +0,0 @@
|
||||
<?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>Wed, 08 May 2024 19:45: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>Sun, 12 Nov 2023 00: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[Install osu! on Endeavour OS]]></title>
|
||||
<link>https://srizan.dev/blog/4</link>
|
||||
<guid>https://srizan.dev/blog/4</guid>
|
||||
<pubDate>Sun, 17 Dec 2023 00:00:00 GMT</pubDate>
|
||||
<description><![CDATA[A guide on how to install osu! on Endeavour OS]]></description>
|
||||
<content:encoded><![CDATA[<p>Alright, so you want to install osu! on Endeavour OS. I just reinstalled my system. Two birds with one stone!<br>Based on <a href="https://wiki.archlinux.org/title/User:Katoumegumi">https://wiki.archlinux.org/title/User:Katoumegumi</a></p>
|
||||
<h1>Backstory</h1>
|
||||
<p>My Windows installation has been unstable since year 1 of my computer, and it even went to the point that when I open osu! sometimes it would just not work, black out my screen and play the last sound it was playing (see audio when a BSOD happens)<br>Yesterday I went to install <a href="https://osu.ppy.sh/home/changelog/stable40/20231217.1">the latest update</a>, click on restart, yadiyadiyada and unfortunately my computer CRASHED. I didn't feel nervous until I opened up the game to play a bit and find out that my skin was the default one.</p>
|
||||
<p>Okay, strange, lemme open up the game...</p>
|
||||
<p>It starts importing all beatmaps.</p>
|
||||
<p>Okay then, a bit understandable because the update is all about difficulty calculator.</p>
|
||||
<p>When it finishes, I go to my most recent plays.<br>Nothing.<br>Never played.<br>What?</p>
|
||||
<p>I go into my collections.<br>They're still there.<br>Okay, but are the scores there?<br>NO!</p>
|
||||
<p>Thus, after recovering my replays using a batch scripts that opens all <code>.osr</code> files in my <code>Data\r</code> folder, I just moved to linux, and this is a writeup on how I've been installing the game since my first time in April.</p>
|
||||
<p>Enjoy!</p>
|
||||
<h1>Installation</h1>
|
||||
<h2>Installing Wine</h2>
|
||||
<p>We first need to install wine and winetricks, to do so run:</p>
|
||||
<pre><code class="language-sh">sudo pacman -S wine
|
||||
wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
|
||||
chmod +x winetricks
|
||||
sudo mv ./winetricks /usr/local/bin/winetricks
|
||||
</code></pre>
|
||||
<h2>Preparing wineprefix</h2>
|
||||
<p>We now need to install dependencies like fonts or dotnet to make osu! run correctly</p>
|
||||
<pre><code class="language-sh">WINEARCH=win32 WINEPREFIX=~/.wineosu winetricks dotnet40 dotnet45 cjkfonts gdiplus
|
||||
</code></pre>
|
||||
<p><strong>(update 12/19/2023)</strong>: added <code>dotnet45</code> to make it work with <a href="https://github.com/l3lackShark/gosumemory/issues/140#issuecomment-1179663744">gosumemory</a></p>
|
||||
<h2>Installing osu!</h2>
|
||||
<p>Skipping this part, but basically download the installer and run it.<br>I already have my own 3-year-old installation with all my everything on it, so I'm bind-mounting it.</p>
|
||||
<p>Create a directory:</p>
|
||||
<pre><code class="language-sh">mkdir ~/.wineosu/osu
|
||||
</code></pre>
|
||||
<p>And then I added the following fstab line:</p>
|
||||
<pre><code class="language-sh">/mnt/HDD/osu! /home/srizan/.wineosu/osu none defaults,bind 0 0
|
||||
</code></pre>
|
||||
<h2>Trying to open it up</h2>
|
||||
<p>Running <code>WINEARCH=win32 WINEPREFIX=~/.wineosu wine ~/.wineosu/osu/osu\!.exe</code> twice leads us in the osu! installer with no network connection found.</p>
|
||||
<p>This is a bit easy to fix, as Wine itself yields this error if you scroll up a bit:</p>
|
||||
<pre><code class="language-sh">01b0:err:winediag:process_attach Failed to load libgnutls, secure connections will not be available.
|
||||
</code></pre>
|
||||
<p>Looking the package up in the <a href="https://archlinux.org/packages/?q=libgnutls">Arch Linux repos</a>, a package called <code>lib32-gnutls</code> shows up which looks to be exactly what we want.</p>
|
||||
<p>After installing it, the SSL connection worked and the game is going to sta- too bad!
|
||||
<img src="/blog/img/osu-eOS/graphicsContext.png" alt=""><br>You thought that was gonna be IT!</p>
|
||||
<p>It wants a GL context, which we don't have, so installing <a href="https://archlinux.org/packages/multilib-testing/x86_64/lib32-mesa/"><code>lib32-mesa</code></a> fixes it. Easy!</p>
|
||||
<p>Now, the window is... dark? WHEN ARE WE DONE MAN?<br>Welp, when going to almost the top of the file, we can see this:</p>
|
||||
<pre><code class="language-sh">0024:err:winediag:create_gl_drawable XComposite is not available, using GLXPixmap hack.
|
||||
</code></pre>
|
||||
<p>"GLXPixmap hack"?<br>The game is, according to peppy, held with duct tape, so no hacks are really going to work.</p>
|
||||
<p>Here we go, <a href="https://archlinux.org/packages/multilib/x86_64/lib32-libxcomposite/">yet ANOTHER lib32 package</a> should fix it.</p>
|
||||
<p>It opens up!<br><img src="/blog/img/osu-eOS/noaudio.mp4" alt=""></p>
|
||||
<p>Yeah. Nice. No audio.<br>Browsing through the Arch forums I found <img src="https://bbs.archlinux.org/viewtopic.php?id=135032" alt="this post">, and I installed the three <code>lib32-alsa-plugins lib32-libpulse lib32-openal</code> packages.</p>
|
||||
<p>And it started up! (too lazy to screenshot)</p>
|
||||
<h2>Setting up the start script</h2>
|
||||
<p>edit a file in <code>~/.wineosu/osu/start.sh</code> with the following contents:</p>
|
||||
<pre><code class="language-sh">#!/usr/bin/env bash
|
||||
|
||||
# props to Katoumegumi for the original script, this is exactly the same one and it works wonders.
|
||||
#export PATH="$HOME/.wineosu/osuwine/bin:$PATH" #Use custom WINE version to run osu!
|
||||
export WINEARCH=win32
|
||||
export WINEPREFIX="$HOME/.wineosu"
|
||||
#export WINEFSYNC=1
|
||||
|
||||
#VSync. For some reason, some people had been getting input latency issues and for some reason, the fix is to set VSync to off.
|
||||
export vblank_mode=0 #For AMD, Intel and others
|
||||
export __GL_SYNC_TO_VBLANK=0 #For NVIDIA proprietary and open source >=500
|
||||
|
||||
#export STAGING_AUDIO_PERIOD=10000
|
||||
|
||||
#start osu!
|
||||
wine osu\!.exe
|
||||
</code></pre>
|
||||
<h2>Setting up the freedesktop entry</h2>
|
||||
<p>Download the osu! logo:</p>
|
||||
<pre><code class="language-sh">wget --output-document ~/.wineosu/osu/icon.png https://github.com/ppy/osu-wiki/raw/master/wiki/Brand_identity_guidelines/img/usage-full-colour.png
|
||||
</code></pre>
|
||||
<p>and finally edit a new file in the path <code>~/.local/share/applications/osu.desktop</code></p>
|
||||
<pre><code>[Desktop Entry]
|
||||
Type=Application
|
||||
Comment=A free-to-play rhythm game inspired in Osu! Tataekae! Ouendan!
|
||||
Icon=/home/<username>/.wineosu/osu/icon.png
|
||||
Exec=/home/<username>/.wineosu/osu/start.sh
|
||||
Path=/home/<username>/.wineosu/osu
|
||||
GenericName=osu!
|
||||
Name=osu!
|
||||
StartupNotify=true
|
||||
</code></pre>
|
||||
<h1>that's it</h1>
|
||||
<p>Way harder than I thought. If you want the command with all dependencies, type in:</p>
|
||||
<pre><code class="language-sh">sudo pacman -S lib32-gnutls lib32-mesa lib32-alsa-plugins lib32-libpulse lib32-openal
|
||||
</code></pre>
|
||||
]]></content:encoded>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[How to change the user password in Arch if you forgot it]]></title>
|
||||
<link>https://srizan.dev/blog/3</link>
|
||||
<guid>https://srizan.dev/blog/3</guid>
|
||||
<pubDate>Thu, 23 Nov 2023 00:00:00 GMT</pubDate>
|
||||
<description><![CDATA[This post was made for a certain person who loves to lose passwords]]></description>
|
||||
<content:encoded><![CDATA[<p>Alright, let's do this. Fast.<br>Disclaimer: this only works when the /home directory is on the same partition, which is the default option if you don't specify.</p>
|
||||
<h1>Step 1: Boot up a live environment.</h1>
|
||||
<p>For the sake of simplicity, I'll be using the Endeavour OS Galileo installation media, but <a href="https://command-not-found.com/arch-chroot">any linux distro should work</a></p>
|
||||
<p>When you're in, open the terminal:<br><img src="https://img.srizan.dev/vmware_zCwt9ac9KE.png" alt=""></p>
|
||||
<h1>Step 2: Mounting the linux distro</h1>
|
||||
<p>Type in <code>lsblk</code>. This will show all mounted drives.<br><img src="https://img.srizan.dev/vmware_LPBNlTo9BI.png" alt=""></p>
|
||||
<p>Locate the drive and partition where your installation is.<br>It's usually the partition with the most space. The space is on the size row (duh)<br>If you have multiple drives with the same size and want more info about the volumes, type in <code>fdisk -l</code>.</p>
|
||||
<p>In my case it's <code>/dev/sda1</code>.</p>
|
||||
<p>So let's mount the partition to the <code>/mnt</code> directory with <code>sudo mount /dev/sda1 /mnt</code>. </p>
|
||||
<h1>Step 3: Chrootin'</h1>
|
||||
<p>Chroot is a linux tool which basically changes the root directory to whatever directory you specify. This will be used to run the <code>passwd</code> command inside your installation's context.</p>
|
||||
<p>Arch Linux has it's own chroot command which does some magic in the background to make it useable on this distro's environments.</p>
|
||||
<pre><code class="language-sh">sudo arch-chroot /mnt
|
||||
</code></pre>
|
||||
<p>should chroot into your installation and after a few seconds a shell will show up!<br><img src="https://img.srizan.dev/vmware_nyyqOA9ELo.png" alt=""></p>
|
||||
<p>And now one last command, the one that actually changes the password:</p>
|
||||
<pre><code class="language-sh">passwd yourusername
|
||||
</code></pre>
|
||||
<p>and boom! that's it! impressive, right? <code>exit</code> off the console and then reboot.</p>
|
||||
<h1>The end</h1>
|
||||
<p>That was quick.</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>Thu, 08 Aug 2024 00: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>
|
||||
<item>
|
||||
<title><![CDATA[Releasing sern Automata v2]]></title>
|
||||
<link>https://srizan.dev/blog/5</link>
|
||||
<guid>https://srizan.dev/blog/5</guid>
|
||||
<pubDate>Wed, 08 May 2024 18:46:01 GMT</pubDate>
|
||||
<content:encoded><![CDATA[<p>As once our lord and savior Terry A. Davis said, <a href="https://www.goodreads.com/quotes/10480697-an-idiot-admires-complexity-a-genius-admires-simplicity-a-physicist">"An idiot admires complexity, a genius admires simplicity."</a><br>And, welp, that's what I just did with my github bot Automata.</p>
|
||||
<h1>v1</h1>
|
||||
<p>Automata v1 was a real mess. We were supposed to publish a next.js website, a jobs system to make our lives easier, and a large etc.</p>
|
||||
<p>Now, I've been working on this for quite a while (about a year) and I felt like I wasn't going anywhere. So I just thought about it we could run the automation scripts on Github Actions, right?</p>
|
||||
<h1>So I got to work</h1>
|
||||
<p>And in 4 days I got the whole thing working. I'm really proud of it.</p>
|
||||
<p>We send API requests to Github whenever we want to trigger a workflow, on demand, by using the webhook system and handling everything from there.<br>As a bonus I also added a command, so we could merge on a more controlled way, specially on larger PRs.</p>
|
||||
<h2>Unexpected bugs on prod just after the PR merge</h2>
|
||||
<p>There were some bugs on launch.</p>
|
||||
<ul>
|
||||
<li>I didn't have a start script nor a license <a href="https://github.com/sern-handler/automata/commit/05c6ac3b81740ab9dcf86155b16afc84e7c850c8">(commit)</a></li>
|
||||
<li>I forgot to change some environment variable names on index.ts <a href="https://github.com/sern-handler/automata/commit/6e5d21ce548db04e9926ef7dfc3c1a5945d61aed">(commit)</a></li>
|
||||
<li>A day later, forgot to listen on the <code>PORT</code> environment variable <a href="https://github.com/sern-handler/automata/commit/622d6e72ded4330e06e802432677855a5397cedc">(commit)</a></li>
|
||||
<li>Squash and merge by default (ended up regretting it, see below) <a href="https://github.com/sern-handler/automata/commit/65ccede477f509c2e2de27be40a78281b79cda18">(commit)</a></li>
|
||||
<li>I FORGOT TO CHECK IF THE COMMAND MENTIONED @SERNBOT <a href="https://github.com/sern-handler/automata/commit/a5e58a415423eace3a8b0d6fbc4fe43e050c0624">(commit)</a></li>
|
||||
</ul>
|
||||
<p>That last one was a real pain, as I detected the bug in production, on the most important PR of the week, if not the month: the website's move to starlight docs.
|
||||
<img src="/blog/img/sernAutomataV2/prMerge1.png" alt=""><br><img src="/blog/img/sernAutomataV2/prMerge2.png" alt=""></p>
|
||||
<h1>Conclusion</h1>
|
||||
<p>Wow, a project with good DevEx? SIGN ME UP!</p>
|
||||
<p>PD: I created a next.js template with my preferred tech stack, make sure to check it out! <a href="https://stack.srizan.dev">link</a></p>
|
||||
]]></content:encoded>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
|
Before Width: | Height: | Size: 492 KiB |
9
public/favicon.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 749 B |
BIN
public/fonts/atkinson-bold.woff
Normal file
BIN
public/fonts/atkinson-regular.woff
Normal file
|
Before Width: | Height: | Size: 293 KiB |
@@ -1,27 +0,0 @@
|
||||
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';
|
||||
import RandomBackground from '@/app/_components/RandomBackground';
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<RandomBackground />
|
||||
<div className='aboutMeBox'>
|
||||
<Image src='/pfp.webp' alt='main profile picture' width='200' height='200' style={{ borderRadius: '100px' }} />
|
||||
<p>Hobbyist developer & <Link href={'https://sern.dev'}>sern</Link> lead developer team member</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.kalico.moe/@srizan'><FaMastodon /></Link>
|
||||
<Link href='https://twitter.com/itssrizan'><FaTwitter /></Link>
|
||||
<Link href='https://osu.ppy.sh/users/25350735'><SiOsu /></Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Collab() {
|
||||
return (
|
||||
<div>
|
||||
<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" />
|
||||
<area target="_blank" alt="Willysuna" title="Willysuna" href="https://willysuna.dev" coords="716,0,1050,317" shape="rect" />
|
||||
<area target="_blank" alt="Aleandro" title="Aleandro" href="https://www.youtube.com/watch?v=G7QME0bzZuA" coords="663,319,355,0" shape="rect" />
|
||||
<area target="_blank" alt="Nerticel" title="Nerticel" href="https://www.youtube.com/shorts/-uAZdIJIl8o" coords="1,340,307,633" shape="rect" />
|
||||
<area target="_blank" alt="Orchuna" title="Orchuna" href="https://www.tumblr.com/orchunaxd" coords="333,328,679,637" shape="rect" />
|
||||
<area target="_blank" alt="XaviXE" title="XaviXE" href="https://srizan.dev/xavixe" coords="761,403,1045,571" shape="rect" />
|
||||
</map>
|
||||
<p>click on each person haha yes </p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
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 "../../../_components/NavBar";
|
||||
import '../../../_css/BlogPost.css';
|
||||
import React from "react";
|
||||
import jsonDataArray from '@/blogPosts.json';
|
||||
import { redirect } from "next/navigation";
|
||||
import { Metadata } from 'next';
|
||||
import dayjs from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
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: ''
|
||||
}
|
||||
|
||||
const filteredPost = jsonDataArray.filter((post) => post.id === id)[0];
|
||||
if (filteredPost) {
|
||||
jsonData = filteredPost;
|
||||
} else {
|
||||
redirect('/blog')
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<BlogNavBar title={jsonData.title} isBlog />
|
||||
<div className={'blogPostContent'}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={{
|
||||
code({node, className, children, ...props}) {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
return match ? (
|
||||
<SyntaxHighlighter
|
||||
style={atomDark}
|
||||
customStyle={{ backgroundColor: '#171717', outline: 'solid' }}
|
||||
codeTagProps={{ className: 'codeHighlighter' }}
|
||||
language={match[1]}
|
||||
// eslint-disable-next-line react/no-children-prop
|
||||
children={String(children).replace(/\n$/, '')}
|
||||
/>
|
||||
) : (
|
||||
<code className={className} {...props} style={{
|
||||
fontSize: '1rem',
|
||||
backgroundColor: '#171717',
|
||||
outline: '3px solid #171717'
|
||||
}}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
},
|
||||
img(props) {
|
||||
// eslint-disable-next-line jsx-a11y/alt-text
|
||||
return <img {...props} style={{ maxWidth: '100%' }} />
|
||||
}
|
||||
}}>
|
||||
{jsonData.fileContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
|
||||
const id = parseInt(params.id);
|
||||
if (Number.isNaN(id)) redirect('/blog')
|
||||
let jsonData = {
|
||||
id: 0,
|
||||
title: '',
|
||||
description: '',
|
||||
date: '',
|
||||
fileName: '',
|
||||
fileContent: ''
|
||||
}
|
||||
|
||||
const filteredPost = jsonDataArray.filter((post) => post.id === id)[0];
|
||||
if (!filteredPost) redirect('/blog')
|
||||
jsonData = filteredPost;
|
||||
|
||||
return {
|
||||
title: jsonData.title,
|
||||
description: jsonData.description,
|
||||
openGraph: {
|
||||
title: jsonData.title,
|
||||
description: jsonData.description,
|
||||
authors: ['Sr Izan'],
|
||||
type: 'article',
|
||||
url: `https://srizan.dev/blog/${id}`,
|
||||
publishedTime: dayjs(jsonData.date).toISOString(),
|
||||
siteName: 'Sr Izan\'s blog',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type BlogPostJSONResponse = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
fileName: string;
|
||||
fileContent: string;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import '../../../app/_css/Blog.css';
|
||||
import blogPosts from '@/blogPosts.json'
|
||||
import BlogPostCard from '../../../app/_components/BlogPostCard';
|
||||
import BlogNavBar from '../../_components/NavBar';
|
||||
import BlogRssDial from '@/app/_components/BlogRssDial';
|
||||
|
||||
function Blog() {
|
||||
return (
|
||||
<div>
|
||||
<BlogNavBar isBlog />
|
||||
<div className="blogPosts">
|
||||
{blogPosts.map((post) => {
|
||||
return (
|
||||
<BlogPostCard {...post} key={post.id} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="bottomRight">
|
||||
<BlogRssDial />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Blog;
|
||||
@@ -1,40 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import Box from '@mui/material/Box';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import CardActions from '@mui/material/CardActions';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import dayjs from 'dayjs'
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
|
||||
import '../_css/BlogPostCard.css'
|
||||
import Link from "next/link";
|
||||
import React from 'react';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
export default function BlogPostCard(props: Props) {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
return (
|
||||
<Card>
|
||||
<Box className={'cardBox'}>
|
||||
<CardContent>
|
||||
<Typography variant="h5">
|
||||
{props.title}
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" color="text.secondary" component="div" suppressHydrationWarning>
|
||||
{dayjs(props.date).toDate().toLocaleDateString()}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" component="div">
|
||||
{props.description}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions className={'actions'}>
|
||||
<Link href={`/blog/${props.id}`}><LoadingButton size="small" loading={loading} onClick={() => setLoading(true)}>Read</LoadingButton></Link>
|
||||
</CardActions>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
type Props = { title: string, description: string, date: string, id: number }
|
||||
@@ -1,44 +0,0 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import '../_css/ImageAuthor.css'
|
||||
import { FaExternalLinkAlt } from "react-icons/fa";
|
||||
|
||||
export default function ImageAuthor(props: Props) {
|
||||
return (
|
||||
<div className='imageAuthor'>
|
||||
<Link href={props.location} target="_blank"><p>{props.description}</p></Link>
|
||||
<p>by {props.author}</p>
|
||||
<Link href={props.url} target="_blank"><FaExternalLinkAlt /></Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type Props = {
|
||||
url: string;
|
||||
author: string;
|
||||
description: string;
|
||||
location: string;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// sadge
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import '../_css/BlogNavBar.css'
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export default function NavBar(props: Props) {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [title, setTitle] = useState(props.title || 'Unnamed page');
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isBlog) {
|
||||
setTitle('Blog');
|
||||
}
|
||||
let prevScrollpos = window.scrollY;
|
||||
|
||||
const handleScroll = () => {
|
||||
const currentScrollPos = window.scrollY;
|
||||
setIsScrolled(prevScrollpos < currentScrollPos);
|
||||
prevScrollpos = currentScrollPos;
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
return (
|
||||
<div className={`navBar ${isScrolled ? 'hide' : ''}`}>
|
||||
<div className="iconContainer">
|
||||
<img src="https://res.cloudinary.com/mainwebsite/image/upload/v1707926066/assets/emmhztassq5zyol9gubk.webp" alt="main profile picture" height="50vh" />
|
||||
<p>{title}</p>
|
||||
</div>
|
||||
<Link href='#' onClick={() => router.back()} className="backHomeLink">Go back</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type Props = { title?: string, isBlog?: boolean }
|
||||
@@ -1,21 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import Backgrounds from '../../backgrounds.json'
|
||||
import ImageAuthor from '@/app/_components/ImageAuthor';
|
||||
import '../_css/RandomBackground.css'
|
||||
import Image from 'next/image'
|
||||
import React from 'react';
|
||||
|
||||
export default function RandomBackground() {
|
||||
const [randomBg, setRandomBg] = React.useState<typeof Backgrounds[0]>()
|
||||
React.useEffect(() => {
|
||||
const random = Math.floor(Math.random() * Backgrounds.length)
|
||||
setRandomBg(Backgrounds[random])
|
||||
}, [])
|
||||
return randomBg ? (
|
||||
<div>
|
||||
<Image className='bgImage' loader={() => randomBg.url} src={randomBg.url} fill={true} alt={randomBg.description} />
|
||||
<ImageAuthor {...randomBg} />
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Roboto } from 'next/font/google';
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
|
||||
const roboto = Roboto({
|
||||
weight: ['300', '400', '500', '700'],
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
});
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
background: {
|
||||
default: '#0d0d0d',
|
||||
paper: '#0d0d0d',
|
||||
},
|
||||
primary: {
|
||||
main: '#646cff',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: roboto.style.fontFamily,
|
||||
},
|
||||
});
|
||||
|
||||
export default theme;
|
||||
@@ -1,35 +0,0 @@
|
||||
/* All navbar code was refactored to now be in the BlogNavBar.css file alongside with the component. */
|
||||
|
||||
/* I adjust the card's width on the BlogPostCard.css file instead of here */
|
||||
.blogPosts {
|
||||
margin-top: 80px;
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.blogPosts > * {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.bottomRight {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 20px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 20px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #191919;
|
||||
border-radius: 20px;
|
||||
border: 6px solid transparent;
|
||||
background-clip: content-box;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
.navBar {
|
||||
width: 40vw;
|
||||
height: 60px;
|
||||
background-color: #0d0d0d;
|
||||
border-radius: 15px;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
transition: top 0.2s;
|
||||
}
|
||||
|
||||
.hide {
|
||||
top: -100px;
|
||||
}
|
||||
|
||||
.iconContainer {
|
||||
margin-left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
.iconContainer img {
|
||||
border-radius: 50px;
|
||||
}
|
||||
.iconContainer p {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.backHomeLink {
|
||||
justify-content: flex-end;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
/* some fixes for mobile and small viewports */
|
||||
@media (max-width: 1160px) {
|
||||
.navBar {
|
||||
width: 100vw;
|
||||
top: 0;
|
||||
border-radius: 0;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.navBar {
|
||||
background-color: #0d0d0d;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap');
|
||||
|
||||
.blogPostContent {
|
||||
margin-top: 100px;
|
||||
z-index: 0;
|
||||
padding: 0 20vw 0 20vw;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
code .codeHighlighter {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.blogPostContent {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
code {
|
||||
color: #FFF
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
.cardBox {
|
||||
width: 40vw;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions {
|
||||
float: right
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.cardBox {
|
||||
width: 100vw;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
.imageAuthor {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
font-size: 0.8em;
|
||||
color: white;
|
||||
background-color: black;
|
||||
opacity: .4;
|
||||
transition: opacity .1s ease-out;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
}
|
||||
.imageAuthor:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.imageAuthor p {
|
||||
margin-right: 0.2em;
|
||||
margin-top: 0.1em;
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
.imageAuthor p:first-child {
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
.bgImage {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
filter: blur(5px);
|
||||
filter: brightness(30%);
|
||||
object-fit: cover;
|
||||
z-index: -1;
|
||||
background-attachment: fixed;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
.aboutMeBox {
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.icons {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.icons * {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.icons svg {
|
||||
/* change from 1em */
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
.icons *:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.aboutMeBox {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/* Yes, this is a kinda unmodified index.css file from the vite template. I like the colors and stuff so I'll keep it. */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter&display=swap");
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #1d1d1d;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
--link-color: #646cff;
|
||||
--link-color-hover: #535bf2;
|
||||
--link-color-hover-light: #747bff;
|
||||
--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;
|
||||
}
|
||||
|
||||
.navbarMargin {
|
||||
margin-top: 90px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: var(--link-color);
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: var(--link-color-hover);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: var('--link-color-hover');
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #1d1d1d;
|
||||
}
|
||||
a:hover {
|
||||
color: var(--link-color-hover-light);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
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 of the net',
|
||||
icons: { icon: '/pfp.webp' },
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script async src="https://analytics.srizan.dev/ua.js" data-website-id="54ccb44c-b03c-4790-8262-3e1a82241a24" />
|
||||
</head>
|
||||
<body className={inter.className}>
|
||||
<RootStyleRegistry>
|
||||
{children}
|
||||
</RootStyleRegistry>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +1,32 @@
|
||||
[
|
||||
{
|
||||
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Dom_Luis_I_Bridge_Porto_dsdodq.jpg",
|
||||
"author": "me",
|
||||
"description": "Dom Luis I Bridge (Porto)",
|
||||
"location": "https://www.openstreetmap.org/way/98835981"
|
||||
},
|
||||
{
|
||||
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Oviedo_Landscape_c8xva6.jpg",
|
||||
"author": "me",
|
||||
"description": "Oviedo Landscape",
|
||||
"location": "https://www.openstreetmap.org/way/605000131#map=17/43.37598/-5.86843"
|
||||
},
|
||||
{
|
||||
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Playa_de_Santa_Marina_ae389j.jpg",
|
||||
"author": "me",
|
||||
"description": "Playa de Santa Marina (Ribadesella)",
|
||||
"location": "https://www.openstreetmap.org/way/822201400#map=19/43.46545/-5.06683"
|
||||
},
|
||||
{
|
||||
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Hotel_Best_Benalmadena_olqc9x.jpg",
|
||||
"author": "me",
|
||||
"description": "Hotel Best (Benalmádena)",
|
||||
"location": "https://www.openstreetmap.org/node/1253883928"
|
||||
},
|
||||
{
|
||||
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Douro_River_Porto_cuga4l.jpg",
|
||||
"author": "me",
|
||||
"description": "Douro River (Porto)",
|
||||
"location": "https://www.openstreetmap.org/#map=18/41.14065/-8.60647"
|
||||
}
|
||||
{
|
||||
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Dom_Luis_I_Bridge_Porto_dsdodq.jpg",
|
||||
"author": "me",
|
||||
"description": "Dom Luis I Bridge (Porto)",
|
||||
"location": "https://www.openstreetmap.org/way/98835981"
|
||||
},
|
||||
{
|
||||
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Oviedo_Landscape_c8xva6.jpg",
|
||||
"author": "me",
|
||||
"description": "Oviedo Landscape",
|
||||
"location": "https://www.openstreetmap.org/way/605000131#map=17/43.37598/-5.86843"
|
||||
},
|
||||
{
|
||||
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Playa_de_Santa_Marina_ae389j.jpg",
|
||||
"author": "me",
|
||||
"description": "Playa de Santa Marina (Ribadesella)",
|
||||
"location": "https://www.openstreetmap.org/way/822201400#map=19/43.46545/-5.06683"
|
||||
},
|
||||
{
|
||||
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Hotel_Best_Benalmadena_olqc9x.jpg",
|
||||
"author": "me",
|
||||
"description": "Hotel Best (Benalmádena)",
|
||||
"location": "https://www.openstreetmap.org/node/1253883928"
|
||||
},
|
||||
{
|
||||
"url": "https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Douro_River_Porto_cuga4l.jpg",
|
||||
"author": "me",
|
||||
"description": "Douro River (Porto)",
|
||||
"location": "https://www.openstreetmap.org/#map=18/41.14065/-8.60647"
|
||||
}
|
||||
]
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
title: How I made the install functionality on Pyramid Launcher
|
||||
description: ""
|
||||
date: 2024-02-02T19:13:07.791Z
|
||||
preview: ""
|
||||
draft: true
|
||||
tags: []
|
||||
categories: []
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
Hey everyone! Back here with a random post about how I added the minecraft installation functionality to Pyramid Launcher.
|
||||
|
||||
Pyramid Launcher is yet another electron launcher I've been working on with [my buddies](https://github.com/pyramidmc/people).
|
||||
|
||||
# The problem
|
||||
|
||||
For some reason `@xmcl/installer` hasn't worked for me. It'd just get stuck downloading stuff. So I wanted to make my own installer.
|
||||
|
||||
# Minecraft's files
|
||||
|
||||
## Assets
|
||||
|
||||
Minecraft's assets are stored in a folder called `assets`. Inside it, there are two folders: `indexes` and `objects`. The `indexes` folder contains a JSON file with a list of all the assets and their hashes and the `objects` folder contains all the assets.
|
||||
|
||||
This last folder has all game files, and its layout was a bit confusing at first. It's a bit like this:
|
||||
|
||||
```
|
||||
.
|
||||
├── 00
|
||||
├── 01
|
||||
├── 02
|
||||
├── 03
|
||||
├── 04
|
||||
├── 05
|
||||
├── 06
|
||||
├── 07
|
||||
├── 08
|
||||
├── 09
|
||||
├── 0a
|
||||
├── 0b
|
||||
├── 0c
|
||||
├── 0d
|
||||
├── 0f
|
||||
(...)
|
||||
```
|
||||
|
||||
It looks a bit confusing, but when you notice that each folder is a hash of the file, it makes sense. This is a way to avoid having a lot of files in a single folder, which would make it slow to read, but also I don't think that's good for the filesystem read and writes! ;D
|
||||
|
||||
## Libraries
|
||||
|
||||
This one is probably the easiest one. It's just a folder with a bunch of `.jar` files inside the normal IDs that java packages use.
|
||||
|
||||
It's also the easiest one to query, as inside the [https://piston-meta.mojang.com/v1/packages/d546f1707a3f2b7d034eece5ea2e311eda875787/1.8.9.json](version metadata JSON file), there's a list of all the libraries, paths and their hashes.
|
||||
|
||||
## Versions
|
||||
|
||||
Finally, the versions folder, which has the client files and the metadata JSON file which is the same one as the one we pull the libraries from.
|
||||
|
||||
# The package
|
||||
|
||||
After starting off a new Typescript package project using [my ts-lib-boilerplate github repo](https://github.com/SrIzan10/ts-lib-boilerplate), I started off by creawting a new class called `Installer`.
|
||||
This class would be responsible for downloading and installing the game files.
|
||||
|
||||
```typescript
|
||||
|
||||
```
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
id: 5
|
||||
title: Releasing sern Automata v2
|
||||
description: ""
|
||||
date: 2024-05-08T18:46:01.405Z
|
||||
preview: ""
|
||||
draft: false
|
||||
tags: []
|
||||
categories: []
|
||||
type: default
|
||||
---
|
||||
|
||||
As once our lord and savior Terry A. Davis said, ["An idiot admires complexity, a genius admires simplicity."](https://www.goodreads.com/quotes/10480697-an-idiot-admires-complexity-a-genius-admires-simplicity-a-physicist)
|
||||
And, welp, that's what I just did with my github bot Automata.
|
||||
|
||||
# v1
|
||||
|
||||
Automata v1 was a real mess. We were supposed to publish a next.js website, a jobs system to make our lives easier, and a large etc.
|
||||
|
||||
Now, I've been working on this for quite a while (about a year) and I felt like I wasn't going anywhere. So I just thought about it we could run the automation scripts on Github Actions, right?
|
||||
|
||||
# So I got to work
|
||||
|
||||
And in 4 days I got the whole thing working. I'm really proud of it.
|
||||
|
||||
We send API requests to Github whenever we want to trigger a workflow, on demand, by using the webhook system and handling everything from there.
|
||||
As a bonus I also added a command, so we could merge on a more controlled way, specially on larger PRs.
|
||||
|
||||
## Unexpected bugs on prod just after the PR merge
|
||||
|
||||
There were some bugs on launch.
|
||||
- I didn't have a start script nor a license [(commit)](https://github.com/sern-handler/automata/commit/05c6ac3b81740ab9dcf86155b16afc84e7c850c8)
|
||||
- I forgot to change some environment variable names on index.ts [(commit)](https://github.com/sern-handler/automata/commit/6e5d21ce548db04e9926ef7dfc3c1a5945d61aed)
|
||||
- A day later, forgot to listen on the `PORT` environment variable [(commit)](https://github.com/sern-handler/automata/commit/622d6e72ded4330e06e802432677855a5397cedc)
|
||||
- Squash and merge by default (ended up regretting it, see below) [(commit)](https://github.com/sern-handler/automata/commit/65ccede477f509c2e2de27be40a78281b79cda18)
|
||||
- I FORGOT TO CHECK IF THE COMMAND MENTIONED @SERNBOT [(commit)](https://github.com/sern-handler/automata/commit/a5e58a415423eace3a8b0d6fbc4fe43e050c0624)
|
||||
|
||||
That last one was a real pain, as I detected the bug in production, on the most important PR of the week, if not the month: the website's move to starlight docs.
|
||||

|
||||

|
||||
|
||||
# Conclusion
|
||||
|
||||
Wow, a project with good DevEx? SIGN ME UP!
|
||||
|
||||
PD: I created a next.js template with my preferred tech stack, make sure to check it out! [link](https://stack.srizan.dev)
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
id: 1
|
||||
title: Welcome to my new blog!
|
||||
description: This post welcomes you to my new blog
|
||||
date: 2023-20-08T00:00:00Z
|
||||
---
|
||||
|
||||
# Hey!
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
Anyways, thank you for reading. I hope you enjoyed my UX/UI for this one!
|
||||
|
||||
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.
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: 3
|
||||
title: How to change the user password in Arch if you forgot it
|
||||
description: This post was made for a certain person who loves to lose passwords
|
||||
date: 2023-11-23T00:00:00Z
|
||||
---
|
||||
|
||||
Alright, let's do this. Fast.
|
||||
Disclaimer: this only works when the /home directory is on the same partition, which is the default option if you don't specify.
|
||||
|
||||
# Step 1: Boot up a live environment.
|
||||
|
||||
For the sake of simplicity, I'll be using the Endeavour OS Galileo installation media, but [any linux distro should work](https://command-not-found.com/arch-chroot)
|
||||
|
||||
When you're in, open the terminal:
|
||||

|
||||
|
||||
# Step 2: Mounting the linux distro
|
||||
|
||||
Type in `lsblk`. This will show all mounted drives.
|
||||

|
||||
|
||||
Locate the drive and partition where your installation is.
|
||||
It's usually the partition with the most space. The space is on the size row (duh)
|
||||
If you have multiple drives with the same size and want more info about the volumes, type in `fdisk -l`.
|
||||
|
||||
In my case it's `/dev/sda1`.
|
||||
|
||||
So let's mount the partition to the `/mnt` directory with `sudo mount /dev/sda1 /mnt`.
|
||||
|
||||
# Step 3: Chrootin'
|
||||
|
||||
Chroot is a linux tool which basically changes the root directory to whatever directory you specify. This will be used to run the `passwd` command inside your installation's context.
|
||||
|
||||
Arch Linux has it's own chroot command which does some magic in the background to make it useable on this distro's environments.
|
||||
|
||||
```sh
|
||||
sudo arch-chroot /mnt
|
||||
```
|
||||
should chroot into your installation and after a few seconds a shell will show up!
|
||||

|
||||
|
||||
And now one last command, the one that actually changes the password:
|
||||
|
||||
```sh
|
||||
passwd yourusername
|
||||
```
|
||||
|
||||
and boom! that's it! impressive, right? `exit` off the console and then reboot.
|
||||
|
||||
# The end
|
||||
|
||||
That was quick.
|
||||
@@ -1,150 +0,0 @@
|
||||
---
|
||||
id: 4
|
||||
title: Install osu! on Endeavour OS
|
||||
description: A guide on how to install osu! on Endeavour OS
|
||||
date: 2023-12-17T00:00:00Z
|
||||
---
|
||||
|
||||
Alright, so you want to install osu! on Endeavour OS. I just reinstalled my system. Two birds with one stone!
|
||||
Based on https://wiki.archlinux.org/title/User:Katoumegumi
|
||||
|
||||
# Backstory
|
||||
|
||||
My Windows installation has been unstable since year 1 of my computer, and it even went to the point that when I open osu! sometimes it would just not work, black out my screen and play the last sound it was playing (see audio when a BSOD happens)
|
||||
Yesterday I went to install [the latest update](https://osu.ppy.sh/home/changelog/stable40/20231217.1), click on restart, yadiyadiyada and unfortunately my computer CRASHED. I didn't feel nervous until I opened up the game to play a bit and find out that my skin was the default one.
|
||||
|
||||
Okay, strange, lemme open up the game...
|
||||
|
||||
It starts importing all beatmaps.
|
||||
|
||||
Okay then, a bit understandable because the update is all about difficulty calculator.
|
||||
|
||||
When it finishes, I go to my most recent plays.
|
||||
Nothing.
|
||||
Never played.
|
||||
What?
|
||||
|
||||
I go into my collections.
|
||||
They're still there.
|
||||
Okay, but are the scores there?
|
||||
NO!
|
||||
|
||||
Thus, after recovering my replays using a batch scripts that opens all `.osr` files in my `Data\r` folder, I just moved to linux, and this is a writeup on how I've been installing the game since my first time in April.
|
||||
|
||||
Enjoy!
|
||||
|
||||
# Installation
|
||||
|
||||
## Installing Wine
|
||||
|
||||
We first need to install wine and winetricks, to do so run:
|
||||
```sh
|
||||
sudo pacman -S wine
|
||||
wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
|
||||
chmod +x winetricks
|
||||
sudo mv ./winetricks /usr/local/bin/winetricks
|
||||
```
|
||||
|
||||
## Preparing wineprefix
|
||||
|
||||
We now need to install dependencies like fonts or dotnet to make osu! run correctly
|
||||
```sh
|
||||
WINEARCH=win32 WINEPREFIX=~/.wineosu winetricks dotnet40 dotnet45 cjkfonts gdiplus
|
||||
```
|
||||
**(update 12/19/2023)**: added `dotnet45` to make it work with [gosumemory](https://github.com/l3lackShark/gosumemory/issues/140#issuecomment-1179663744)
|
||||
|
||||
## Installing osu!
|
||||
|
||||
Skipping this part, but basically download the installer and run it.
|
||||
I already have my own 3-year-old installation with all my everything on it, so I'm bind-mounting it.
|
||||
|
||||
Create a directory:
|
||||
```sh
|
||||
mkdir ~/.wineosu/osu
|
||||
```
|
||||
And then I added the following fstab line:
|
||||
```sh
|
||||
/mnt/HDD/osu! /home/srizan/.wineosu/osu none defaults,bind 0 0
|
||||
```
|
||||
|
||||
## Trying to open it up
|
||||
|
||||
Running `WINEARCH=win32 WINEPREFIX=~/.wineosu wine ~/.wineosu/osu/osu\!.exe` twice leads us in the osu! installer with no network connection found.
|
||||
|
||||
This is a bit easy to fix, as Wine itself yields this error if you scroll up a bit:
|
||||
```sh
|
||||
01b0:err:winediag:process_attach Failed to load libgnutls, secure connections will not be available.
|
||||
```
|
||||
Looking the package up in the [Arch Linux repos](https://archlinux.org/packages/?q=libgnutls), a package called `lib32-gnutls` shows up which looks to be exactly what we want.
|
||||
|
||||
After installing it, the SSL connection worked and the game is going to sta- too bad!
|
||||

|
||||
You thought that was gonna be IT!
|
||||
|
||||
It wants a GL context, which we don't have, so installing [`lib32-mesa`](https://archlinux.org/packages/multilib-testing/x86_64/lib32-mesa/) fixes it. Easy!
|
||||
|
||||
Now, the window is... dark? WHEN ARE WE DONE MAN?
|
||||
Welp, when going to almost the top of the file, we can see this:
|
||||
```sh
|
||||
0024:err:winediag:create_gl_drawable XComposite is not available, using GLXPixmap hack.
|
||||
```
|
||||
"GLXPixmap hack"?
|
||||
The game is, according to peppy, held with duct tape, so no hacks are really going to work.
|
||||
|
||||
Here we go, [yet ANOTHER lib32 package](https://archlinux.org/packages/multilib/x86_64/lib32-libxcomposite/) should fix it.
|
||||
|
||||
It opens up!
|
||||

|
||||
|
||||
Yeah. Nice. No audio.
|
||||
Browsing through the Arch forums I found , and I installed the three `lib32-alsa-plugins lib32-libpulse lib32-openal` packages.
|
||||
|
||||
And it started up! (too lazy to screenshot)
|
||||
|
||||
## Setting up the start script
|
||||
|
||||
edit a file in `~/.wineosu/osu/start.sh` with the following contents:
|
||||
```sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# props to Katoumegumi for the original script, this is exactly the same one and it works wonders.
|
||||
#export PATH="$HOME/.wineosu/osuwine/bin:$PATH" #Use custom WINE version to run osu!
|
||||
export WINEARCH=win32
|
||||
export WINEPREFIX="$HOME/.wineosu"
|
||||
#export WINEFSYNC=1
|
||||
|
||||
#VSync. For some reason, some people had been getting input latency issues and for some reason, the fix is to set VSync to off.
|
||||
export vblank_mode=0 #For AMD, Intel and others
|
||||
export __GL_SYNC_TO_VBLANK=0 #For NVIDIA proprietary and open source >=500
|
||||
|
||||
#export STAGING_AUDIO_PERIOD=10000
|
||||
|
||||
#start osu!
|
||||
wine osu\!.exe
|
||||
```
|
||||
|
||||
## Setting up the freedesktop entry
|
||||
|
||||
Download the osu! logo:
|
||||
```sh
|
||||
wget --output-document ~/.wineosu/osu/icon.png https://github.com/ppy/osu-wiki/raw/master/wiki/Brand_identity_guidelines/img/usage-full-colour.png
|
||||
```
|
||||
and finally edit a new file in the path `~/.local/share/applications/osu.desktop`
|
||||
```
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Comment=A free-to-play rhythm game inspired in Osu! Tataekae! Ouendan!
|
||||
Icon=/home/<username>/.wineosu/osu/icon.png
|
||||
Exec=/home/<username>/.wineosu/osu/start.sh
|
||||
Path=/home/<username>/.wineosu/osu
|
||||
GenericName=osu!
|
||||
Name=osu!
|
||||
StartupNotify=true
|
||||
```
|
||||
|
||||
# that's it
|
||||
|
||||
Way harder than I thought. If you want the command with all dependencies, type in:
|
||||
```sh
|
||||
sudo pacman -S lib32-gnutls lib32-mesa lib32-alsa-plugins lib32-libpulse lib32-openal
|
||||
```
|
||||
@@ -1,223 +0,0 @@
|
||||
---
|
||||
id: 2
|
||||
title: My tales of MongoDB migration
|
||||
description: Here I ramble about the last service migration I did, MongoDB, and all the difficulties that came with it.
|
||||
date: 2023-11-12T00:00:00Z
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
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.
|
||||
|
||||
I've been using MongoDB for a while now and I've been using it for a few things, like my discord bots, [webhooks-ui](https://github.com/SrIzan10/webhooks-ui) and probably other projects I don't remember right now.
|
||||
|
||||
So, let's get started!
|
||||
|
||||
## Testing the plan
|
||||
|
||||
My database instance is on Docker with a replica set of 1 node (itself) so [Prisma](https://www.prisma.io/) works.
|
||||
|
||||
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.
|
||||
|
||||
I first created 2 docker containers on my [main Ryzen machine](https://gist.github.com/SrIzan10/50bc2ba689a4cc43bcbac2799cc733c9)'s WSL Ubuntu instance.
|
||||
|
||||
I created a `docker-compose.yml` file with the following content:
|
||||
|
||||
```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:
|
||||
```
|
||||
|
||||
and ran it with `docker compose up -d`.
|
||||
|
||||
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 `mongosh` and tried to connect with that.
|
||||
|
||||
```bash
|
||||
$ mongosh mongodb://localhost:27017
|
||||
```
|
||||
...and it worked! That didn't make any sense, but okay, we can work with it.
|
||||
|
||||
I then connected to the `mongo1` instance and ran the following commands:
|
||||
|
||||
```bash
|
||||
> rs.initiate()
|
||||
```
|
||||
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):
|
||||
|
||||
```bash
|
||||
docker exec mongo1 sh -c "rm /bin/ping;apt update;apt install inetutils-ping -y;ping mongo2"
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
It worked, so I went ahead and added the second database to the replica set:
|
||||
|
||||
```bash
|
||||
> rs.add("mongo2")
|
||||
```
|
||||
|
||||
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):
|
||||
|
||||
```bash
|
||||
> use test
|
||||
> db.createCollection("test")
|
||||
> db.test.insertOne({ name: "test" })
|
||||
```
|
||||
|
||||
and then, let's check if it's on the second replica (mongo2):
|
||||
|
||||
```bash
|
||||
$ mongosh mongodb://localhost:27017
|
||||
```
|
||||
|
||||
```bash
|
||||
> use test
|
||||
> db.getMongo().setReadPref("secondaryPreferred")
|
||||
> db.test.find()
|
||||
```
|
||||
and, yeah, that worked.
|
||||
|
||||
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.
|
||||
So, to promote I connected to the primary replica (mongo1) and ran the following command:
|
||||
|
||||
```bash
|
||||
> rs.stepDown()
|
||||
```
|
||||
And that worked! Woo! The second replica is now the primary one. We can now start *drum rolls please*:
|
||||
|
||||
## The migration
|
||||
|
||||
This is it. We're doing it.
|
||||
|
||||
I went ahead and created a new docker-compose file on my server with the following content:
|
||||
|
||||
```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
|
||||
```
|
||||
After deploying the stack, I connected using mongosh to the primary db and ran the following command:
|
||||
```bash
|
||||
> rs.add("ip")
|
||||
```
|
||||
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:
|
||||
```bash
|
||||
> db.getMongo().setReadPref("secondaryPreferred")
|
||||
```
|
||||
and let's just let the results speak for themselves:
|
||||
```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>
|
||||
```
|
||||
Nice. let's now try to write something to the database from Vinci:
|
||||

|
||||
That just worked and we can see it on the secondary replica:
|
||||
```bash
|
||||
rs0 [direct: secondary] vinci> db.afk.find({ id: '703974042700611634' })
|
||||
[
|
||||
{
|
||||
_id: ObjectId("6550eccc6154a8c9030fe76a"),
|
||||
id: '703974042700611634',
|
||||
reason: 'test',
|
||||
__v: 0
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
I really hope that doesn't make my server run out of ram, because I'm really short on that.
|
||||
|
||||
After executing all the commands I `rs.stepDown()`'ed the primary Raspberry Pi replica and, as expected, the HP Server took over.
|
||||
|
||||
The last command of the day:
|
||||
```bash
|
||||
> rs.remove("ip")
|
||||
```
|
||||
|
||||
...SIKE! I needed to check the logs of the containers to see if everything was working fine. The `api` and `vinci` to be exact.
|
||||
This is because `api` runs Prisma and `vinci` runs the now defunct in my stack, [mongoose](https://mongoosejs.com/).
|
||||
|
||||
Luckily enough, both were fine, so I was free. Yay!
|
||||
|
||||
## Conclusion
|
||||
|
||||
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.
|
||||
Could I have done it in less time? Yes.
|
||||
Was I lazy? Also yes.
|
||||
|
||||
So that answers all your questions.
|
||||
|
||||
I hope you enjoyed this my first blog post, and thankfully it was a big one.
|
||||
This took 3 hours in total, but at the end of the day, it was worth it.
|
||||
|
||||
I'll see you in the next one!
|
||||
@@ -1,67 +0,0 @@
|
||||
import { glob } from 'glob'
|
||||
import * as fs from 'node:fs'
|
||||
import gm from 'gray-matter'
|
||||
import { Feed } from 'feed'
|
||||
import dayjs from 'dayjs'
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat.js";
|
||||
import { marked } from "marked";
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const feed = new Feed({
|
||||
title: 'Sr Izan\'s Blog',
|
||||
description: 'My little donowall place on the net',
|
||||
id: 'https://srizan.dev/blog',
|
||||
link: 'https://srizan.dev/blog',
|
||||
language: 'en',
|
||||
image: 'https://srizan.dev/pfp.png',
|
||||
favicon: 'https://srizan.dev/pfp.png',
|
||||
copyright: 'Copyleft 2023, Sr Izan',
|
||||
feedLinks: {
|
||||
json: 'https://srizan.dev/blog/feed.json',
|
||||
atom: 'https://srizan.dev/blog/atom.xml',
|
||||
rss: 'https://srizan.dev/blog/rss.xml'
|
||||
},
|
||||
author: {
|
||||
link: 'https://srizan.dev',
|
||||
name: 'Sr Izan',
|
||||
email: 'izan@srizan.dev'
|
||||
}
|
||||
})
|
||||
|
||||
const data = []
|
||||
|
||||
await glob('./src/blog/**/*.md').then(async (files) => {
|
||||
for (const file of files) {
|
||||
const readFile = fs.readFileSync(file)
|
||||
const dt = gm(readFile).data
|
||||
const fileContent = gm(readFile).content
|
||||
if (dt.draft) return
|
||||
|
||||
dt.fileContent = fileContent
|
||||
dt.fileName = file.replace('src/blog/', '')
|
||||
console.log(`File ${dt.fileName} read successfully`)
|
||||
data.push(dt)
|
||||
|
||||
feed.addItem({
|
||||
title: dt.title,
|
||||
id: `https://srizan.dev/blog/${dt.id}`,
|
||||
link: `https://srizan.dev/blog/${dt.id}`,
|
||||
description: dt.description,
|
||||
content: marked.parse(fileContent),
|
||||
date: dayjs(dt.date).toDate(),
|
||||
author: [
|
||||
{
|
||||
name: 'Sr Izan',
|
||||
link: 'https://srizan.dev'
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
data.sort((a, b) => b.id - a.id);
|
||||
|
||||
fs.writeFileSync('./src/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())
|
||||
46
src/components/BaseHead.astro
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
// Import the global.css file here so that it is included on
|
||||
// all pages through the use of the <BaseHead /> component.
|
||||
import '@/styles/global.css';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
|
||||
const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props;
|
||||
---
|
||||
|
||||
<!-- Global Metadata -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<!-- Font preloads -->
|
||||
<link rel="preload" href="https://fonts.srizan.dev/satoshi/fonts/Satoshi-Medium.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title} />
|
||||
<meta name="description" content={description} />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={new URL(image, Astro.url)} />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={Astro.url} />
|
||||
<meta property="twitter:title" content={title} />
|
||||
<meta property="twitter:description" content={description} />
|
||||
<meta property="twitter:image" content={new URL(image, Astro.url)} />
|
||||
17
src/components/FormattedDate.astro
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
interface Props {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
const { date } = Astro.props;
|
||||
---
|
||||
|
||||
<time datetime={date.toISOString()}>
|
||||
{
|
||||
date.toLocaleDateString('en-us', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
}
|
||||
</time>
|
||||
31
src/components/Header.astro
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
import { SITE_TITLE } from '../consts';
|
||||
import { ModeToggle } from '@/components/ui/modetoggle';
|
||||
---
|
||||
|
||||
<script is:inline>
|
||||
const getThemePreference = () => {
|
||||
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
|
||||
return localStorage.getItem('theme');
|
||||
}
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
};
|
||||
const isDark = getThemePreference() === 'dark';
|
||||
document.documentElement.classList[isDark ? 'add' : 'remove']('dark');
|
||||
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const observer = new MutationObserver(() => {
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
});
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex items-center fixed gap-4 p-2 bg-slate-300/35 dark:bg-slate-500/20 backdrop-blur-md w-full sm:w-auto">
|
||||
<a href="/" class="font-bold text-lg">{ SITE_TITLE }</a>
|
||||
<a href="/blog">Blog</a>
|
||||
<a href="https://jp.srizan.dev">日本語</a>
|
||||
<div class="flex-1" />
|
||||
<ModeToggle client:load />
|
||||
</div>
|
||||
24
src/components/ImageAuthor.astro
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
import { FaExternalLinkAlt } from 'react-icons/fa';
|
||||
import '@/styles/global.css'
|
||||
type Props = {
|
||||
url: string;
|
||||
author: string;
|
||||
description: string;
|
||||
location: string;
|
||||
}
|
||||
|
||||
const { url, author, description, location } = Astro.props;
|
||||
---
|
||||
|
||||
<style>
|
||||
p {
|
||||
@apply mr-[0.2em] my-[0.1em] first:ml-[0.2em];
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class='absolute bottom-0 right-0 text-[0.8em] text-white bg-black opacity-40 select-none flex transition-opacity duration-150 ease-in-out hover:opacity-100'>
|
||||
<a href={location} target="_blank"><p>{description}</p></a>
|
||||
<p>by {author}</p>
|
||||
<a href={url} target="_blank"><FaExternalLinkAlt /></a>
|
||||
</div>
|
||||
56
src/components/ui/button.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
198
src/components/ui/dropdown-menu.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
|
||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
|
||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
|
||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
))
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
}
|
||||
52
src/components/ui/modetoggle.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import * as React from "react"
|
||||
import { Moon, Sun } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
|
||||
export function ModeToggle() {
|
||||
const [theme, setThemeState] = React.useState<
|
||||
"theme-light" | "dark" | "system"
|
||||
>("theme-light")
|
||||
|
||||
React.useEffect(() => {
|
||||
const isDarkMode = document.documentElement.classList.contains("dark")
|
||||
setThemeState(isDarkMode ? "dark" : "theme-light")
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
const isDark =
|
||||
theme === "dark" ||
|
||||
(theme === "system" &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
document.documentElement.classList[isDark ? "add" : "remove"]("dark")
|
||||
}, [theme])
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setThemeState("theme-light")}>
|
||||
Light
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setThemeState("dark")}>
|
||||
Dark
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setThemeState("system")}>
|
||||
System
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
5
src/consts.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// Place any global data in this file.
|
||||
// You can import this data from anywhere in your site by using the `import` keyword.
|
||||
|
||||
export const SITE_TITLE = 'Sr Izan\'s website';
|
||||
export const SITE_DESCRIPTION = 'Welcome to my website!';
|
||||
6
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
interface ImportMetaEnv {
|
||||
readonly BLOG_CONTENT_KEY: string;
|
||||
readonly BLOG_CONTENT_URL: string;
|
||||
}
|
||||
52
src/layouts/BlogPost.astro
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
import BaseHead from '../components/BaseHead.astro'
|
||||
import Header from '../components/Header.astro'
|
||||
import FormattedDate from '../components/FormattedDate.astro'
|
||||
import type { PostOrPage } from '@tryghost/content-api'
|
||||
import { Image } from 'astro:assets'
|
||||
|
||||
type Props = PostOrPage
|
||||
|
||||
const { title, excerpt: description, published_at, updated_at, feature_image, feature_image_caption, feature_image_alt } = Astro.props
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={title!} description={description || '(no description)'} />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Header />
|
||||
<main class="w-full max-w-full mx-0">
|
||||
<article class="my-16 sm:my-0">
|
||||
<div class="mb-4 pt-4 text-sm leading-none">
|
||||
|
||||
<h1 class="m-0 mb-2 text-center text-4xl font-bold">{title}</h1>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="mb-4">
|
||||
{feature_image && <Image class="shadow" width={720} height={200} src={feature_image} alt={feature_image_alt || ''} />}
|
||||
</div>
|
||||
{feature_image_caption && <span class="text-left text-sm italic ml-4" set:html={feature_image_caption} />}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="prose dark:prose-invert prose-code:dark:text-black w-prose max-w-[calc(100%-2em)] mx-auto p-2">
|
||||
<div class="mb-2 text-gray-500">
|
||||
<FormattedDate date={new Date(published_at!)} />
|
||||
{
|
||||
updated_at && (
|
||||
<div class="italic">
|
||||
Last updated on <FormattedDate date={new Date(updated_at)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
8
src/lib/ghost.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import GhostContentAPI from '@tryghost/content-api';
|
||||
|
||||
// Create API instance with site credentials
|
||||
export const ghostClient = new GhostContentAPI({
|
||||
url: import.meta.env.BLOG_URL, // This is the default URL if your site is running on a local environment
|
||||
key: import.meta.env.BLOG_CONTENT_KEY,
|
||||
version: 'v5.82',
|
||||
});
|
||||
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
41
src/pages/blog/[...slug].astro
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
/* import { type CollectionEntry, getCollection } from 'astro:content
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('blog')
|
||||
return posts.map((post) => ({
|
||||
params: { slug: post.slug },
|
||||
props: post,
|
||||
}))
|
||||
}
|
||||
type Props = CollectionEntry<'blog'>
|
||||
|
||||
const rawHTMLString = "Hello <strong>World</strong>"
|
||||
|
||||
const post = Astro.props
|
||||
const { Content } = await post.render() */
|
||||
|
||||
import BlogPost from '../../layouts/BlogPost.astro'
|
||||
import { ghostClient } from '../../lib/ghost';
|
||||
export async function getStaticPaths() {
|
||||
const posts = await ghostClient.posts
|
||||
.browse({
|
||||
limit: 'all',
|
||||
});
|
||||
return posts.map((post) => {
|
||||
return {
|
||||
params: {
|
||||
slug: post.slug,
|
||||
},
|
||||
props: {
|
||||
post: post,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
const { post } = Astro.props;
|
||||
---
|
||||
|
||||
<BlogPost {...post}>
|
||||
<Fragment set:html={post.html} />
|
||||
</BlogPost>
|
||||
46
src/pages/blog/index.astro
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
import BaseHead from '../../components/BaseHead.astro'
|
||||
import Header from '../../components/Header.astro'
|
||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts'
|
||||
import FormattedDate from '../../components/FormattedDate.astro'
|
||||
import { Image } from 'astro:assets'
|
||||
import { ghostClient } from '@/lib/ghost'
|
||||
|
||||
const posts = await ghostClient.posts.browse({
|
||||
limit: 'all',
|
||||
})
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
|
||||
<style is:inline>
|
||||
[data-astro-code-mark] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<Header />
|
||||
<main>
|
||||
<section class="p-12">
|
||||
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
{
|
||||
posts.map((post) => (
|
||||
<li class="flex flex-col items-center sm:items-start">
|
||||
<a href={`/blog/${post.slug}/`} class="hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors duration-200">
|
||||
<Image class="rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200" width={720} height={360} src={post.feature_image || 'https://images.unsplash.com/photo-1519681393784-d120267933ba'} alt="" loading="lazy" />
|
||||
<h4 class="text-xl font-bold mt-2">{post.title}</h4>
|
||||
<p class="mt-1">
|
||||
<FormattedDate date={new Date(post.published_at!)} />
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
39
src/pages/index.astro
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
import BaseHead from '../components/BaseHead.astro';
|
||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
|
||||
import Backgrounds from '../backgrounds.json';
|
||||
import { Image } from 'astro:assets';
|
||||
import ImageAuthor from '@/components/ImageAuthor.astro';
|
||||
import { FaGithub, FaBlog, FaDiscord, FaMastodon, FaTwitter } from 'react-icons/fa';
|
||||
import { SiOsu } from 'react-icons/si';
|
||||
|
||||
const randomBackground = Backgrounds[Math.floor(Math.random() * Backgrounds.length)];
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
|
||||
<style>
|
||||
svg {
|
||||
@apply w-6 h-6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="relative">
|
||||
<div class={`w-screen h-screen z-[-1] bg-fixed bg-center bg-no-repeat bg-cover rounded-none object-cover brightness-[30%]`} style={`background-image: url('${randomBackground.url}')`}/>
|
||||
<ImageAuthor {...randomBackground} />
|
||||
<div class="w-[480px] h-[360px] absolute -translate-x-2/4 -translate-y-2/4 text-center overflow-visible left-2/4 top-2/4 text-white flex justify-center items-center flex-col gap-5">
|
||||
<Image src='/pfp.webp' alt={''} width='200' height='200' class='rounded-full' />
|
||||
<p>Hobbyist developer & <a href={'https://sern.dev'}>sern</a> lead developer team member</p>
|
||||
<div class='flex gap-5 justify-center'>
|
||||
<a href='https://github.com/SrIzan10'><FaGithub /></a>
|
||||
<a href='/blog'><FaBlog /></a>
|
||||
<a href='https://discord.com/users/703974042700611634'><FaDiscord /></a>
|
||||
<a href='https://social.kalico.moe/@srizan'><FaMastodon /></a>
|
||||
<a href='https://twitter.com/itssrizan'><FaTwitter /></a>
|
||||
<a href='https://osu.ppy.sh/users/25350735'><SiOsu /></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
16
src/pages/rss.xml.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import rss from '@astrojs/rss';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
|
||||
|
||||
export async function GET(context) {
|
||||
const posts = await getCollection('blog');
|
||||
return rss({
|
||||
title: SITE_TITLE,
|
||||
description: SITE_DESCRIPTION,
|
||||
site: context.site,
|
||||
items: posts.map((post) => ({
|
||||
...post.data,
|
||||
link: `/blog/${post.slug}/`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
182
src/styles/global.css
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
The CSS in this style tag is based off of Bear Blog's default CSS.
|
||||
https://github.com/HermanMartinus/bearblog/blob/297026a877bc2ab2b3bdfbd6b9f7961c350917dd/templates/styles/blog/default.css
|
||||
License MIT: https://github.com/HermanMartinus/bearblog/blob/master/LICENSE.md
|
||||
*/
|
||||
@import url('https://fonts.srizan.dev/satoshi.css');
|
||||
|
||||
:root {
|
||||
--accent: #2337ff;
|
||||
--accent-dark: #000d8a;
|
||||
--black: 15, 18, 25;
|
||||
--gray: 96, 115, 159;
|
||||
--gray-light: 229, 233, 240;
|
||||
--gray-dark: 34, 41, 57;
|
||||
--gray-gradient: rgba(var(--gray-light), 50%), #fff;
|
||||
--box-shadow: 0 2px 6px rgba(var(--gray), 25%), 0 8px 24px rgba(var(--gray), 33%), 0 16px 32px rgba(var(--gray), 33%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
body {
|
||||
font-family: 'Satoshi-Medium', sans-serif !important;
|
||||
@apply text-left overflow-auto text-lg leading-relaxed bg-white dark:bg-gray-900 text-black dark:text-white;
|
||||
}
|
||||
|
||||
main {
|
||||
@apply mx-auto px-4 py-16 w-full max-w-7xl;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-6xl mb-2 leading-tight;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-5xl mb-2 leading-snug;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-4xl mb-2 leading-normal;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@apply text-3xl mb-2 leading-normal;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@apply text-2xl mb-2 leading-normal;
|
||||
}
|
||||
h6 {
|
||||
@apply text-xl mb-2 leading-normal;
|
||||
}
|
||||
|
||||
strong,
|
||||
b {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply !text-blue-500;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
@apply !text-blue-700;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@apply w-full text-base;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
table {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
img {
|
||||
@apply max-w-full h-auto rounded-lg;
|
||||
}
|
||||
|
||||
code {
|
||||
@apply px-1 py-0.5 bg-gray-200 rounded-sm;
|
||||
}
|
||||
|
||||
pre {
|
||||
@apply p-6 rounded-lg;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
@apply border-l-4 border-blue-600 pl-5 text-xl;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply border-none border-t border-gray-200;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
body {
|
||||
@apply text-base;
|
||||
}
|
||||
main {
|
||||
@apply p-4;
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
75
tailwind.config.mjs
Normal file
@@ -0,0 +1,75 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: ["class"],
|
||||
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
|
||||
prefix: "",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
width: {
|
||||
'prose': '720px'
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate"), require('@tailwindcss/typography')],
|
||||
}
|
||||
@@ -1,27 +1,14 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"strictNullChecks": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
}
|
||||
}
|
||||