Add log routes and update dashboard
This commit is contained in:
parent
691aa744a0
commit
8985eecfea
@ -4,6 +4,7 @@ from fastapi.responses import JSONResponse, RedirectResponse
|
||||
from api.routes.auth_routes import router as auth_router
|
||||
from api.routes.links_routes import router as links_router
|
||||
from api.routes.user_routes import router as user_router
|
||||
from api.routes.log_routes import router as log_router
|
||||
from typing import Annotated
|
||||
from fastapi.exceptions import HTTPException
|
||||
from starlette.status import HTTP_404_NOT_FOUND
|
||||
@ -44,6 +45,7 @@ app.add_middleware(
|
||||
app.include_router(auth_router, prefix="/api")
|
||||
app.include_router(links_router, prefix="/api")
|
||||
app.include_router(user_router, prefix="/api")
|
||||
app.include_router(log_router, prefix="/api")
|
||||
|
||||
|
||||
@app.get("/c/{link}")
|
||||
|
@ -7,8 +7,10 @@ from typing import Annotated
|
||||
from api.util.authentication import (
|
||||
create_access_token,
|
||||
authenticate_user,
|
||||
get_current_user,
|
||||
)
|
||||
from api.util.db_dependency import get_db
|
||||
from api.schemas.auth_schemas import User
|
||||
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
@ -44,3 +46,14 @@ async def login_for_access_token(
|
||||
# secure=True, # Cookies are only sent over HTTPS
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
# Check if the user is logged in
|
||||
@router.get("/check", summary="Check if the user is logged in")
|
||||
async def check_login(
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
):
|
||||
"""
|
||||
If the user actually makes it to this endpoint, they are logged in
|
||||
"""
|
||||
return {"success": True}
|
||||
|
72
api/routes/log_routes.py
Normal file
72
api/routes/log_routes.py
Normal file
@ -0,0 +1,72 @@
|
||||
from fastapi import APIRouter, status, Path, Depends
|
||||
from fastapi.exception_handlers import HTTPException
|
||||
from typing import Annotated
|
||||
import string
|
||||
import random
|
||||
import datetime
|
||||
import validators
|
||||
|
||||
from api.util.db_dependency import get_db
|
||||
from models import Link, Log
|
||||
from api.schemas.links_schemas import URLSchema
|
||||
from api.schemas.auth_schemas import User
|
||||
from api.util.authentication import get_current_user
|
||||
|
||||
|
||||
router = APIRouter(prefix="/logs", tags=["logs"])
|
||||
|
||||
|
||||
@router.get("", summary="Get all of the logs associated with your account")
|
||||
async def get_logs(
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
db=Depends(get_db),
|
||||
):
|
||||
logs = (
|
||||
db.query(Log)
|
||||
.filter(Log.owner == current_user.id)
|
||||
.order_by(Log.timestamp.desc())
|
||||
.all()
|
||||
)
|
||||
if not logs:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="No logs found"
|
||||
)
|
||||
return logs
|
||||
|
||||
|
||||
@router.get("/{log_id}", summary="Get a specific log")
|
||||
async def get_log(
|
||||
log_id: Annotated[int, Path(title="ID of log to delete")],
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
db=Depends(get_db),
|
||||
):
|
||||
log = (
|
||||
db.query(Log)
|
||||
.filter(Log.id == log_id, Log.owner == current_user.id)
|
||||
.first()
|
||||
)
|
||||
if not log:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Log not found"
|
||||
)
|
||||
return log
|
||||
|
||||
|
||||
@router.delete("/{log_id}", summary="Delete a log")
|
||||
async def delete_log(
|
||||
log_id: Annotated[int, Path(title="ID of log to delete")],
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
db=Depends(get_db),
|
||||
):
|
||||
log = (
|
||||
db.query(Log)
|
||||
.filter(Log.id == log_id, Log.owner == current_user.id)
|
||||
.first()
|
||||
)
|
||||
if not log:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Log not found"
|
||||
)
|
||||
db.delete(log)
|
||||
db.commit()
|
||||
return status.HTTP_204_NO_CONTENT
|
@ -22,7 +22,7 @@ router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
@router.delete("/{user_id}", summary="Delete your account")
|
||||
async def delete_user(
|
||||
user_id: Annotated[int, Path(title="Link to delete")],
|
||||
user_id: Annotated[int, Path(title="ID of user to delete")],
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
db=Depends(get_db),
|
||||
):
|
||||
@ -51,7 +51,7 @@ async def delete_user(
|
||||
|
||||
@router.post("/{user_id}/password", summary="Update your account password")
|
||||
async def update_pass(
|
||||
user_id: Annotated[int, Path(title="Link to update")],
|
||||
user_id: Annotated[int, Path(title="ID of user to update")],
|
||||
update_data: UpdatePasswordSchema,
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
db=Depends(get_db),
|
||||
|
@ -10,6 +10,9 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.6.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"axios": "^1.7.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
||||
// Import components
|
||||
import Login from './components/Login'
|
||||
import Signup from './components/Signup'
|
||||
import Dashboard from './components/Dashboard'
|
||||
|
@ -1,46 +1,126 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Axios from 'axios';
|
||||
import styles from '../styles/Dashboard.module.css';
|
||||
// import { accessAPI } from '../helpers/api';
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
function Dashboard() {
|
||||
// Get the links from the API
|
||||
const [links, setLinks] = useState([]);
|
||||
interface Log {
|
||||
id: number;
|
||||
link: string;
|
||||
timestamp: string;
|
||||
ip: string;
|
||||
location: string;
|
||||
browser: string;
|
||||
os: string;
|
||||
userAgent: string;
|
||||
isp: string;
|
||||
}
|
||||
|
||||
interface Link {
|
||||
link: string;
|
||||
owner: number;
|
||||
redirect_link: string;
|
||||
expire_date: string;
|
||||
}
|
||||
|
||||
const [links, setLinks] = useState<Link[]>([]);
|
||||
const [logs, setLogs] = useState<Log[]>([]);
|
||||
const [visibleLog, setVisibleLog] = useState<string | null>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Fetch links from API
|
||||
useEffect(() => {
|
||||
Axios.get('/api/links')
|
||||
.then((res) => {
|
||||
Axios.get('/api/links').then((res) => {
|
||||
if (res.status === 200) {
|
||||
setLinks(res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
} else {
|
||||
navigate('/login');
|
||||
}
|
||||
}).catch(() => {
|
||||
navigate('/login');
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Fetch logs from API
|
||||
useEffect(() => {
|
||||
Axios.get('/api/logs').then((res) => {
|
||||
if (res.status === 200) {
|
||||
setLogs(res.data);
|
||||
} else {
|
||||
navigate('/login');
|
||||
}
|
||||
}).catch(() => {
|
||||
navigate('/login');
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
const toggleLogRow = (link: string) => {
|
||||
setVisibleLog(visibleLog === link ? null : link);
|
||||
};
|
||||
|
||||
return (
|
||||
<div id={styles.container}>
|
||||
<table>
|
||||
<thead>
|
||||
<tr style={{ border: '2px solid #ccc' }}>
|
||||
<th>Link</th>
|
||||
<th>Visits</th>
|
||||
<th>Redirect</th>
|
||||
<th>Expire Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{/* {links.map((link: any) => (
|
||||
<tr key={link.id}>
|
||||
<td>{link.url}</td>
|
||||
<td>{link.visits}</td>
|
||||
<td>{link.redirect}</td>
|
||||
<table id={styles.mainTable}>
|
||||
<thead>
|
||||
<tr style={{ border: '2px solid #ccc' }}>
|
||||
<th>Link</th>
|
||||
<th>Visits</th>
|
||||
<th>Redirect</th>
|
||||
<th>Expire Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{/* For every link and its logs */}
|
||||
{links.map((link) => (
|
||||
<React.Fragment key={link.link}>
|
||||
<tr className={styles.linkTableRow}>
|
||||
<td>
|
||||
<button onClick={() => toggleLogRow(link.link)} className={styles.linkButton}>{link.link}</button>
|
||||
</td>
|
||||
<td>{logs.filter((log) => log.link === link.link).length || 0}</td>
|
||||
<td>{link.redirect_link}</td>
|
||||
<td>{link.expire_date}</td>
|
||||
</tr>
|
||||
))} */}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Conditionally render logs for this link */}
|
||||
{visibleLog === link.link && (
|
||||
<tr className={styles.logTableRow}>
|
||||
<td colSpan={6}>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Timestamp</th>
|
||||
<th>IP</th>
|
||||
<th>Location</th>
|
||||
<th colSpan={2}>ISP</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{/* Render logs only if visibleLog matches the link */}
|
||||
{logs
|
||||
.filter((log) => log.link === link.link)
|
||||
.map((log, index, filteredLogs) => (
|
||||
<tr key={log.id}>
|
||||
<td>{filteredLogs.length - index}</td>
|
||||
<td>{log.timestamp}</td>
|
||||
<td>{log.ip}</td>
|
||||
<td>{log.location}</td>
|
||||
<td>{log.isp}</td>
|
||||
<td><FontAwesomeIcon icon={faTrash} className={styles.trashBin}/></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -5,15 +5,15 @@ body {
|
||||
background-color: #2c3338;
|
||||
}
|
||||
|
||||
#container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 100px;
|
||||
#mainTable {
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
|
||||
table {
|
||||
margin: 20px 0 20px 0;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
font-size: 25px;
|
||||
width: 1000px;
|
||||
@ -23,11 +23,11 @@ table {
|
||||
}
|
||||
|
||||
/* Center all sub tables */
|
||||
.log-table-row table {
|
||||
.logTableRow table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.log-table-row table {
|
||||
.logTableRow table {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ table th {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.link-table-row {
|
||||
.linkTableRow {
|
||||
border: 2px solid #ccc;
|
||||
}
|
||||
|
||||
@ -45,20 +45,20 @@ table td {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.link-table-row td {
|
||||
.linkTableRow td {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.log-table-row table td {
|
||||
.logTableRow table td {
|
||||
background-color: #3b4148;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.log-table-row table tr {
|
||||
.logTableRow table tr {
|
||||
border: 2px solid #ccc;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
.linkButton {
|
||||
background-color: #3b4148;
|
||||
color: #ccc;
|
||||
border: none;
|
||||
@ -68,7 +68,12 @@ table td {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.fa-trash:hover {
|
||||
.trashBin:hover {
|
||||
color: rgb(238, 86, 86);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.trashBin:active {
|
||||
transform: scale(0.95);
|
||||
}
|
@ -336,6 +336,32 @@
|
||||
dependencies:
|
||||
levn "^0.4.1"
|
||||
|
||||
"@fortawesome/fontawesome-common-types@6.6.0":
|
||||
version "6.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz#31ab07ca6a06358c5de4d295d4711b675006163f"
|
||||
integrity sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==
|
||||
|
||||
"@fortawesome/fontawesome-svg-core@^6.6.0":
|
||||
version "6.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz#2a24c32ef92136e98eae2ff334a27145188295ff"
|
||||
integrity sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.6.0"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@^6.6.0":
|
||||
version "6.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz#061751ca43be4c4d814f0adbda8f006164ec9f3b"
|
||||
integrity sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.6.0"
|
||||
|
||||
"@fortawesome/react-fontawesome@^0.2.2":
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz#68b058f9132b46c8599875f6a636dad231af78d4"
|
||||
integrity sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==
|
||||
dependencies:
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@humanfs/core@^0.19.1":
|
||||
version "0.19.1"
|
||||
resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
|
||||
@ -1209,7 +1235,7 @@ lodash.merge@^4.6.2:
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
loose-envify@^1.1.0:
|
||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
@ -1282,6 +1308,11 @@ node-releases@^2.0.18:
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f"
|
||||
integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==
|
||||
|
||||
object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
optionator@^0.9.3:
|
||||
version "0.9.4"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
|
||||
@ -1349,6 +1380,15 @@ prelude-ls@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.13.1"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
@ -1372,6 +1412,11 @@ react-dom@^18.3.1:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.2"
|
||||
|
||||
react-is@^16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-refresh@^0.14.2:
|
||||
version "0.14.2"
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
|
||||
|
Loading…
x
Reference in New Issue
Block a user