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: 1 addition & 1 deletion backend/app/auth/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def _verify_backup_code(cur, user_id, code):
if not row:
return False

cur.execute("UPDATE user_2fa_backup_codes SET used_at=NOW() WHERE id=%s", (row["id"],))
cur.execute("DELETE FROM user_2fa_backup_codes WHERE id=%s", (row["id"],))
return True


Expand Down
15 changes: 9 additions & 6 deletions backend/app/profile/badges.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def get_public_profile(username):

try:
cursor.execute(
"SELECT username, avatar, role, badges, created_at, bio, location, timezone, github_username "
"SELECT username, avatar, badges, created_at, bio, location, timezone, github_username "
"FROM users WHERE username = %s AND verified = 1",
(username,),
)
Expand All @@ -170,20 +170,23 @@ def get_public_profile(username):
badges = []

created_at = user.get("created_at")
avatar = user.get("avatar")
if not isinstance(avatar, str) or not avatar.startswith("/uploads/avatars/"):
avatar = None

member_since = (
created_at.isoformat() if hasattr(created_at, "isoformat")
else str(created_at) if created_at
created_at.strftime("%Y-%m-01") if hasattr(created_at, "strftime")
else str(created_at)[:7] + "-01" if created_at
else None
)

return {
"username": user["username"],
"avatar": user.get("avatar"),
"avatar": avatar,
"bio": user.get("bio"),
"location": user.get("location"),
"timezone": user.get("timezone"),
"github_username": user.get("github_username"),
"role": user.get("role"),
"badges": badges,
"member_since": member_since,
}
}
26 changes: 23 additions & 3 deletions backend/app/profile/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def update_user(user_id, data):

if avatar is not None:
avatar = avatar.strip()
if avatar and not avatar.startswith(("http://", "https://", "/uploads/")):
if avatar and not avatar.startswith("/uploads/avatars/"):
db.close()
return False, "invalid_avatar"
fields.append("avatar=%s")
Expand Down Expand Up @@ -131,6 +131,12 @@ def update_user(user_id, data):
logger = logging.getLogger(__name__)


def _local_avatar_url(value):
if isinstance(value, str) and value.startswith("/uploads/avatars/"):
return value
return None


def _safe_unlink(path):
try:
if path and os.path.isfile(path):
Expand Down Expand Up @@ -314,7 +320,7 @@ def serialize_user(user):
"bio": user.get("bio"),
"location": user.get("location"),
"timezone": user.get("timezone"),
"avatar": user.get("avatar"),
"avatar": _local_avatar_url(user.get("avatar")),
"role": user.get("role"),
"badges": user.get("badges") or [],
"achievements": user.get("achievements") or [],
Expand All @@ -339,15 +345,29 @@ def get_user_and_refresh_token(user_id):
def delete_user_account(user_id):
db = get_db()
cursor = db.cursor()
avatar_path = None

try:
cursor.execute("SELECT avatar FROM users WHERE id=%s", (user_id,))
user = cursor.fetchone()
if not user:
return False

avatar = user.get("avatar")
if avatar and avatar.startswith("/uploads/avatars/"):
avatar_path = os.path.join(
current_app.config["UPLOAD_FOLDER"],
avatar.rsplit("/", 1)[-1],
)

cursor.execute("DELETE FROM refresh_tokens WHERE user_id=%s", (user_id,))
cursor.execute("DELETE FROM password_resets WHERE user_id=%s", (user_id,))
cursor.execute("DELETE FROM email_verifications WHERE user_id=%s", (user_id,))
cursor.execute("DELETE FROM users WHERE id=%s", (user_id,))

db.commit()
return cursor.rowcount > 0
_safe_unlink(avatar_path)
return True
except Exception:
db.rollback()
return False
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import VerifyEmail from "./pages/VerifyEmail";
import ResetPassword from "./pages/ResetPassword";
import UserProfile from "./pages/user/UserProfile";
import GitHubCallback from "./pages/GitHubCallback";
import Services from "./pages/services";

function AppContent() {
const { user } = useAuth();
Expand All @@ -33,6 +34,7 @@ function AppContent() {
<Routes>
<Route path="/" element={<Home />} />
<Route path="/home" element={<Home />} />
<Route path="/services" element={<Services />} />
<Route path="/github" element={<GitHub />} />
<Route path="/contact" element={<Contact />} />
<Route path="/imprint" element={<Imprint />} />
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/Footer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default function Footer() {
<h4 className="footer-section-title">{t("footer.nav_heading")}</h4>
<ul className="footer-nav-list">
<li><Link to="/">{t("nav.home")}</Link></li>
<li><Link to="/services">{t("nav.services")}</Link></li>
<li><Link to="/github">{t("nav.github")}</Link></li>
<li><Link to="/contact">{t("nav.contact")}</Link></li>
</ul>
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/components/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ export default function Navbar() {
</Link>
</li>

<li>
<Link
to="/services"
className={`nav-link-featured ${isActive("/services") ? "active" : ""}`.trim()}
>
{t("nav.services")}
</Link>
</li>

<li>
<Link
to="/github"
Expand Down Expand Up @@ -265,4 +274,4 @@ export default function Navbar() {

</nav>
);
}
}
Loading