Compare commits
5 Commits
6bda5876ba
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ff55c4679 | |||
| a41d1ceaea | |||
| 60c9cec7aa | |||
| c0eae42daf | |||
| afe6c08101 |
@@ -26,3 +26,5 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
.env
|
||||||
|
|||||||
+14
-2
@@ -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
@@ -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
@@ -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;"]
|
|
||||||
|
|||||||
+6
-23
@@ -1,28 +1,11 @@
|
|||||||
worker_processes 1;
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
events { worker_connections 1024; }
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
http {
|
|
||||||
include mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
sendfile on;
|
|
||||||
keepalive_timeout 65;
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
root /usr/share/nginx/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 |
+36
-26
@@ -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">
|
||||||
<img
|
{[cruiseImg, discGolfImg, odinImg].map((src, i) => (
|
||||||
src="/images/photo1.jpg"
|
<img
|
||||||
alt="Photo 1"
|
key={i}
|
||||||
className="w-24 h-24 rounded-full"
|
src={src}
|
||||||
/>
|
alt={["Cruise", "Disc golf ace", "Odin"][i]}
|
||||||
<img
|
className="
|
||||||
src="/images/photo2.jpg"
|
rounded-full
|
||||||
alt="Photo 2"
|
aspect-square
|
||||||
className="w-24 h-24 rounded-full transform translate-y-4"
|
object-cover
|
||||||
/>
|
w-1/3 max-w-[120px] /* small screens: 33% of container, up to 120px */
|
||||||
<img
|
sm:w-1/4 sm:max-w-[150px] /* ≥640px: 25% up to 150px */
|
||||||
src="/images/photo3.jpg"
|
md:w-1/6 md:max-w-[200px] /* ≥768px: ~16% up to 200px */
|
||||||
alt="Photo 3"
|
lg:w-1/8 lg:max-w-[250px] /* ≥1024px: 12.5% up to 250px */
|
||||||
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
|
I’m 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 I’m all about
|
||||||
across backend, mobile, and firmware. I’m comfortable in many tech
|
spreading that knowledge fast. Off-duty you’ll catch me reading a
|
||||||
stacks and learn new ones quickly—never 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 she’s real!), or rolling dice as a D&D sorcerer. Here
|
||||||
|
you’ll 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 |
@@ -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);
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user