Compare commits

..

5 Commits

Author SHA1 Message Date
muszyn 7ff55c4679 fix: fetch was not using rest requests 2025-07-04 12:15:32 -04:00
muszyn a41d1ceaea feat: public self-hosting 2025-07-04 10:04:28 -04:00
muszyn 60c9cec7aa feat: fix vite url using env var 2025-06-25 20:56:16 -04:00
muszyn c0eae42daf feat: expand cors 2025-06-25 19:57:00 -04:00
muszyn afe6c08101 feat(aboutme): update photos and blurb 2025-06-24 21:54:59 -04:00
15 changed files with 95 additions and 63 deletions
+2
View File
@@ -26,3 +26,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.env
+14 -2
View File
@@ -1,5 +1,6 @@
from fastapi import FastAPI, Depends, HTTPException, status from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import Optional from typing import Optional
@@ -10,11 +11,22 @@ from .database import SessionLocal, engine, Base
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
app = FastAPI() app = FastAPI(proxy_headers=True)
app.add_middleware(
TrustedHostMiddleware, allowed_hosts=["site-api.muszyn.dev", "*.muszyn.dev"]
)
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["http://localhost:3000", "https://localhost:3000"], allow_origins=[
"http://localhost:3000",
"http://localhost:8000",
"http://192.168.125.129:3000",
"https://192.168.125.129:3000",
"http://192.168.125.129:8000",
"https://192.168.125.129:8000",
"https://site.muszyn.dev",
],
allow_credentials=True, allow_credentials=True,
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
+5 -3
View File
@@ -1,5 +1,3 @@
version: '3.8'
services: services:
db: db:
image: postgres:15 image: postgres:15
@@ -37,7 +35,11 @@ services:
- app-network - app-network
frontend: frontend:
build: ./frontend build:
context: ./frontend
args:
VITE_API_URL: ${API_URL}
ports: ports:
- "3000:80" - "3000:80"
depends_on: depends_on:
+13 -6
View File
@@ -1,15 +1,22 @@
# Build stage FROM node:20 AS builder
FROM node:20 AS build
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install
COPY . . COPY . .
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL
RUN npm run build RUN npm run build
# Production stage # Stage 2: Serve with NGINX
FROM nginx:alpine FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY --from=build /app/dist /usr/share/nginx/html # Clean out default config
RUN rm /etc/nginx/conf.d/default.conf
# Copy your custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built files from Vite
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80 EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+2 -19
View File
@@ -1,28 +1,11 @@
worker_processes 1;
events { worker_connections 1024; }
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server { server {
listen 80; listen 80;
server_name _; server_name localhost;
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;
location / { location / {
# try to serve file directly, otherwise fallback to index.html try_files $uri /index.html;
try_files $uri $uri/ /index.html;
}
# optional: block .git, .env, etc
location ~ /\.(?!well-known).* {
deny all;
}
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

+34 -24
View File
@@ -5,6 +5,9 @@ import React, {
useEffect, useEffect,
useRef, useRef,
} from "react"; } from "react";
import cruiseImg from "./images/cruise.jpg";
import discGolfImg from "./images/disc_golf_ace.jpg";
import odinImg from "./images/odin.jpg";
import { import {
BrowserRouter as Router, BrowserRouter as Router,
Routes, Routes,
@@ -28,9 +31,7 @@ import {
} from "react-icons/fa"; } from "react-icons/fa";
import type { Blog } from "./utils/types"; import type { Blog } from "./utils/types";
import { countWords } from "./utils/countWords"; import { countWords } from "./utils/countWords";
import { API_URL } from "./utils/constants";
// Base API URL from env
const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8000";
// Auth Context // Auth Context
interface AuthContextProps { interface AuthContextProps {
@@ -82,7 +83,12 @@ function BlogPage() {
const [blogs, setBlogs] = useState<Blog[]>([]); const [blogs, setBlogs] = useState<Blog[]>([]);
useEffect(() => { useEffect(() => {
fetch(`${API_URL}/blogs`) fetch(`${API_URL}/blogs`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.json()) .then((res) => res.json())
.then((data: Blog[]) => setBlogs(data)) .then((data: Blog[]) => setBlogs(data))
.catch((err) => console.error(err)); .catch((err) => console.error(err));
@@ -418,32 +424,36 @@ function LandingPage() {
function About() { function About() {
return ( return (
<div className="py-10"> <div className="py-10">
<div className="flex justify-center items-end space-x-4"> <div className="flex justify-center items-end space-x-4 flex-wrap">
{[cruiseImg, discGolfImg, odinImg].map((src, i) => (
<img <img
src="/images/photo1.jpg" key={i}
alt="Photo 1" src={src}
className="w-24 h-24 rounded-full" alt={["Cruise", "Disc golf ace", "Odin"][i]}
/> className="
<img rounded-full
src="/images/photo2.jpg" aspect-square
alt="Photo 2" object-cover
className="w-24 h-24 rounded-full transform translate-y-4" w-1/3 max-w-[120px] /* small screens: 33% of container, up to 120px */
/> sm:w-1/4 sm:max-w-[150px] /* ≥640px: 25% up to 150px */
<img md:w-1/6 md:max-w-[200px] /* ≥768px: ~16% up to 200px */
src="/images/photo3.jpg" lg:w-1/8 lg:max-w-[250px] /* ≥1024px: 12.5% up to 250px */
alt="Photo 3" "
className="w-24 h-24 rounded-full"
/> />
))}
</div> </div>
<div className="text-center mt-8 px-4"> <div className="text-center mt-8 px-4">
<h2 className="text-3xl font-bold mb-4">About Me</h2> <h2 className="text-3xl font-bold mb-4">About Me</h2>
<p className="text-lg text-gray-700 dark:text-gray-300 max-w-2xl mx-auto"> <p className="text-lg text-gray-700 dark:text-gray-300 max-w-2xl mx-auto">
I am a software engineer at Whisker who designs and implements Im Alex, a full-stack engineer at Whisker, working on backend one
whatever is highest priority. I work with every team in the minute, mobile tweaks the next, and firmware the day after. Learning
engineering organization to coordinate mission-critical projects new tech is something I'm passionate about, and Im all about
across backend, mobile, and firmware. Im comfortable in many tech spreading that knowledge fast. Off-duty youll catch me reading a
stacks and learn new ones quicklynever afraid to jump in the deep end bunch, writing (ironically, on my blog, which you are currently
and adapt on the fly. viewing), exploring the outdoors with my dogs (and my girlfriend,
trust me shes real!), or rolling dice as a D&amp;D sorcerer. Here
youll find book thoughts, code experiments, and random shower
thoughts. Thanks for stopping by!
</p> </p>
</div> </div>
<div className="mt-8 flex flex-col items-center space-y-2"> <div className="mt-8 flex flex-col items-center space-y-2">
Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

+7 -1
View File
@@ -1,9 +1,15 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { API_URL } from "./constants";
export function BlogList() { export function BlogList() {
const [content, setContent] = useState(""); const [content, setContent] = useState("");
useEffect(() => { useEffect(() => {
fetch(`localhost:8000/get-blogs`) fetch(`${API_URL}/get-blogs`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.text()) .then((res) => res.text())
.then(setContent); .then(setContent);
}, []); }, []);
+6 -1
View File
@@ -16,7 +16,12 @@ export function BlogViewer() {
useEffect(() => { useEffect(() => {
if (!slug) return; if (!slug) return;
fetch(`${API_URL}/blogs/${slug}`) fetch(`${API_URL}/blogs/${slug}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.json()) .then((res) => res.json())
.then((data: Blog) => setBlog(data)) .then((data: Blog) => setBlog(data))
.catch((err) => console.error(err)); .catch((err) => console.error(err));
+6 -1
View File
@@ -16,7 +16,12 @@ export function EditBlog() {
useEffect(() => { useEffect(() => {
if (!slug) return; if (!slug) return;
fetch(`${API_URL}/blogs/${slug}`) fetch(`${API_URL}/blogs/${slug}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((res) => res.json()) .then((res) => res.json())
.then((data: Blog) => { .then((data: Blog) => {
setBlog(data); setBlog(data);