Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ The app which fulfills the following requirements for a
- Deployment of a productive version with precompiled frontend server
- Deployment to Azure App Service Free Plan with Actions on Pull Request
- Handle Front and Backend routing in FastAPI both together
- Handle 404 page
- A frame for further development with server side Authentication

## Prerequisites
Here are the prerequisites that you have to install before running the app
Expand Down
6 changes: 6 additions & 0 deletions backend/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from fastapi import APIRouter

api_router = APIRouter()
@api_router.get("/data")
def read_hello():
return {"message": "Hello from API"}
16 changes: 16 additions & 0 deletions backend/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from fastapi import APIRouter
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

class LoginItem(BaseModel):
message: str

auth_router = APIRouter()
@auth_router.post("/login")
async def login(loginitem: LoginItem):
data = jsonable_encoder(loginitem)
return {"token": "ABCDEF", "data": data}

@auth_router.post("/logout")
async def login():
return {"message": 'Loggout ok'}
11 changes: 6 additions & 5 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, RedirectResponse
from pathlib import Path
from auth import auth_router
from api import api_router
# Init FastAPI
app = FastAPI()
origins = ["http://localhost:5173", "localhost:5173"]
app.add_middleware(CORSMiddleware,allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"])

# API Router
api_router = APIRouter()
@api_router.get("/hello")
def read_hello():
return {"message": "Hello from API"}
# Register Auth Router
app.include_router(auth_router, prefix="/auth")

# Register API Router
app.include_router(api_router, prefix="/api")

# Frontend Router
Expand Down
4 changes: 2 additions & 2 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>EntraID-Web-Auth-Reference</title>
<title>FastAPI-Reference</title>
</head>
<body>
<div id="root"></div>
Expand Down
Binary file added frontend/public/favicon.ico
Binary file not shown.
775 changes: 775 additions & 0 deletions frontend/public/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion frontend/public/vite.svg

This file was deleted.

10 changes: 10 additions & 0 deletions frontend/src/404.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Link } from "react-router-dom";
export default function NotFound() {
return (
<>
<h1>404</h1>
<Link to='/'>Goto Home</Link>
</>
);
}

7 changes: 5 additions & 2 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { useState, useEffect } from 'react'
import { Routes, Route } from "react-router-dom";
import Login from './components/Login';
import { RequireToken, setToken } from "./components/auth";
import Login from './Login';
import Home from './pages/Home';
import NotFound from './404';
import './App.css'

function App() {
return (
<>
<div className ="App">
<Routes>
<Route path="/" element = {<Home/>}/>
<Route path="/" element = {<RequireToken><Home/></RequireToken>}/>
<Route path="/login" element = {<Login/>}/>
<Route path="*" element = {<NotFound/>}/>
</Routes>
</div>
</>
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/Login.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useNavigate } from "react-router";
import { Link } from "react-router-dom";
import { fetchToken, login, logout } from "./components/auth";
import logo from './assets/logo.png'
export default function Login() {
const navigate = useNavigate();
const token = fetchToken();

const loginFunc = async () => {
await login();
navigate('/');
}

return (
<>
<div>
<a href="https://github.com/kstrassheim/fastapi-reference" target="_blank">
<img src={logo} className="logo " alt="logo" />
</a>
</div>
<h1>Login to FastAPI-Reference</h1>
<p>Just press the button to automatically logon without any forms</p>
{
token ? (<>
<p>you are logged in</p>
<Link to='/'>Goto Home</Link>
</>
)
:
(
<button onClick={loginFunc}>Login</button>
)
}
</>
);
}

Binary file added frontend/src/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion frontend/src/assets/react.svg

This file was deleted.

10 changes: 0 additions & 10 deletions frontend/src/components/Login.jsx

This file was deleted.

14 changes: 14 additions & 0 deletions frontend/src/components/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const backendUrl = import.meta.env.MODE === 'production' ? '': 'http://localhost:8000';

export const apiHello = async () => {
try {
const response = await fetch(`${backendUrl}/api/data`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
}
};
75 changes: 75 additions & 0 deletions frontend/src/components/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom"
import { backendUrl } from "./api"

export const setToken = (token) => {
console.log(token, "setToken");
localStorage.setItem('mytoken', token) // make up your own token
}

export const clearToken = () => {
localStorage.removeItem('mytoken');
};

export const fetchToken = () => {
let token = localStorage.getItem('mytoken')
console.log(token, "fetchToken");
return token
}

export const RequireToken = ({children}) =>{
const auth = fetchToken()
const location = useLocation()
const navigate = useNavigate();

useEffect(() => {
if (!auth) {
navigate("/login", { state: { from: location } });
}
}, [auth, navigate, location]);

return children;
};

export const login = async () => {
try {
//const navigate = useNavigate();
const response = await fetch(`${backendUrl}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: "login attempt" }),
});

if (!response.ok) { throw new Error("Network response was not ok"); }

const data = await response.json();
console.log(data.token, "data.token");
if (data.token) {
setToken(data.token);
//navigate("/");
}
} catch (error) {
console.error("Error during login:", error);
}
};


export const logout = async () => {
try {
//const navigate = useNavigate();
const response = await fetch(`${backendUrl}/auth/logout`, {
method: "POST",
headers: { "Content-Type": "application/json" }
});

if (!response.ok) { throw new Error("Network response was not ok"); }

const data = await response.json();
console.log(data.message);
clearToken();
//navigate("/");
} catch (error) {
console.error("Error during logout:", error);
}
};

4 changes: 2 additions & 2 deletions frontend/src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ createRoot(document.getElementById('root')).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>,
</StrictMode>,
</BrowserRouter>
</StrictMode>
)
71 changes: 38 additions & 33 deletions frontend/src/pages/Home.jsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,64 @@
import { useState, useEffect } from 'react'
import reactLogo from '../assets/react.svg'
import { Link } from 'react-router-dom'
import logo from '../assets/logo.png'
import { Link, useNavigate } from 'react-router-dom'
import './Home.css'
import { apiHello } from '../components/api'
import { fetchToken, logout, login } from "../components/auth";

function Home() {
const [count, setCount] = useState(0)
const [hello, setHello] = useState(null)
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
const navigate = useNavigate();

const setCountFunc = () => {
setCount(count + 1);
}

const apiHost= import.meta.env.MODE === 'production' ? '': 'http://localhost:8000';
const logoutFunc = async () => {
await logout();
navigate('/');
}

const fetchData = async () => {
const data = await apiHello()
setData(data)
}

useEffect(() => {
// Fetch JSON data from the API endpoint
fetch(`${apiHost}/api/hello`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => setHello(data))
.catch(error => console.error('Error fetching data:', error));
}, []); // Empty dependency array runs this effect once on mount
fetchData();
}, []);

return (
<>
<div>
{/* <a href="https://vite.dev" target="_blank">W
<img src={viteLogo} className="logo" alt="Vite logo" />
</a> */}
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
<a href="https://github.com/kstrassheim/fastapi-reference" target="_blank">
<img src={logo} className="logo " alt="logo" />
</a>
</div>
<h1>Vite + React</h1>
<h1>FastAPI-Reference</h1>
<div className="login-link">
<Link to="/login">Go to Login</Link>
{
fetchToken() ? (<>
<p>you are logged in</p>
<button onClick={logoutFunc}>Logout</button>
</>
)
:
<></>
}
</div>
<div className="card">
<button onClick={setCountFunc}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
<p className="read-the-docs">
Click on the Logo to learn more
</p>
<p>{import.meta.env.MODE}</p>
<h2>
{data ? data.message : 'Loading...'}
</h2>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
<p>{import.meta.env.MODE}</p>
<h2>
{hello ? hello.message : 'Loading...'}
</h2>
</>
)
}
Expand Down