mirror of
https://github.com/Xevion/linkpulse.git
synced 2025-12-16 10:12:18 -06:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5414ae80a8 | |||
| 2f7aea9d25 | |||
| 5d8621feb5 | |||
| f11d5af7cd |
@@ -1,5 +1,7 @@
|
||||
# linkpulse
|
||||
|
||||
   [](https://wakatime.com/badge/github/Xevion/linkpulse)
|
||||
|
||||
A project for monitoring websites, built with FastAPI and React.
|
||||
|
||||
## Structure
|
||||
|
||||
@@ -1,16 +1,42 @@
|
||||
"""Miscellaneous endpoints for the Linkpulse API."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import structlog
|
||||
import toml
|
||||
from fastapi import APIRouter
|
||||
from fastapi_cache.decorator import cache
|
||||
from linkpulse.utilities import get_db
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
db = get_db()
|
||||
|
||||
|
||||
@router.get("/api/version")
|
||||
@cache(expire=None)
|
||||
async def version() -> dict[str, str]:
|
||||
"""Get the version of the API.
|
||||
:return: The version of the API.
|
||||
:rtype: dict[str, str]
|
||||
"""
|
||||
|
||||
pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
|
||||
|
||||
version = "unknown"
|
||||
if pyproject_path.exists() and pyproject_path.is_file():
|
||||
data = toml.load(pyproject_path)
|
||||
version = data["tool"]["poetry"]["version"]
|
||||
logger.debug("Version loaded from pyproject.toml", version=version)
|
||||
else:
|
||||
version = "error"
|
||||
|
||||
return {"version": version}
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def health():
|
||||
"""An endpoint to check if the service is running.
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import re
|
||||
from linkpulse.utilities import utc_now
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from linkpulse.app import app
|
||||
|
||||
|
||||
def test_utcnow_tz_aware():
|
||||
dt = utc_now()
|
||||
dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
|
||||
|
||||
|
||||
def test_api_version():
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/api/version")
|
||||
assert response.status_code == 200
|
||||
assert "version" in response.json()
|
||||
|
||||
version = response.json()["version"]
|
||||
assert isinstance(version, str)
|
||||
assert re.match(r"^\d+\.\d+\.\d+$", version)
|
||||
|
||||
24
backend/poetry.lock
generated
24
backend/poetry.lock
generated
@@ -1626,6 +1626,17 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi
|
||||
tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"]
|
||||
typing = ["mypy (>=1.4)", "rich", "twisted"]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
files = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-peewee"
|
||||
version = "3.17.7.20241017"
|
||||
@@ -1659,6 +1670,17 @@ files = [
|
||||
{file = "types_pytz-2024.2.0.20241003-py3-none-any.whl", hash = "sha256:3e22df1336c0c6ad1d29163c8fda82736909eb977281cb823c57f8bae07118b7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-toml"
|
||||
version = "0.10.8.20240310"
|
||||
description = "Typing stubs for toml"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-toml-0.10.8.20240310.tar.gz", hash = "sha256:3d41501302972436a6b8b239c850b26689657e25281b48ff0ec06345b8830331"},
|
||||
{file = "types_toml-0.10.8.20240310-py3-none-any.whl", hash = "sha256:627b47775d25fa29977d9c70dc0cbab3f314f32c8d8d0c012f2ef5de7aaec05d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
@@ -1840,4 +1862,4 @@ h11 = ">=0.9.0,<1"
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "9dff2a47bb95e65f15616bb82926eb81d418456c5ec60db46dfe06056c643e31"
|
||||
content-hash = "4e094b6d46fab5f7886452b22c30fc520d396c6d77f9f92dfbd4ef36257c10df"
|
||||
|
||||
@@ -5,7 +5,7 @@ description = ""
|
||||
authors = ["Xevion <xevion@xevion.dev>"]
|
||||
license = "GNU GPL v3"
|
||||
readme = "README.md"
|
||||
package-mode = false
|
||||
package-mode = true
|
||||
|
||||
[tool.poetry.scripts]
|
||||
app = "linkpulse"
|
||||
@@ -32,6 +32,8 @@ pwdlib = {extras = ["argon2"], version = "^0.2.1"}
|
||||
pytest-xdist = "^3.6.1"
|
||||
email-validator = "^2.2.0"
|
||||
limits = "^3.13.0"
|
||||
toml = "^0.10.2"
|
||||
types-toml = "^0.10.8.20240310"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
||||
@@ -8,10 +8,6 @@ import { useUserStore } from "@/lib/state";
|
||||
// If any protected API call suddenly fails with a 401 status code, the user store is reset, a logout message is displayed, and the user is redirected to the login page.
|
||||
// All redirects to the login page will carry a masked URL parameter that can be used to redirect the user back to the page they were on after logging in.
|
||||
|
||||
const TARGET = !import.meta.env.DEV
|
||||
? `http://${import.meta.env.VITE_BACKEND_TARGET}`
|
||||
: "";
|
||||
|
||||
type ErrorResponse = {
|
||||
detail: string;
|
||||
};
|
||||
@@ -31,7 +27,7 @@ type SessionResponse = {
|
||||
export const getSession = async (): Promise<
|
||||
Result<SessionResponse, ErrorResponse>
|
||||
> => {
|
||||
const response = await fetch(TARGET + "/api/session");
|
||||
const response = await fetch("/api/session");
|
||||
if (response.ok) {
|
||||
const user = await response.json();
|
||||
useUserStore.getState().setUser(user);
|
||||
@@ -62,7 +58,7 @@ export const login = async (
|
||||
email: string,
|
||||
password: string,
|
||||
): Promise<Result<LoginResponse, ErrorResponse>> => {
|
||||
const response = await fetch(TARGET + "/api/login", {
|
||||
const response = await fetch("/api/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import { createRootRoute, Outlet } from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
|
||||
import { lazy, Suspense } from "react";
|
||||
|
||||
const TanStackRouterDevtools =
|
||||
process.env.NODE_ENV === "production"
|
||||
? () => null // Render nothing in production
|
||||
: lazy(() =>
|
||||
// Lazy load in development
|
||||
import("@tanstack/router-devtools").then((res) => ({
|
||||
default: res.TanStackRouterDevtools,
|
||||
// For Embedded Mode
|
||||
// default: res.TanStackRouterDevtoolsPanel
|
||||
})),
|
||||
);
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: () => (
|
||||
<>
|
||||
<Outlet />
|
||||
<TanStackRouterDevtools />
|
||||
<Suspense>
|
||||
<TanStackRouterDevtools />
|
||||
</Suspense>
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user