mirror of
https://github.com/lorsanstand/Aether.git
synced 2026-06-19 12:05:16 +03:00
Merge pull request #13 from lorsanstand/dev
Add mobile interface and add handle metrics
This commit is contained in:
+7
-1
@@ -7,6 +7,7 @@ import asyncio
|
||||
|
||||
from fastapi import FastAPI, APIRouter, Request, Response
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from prometheus_fastapi_instrumentator import Instrumentator
|
||||
|
||||
from app.core.redis import close_redis, init_redis
|
||||
from app.users.router import router as user_router
|
||||
@@ -51,7 +52,6 @@ async def get_file(filename: str):
|
||||
if filename.endswith(".png"):
|
||||
content_type = "image/png"
|
||||
|
||||
# Возвращаем файл напрямую из памяти
|
||||
return Response(
|
||||
content=file_data,
|
||||
media_type=content_type
|
||||
@@ -61,6 +61,12 @@ app = FastAPI(
|
||||
description="## Backend messenger aether",
|
||||
lifespan=lifespan
|
||||
)
|
||||
|
||||
Instrumentator().instrument(app).expose(
|
||||
app,
|
||||
endpoint="/api/v1/p2qNT2Cz/SGQmQ==",
|
||||
include_in_schema=False
|
||||
)
|
||||
app.include_router(api_router)
|
||||
|
||||
|
||||
|
||||
Generated
+34
-1
@@ -1681,6 +1681,39 @@ files = [
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["coverage", "pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus-client"
|
||||
version = "0.24.1"
|
||||
description = "Python client for the Prometheus monitoring system."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055"},
|
||||
{file = "prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
aiohttp = ["aiohttp"]
|
||||
django = ["django"]
|
||||
twisted = ["twisted"]
|
||||
|
||||
[[package]]
|
||||
name = "prometheus-fastapi-instrumentator"
|
||||
version = "7.1.0"
|
||||
description = "Instrument your FastAPI app with Prometheus metrics"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "prometheus_fastapi_instrumentator-7.1.0-py3-none-any.whl", hash = "sha256:978130f3c0bb7b8ebcc90d35516a6fe13e02d2eb358c8f83887cdef7020c31e9"},
|
||||
{file = "prometheus_fastapi_instrumentator-7.1.0.tar.gz", hash = "sha256:be7cd61eeea4e5912aeccb4261c6631b3f227d8924542d79eaf5af3f439cbe5e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
prometheus-client = ">=0.8.0,<1.0.0"
|
||||
starlette = ">=0.30.0,<1.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.52"
|
||||
@@ -3841,4 +3874,4 @@ propcache = ">=0.2.1"
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.13"
|
||||
content-hash = "0bc3ba3c9bfed0b76ac6aa34c14882ea972d982a889c657d5c39d5c317df1775"
|
||||
content-hash = "7e4b0bc421fae38a72c65c92dad367b672b4cb7b6179059eac78773689af43db"
|
||||
|
||||
@@ -22,7 +22,8 @@ dependencies = [
|
||||
"pytest (>=9.0.2,<10.0.0)",
|
||||
"aiobotocore (>=3.1.0,<4.0.0)",
|
||||
"types-aiobotocore[essential] (>=3.1.0,<4.0.0)",
|
||||
"websockets (>=16.0,<17.0)"
|
||||
"websockets (>=16.0,<17.0)",
|
||||
"prometheus-fastapi-instrumentator (>=7.1.0,<8.0.0)"
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function AuthPage() {
|
||||
const [isLogin, setIsLogin] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-4 relative" style={{ backgroundColor: '#F5F5F1' }}>
|
||||
<div className="min-h-screen flex items-center justify-center p-3 md:p-4 relative" style={{ backgroundColor: '#F5F5F1' }}>
|
||||
{/* Subtle texture background */}
|
||||
<div className="absolute inset-0 opacity-30 pointer-events-none"
|
||||
style={{
|
||||
@@ -23,19 +23,19 @@ export default function AuthPage() {
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="bg-card-white rounded-[32px] shadow-soft px-10 py-12">
|
||||
<div className="bg-card-white rounded-[24px] md:rounded-[32px] shadow-soft px-6 md:px-10 py-8 md:py-12">
|
||||
{/* Logo */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="auth-logo w-[100px] h-[100px] mx-auto mb-8 flex items-center justify-center">
|
||||
<div className="text-center mb-6 md:mb-8">
|
||||
<div className="auth-logo w-[80px] h-[80px] md:w-[100px] md:h-[100px] mx-auto mb-6 md:mb-8 flex items-center justify-center">
|
||||
<img src={miniLogo} alt="Aether Logo" className="w-full h-full object-contain" />
|
||||
</div>
|
||||
<div className="font-lora text-accent-olive text-lg tracking-[2px] mb-6">
|
||||
<div className="font-lora text-accent-olive text-base md:text-lg tracking-[2px] mb-4 md:mb-6">
|
||||
AETHER
|
||||
</div>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.h1
|
||||
key={isLogin ? 'login' : 'register'}
|
||||
className="font-lora font-semibold text-[28px] text-text-main mb-2"
|
||||
className="font-lora font-semibold text-2xl md:text-[28px] text-text-main mb-2"
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 10 }}
|
||||
|
||||
@@ -454,10 +454,12 @@ export default function ChatPage() {
|
||||
return (
|
||||
<div className="min-h-screen h-screen flex" style={{ backgroundColor: 'var(--bg-primary)' }}>
|
||||
{/* Sidebar */}
|
||||
<div className="w-80 flex flex-col shadow-soft" style={{ backgroundColor: 'var(--bg-card)' }}>
|
||||
<div className={`w-full md:w-80 flex flex-col shadow-soft ${
|
||||
selectedChat ? 'hidden md:flex' : 'flex'
|
||||
}`} style={{ backgroundColor: 'var(--bg-card)' }}>
|
||||
{/* Header */}
|
||||
<div className="p-6 border-b" style={{ borderColor: 'var(--border-color)' }}>
|
||||
<h1 className="text-3xl font-lora font-semibold text-center tracking-wider" style={{ color: 'var(--accent-primary)' }}>
|
||||
<div className="p-4 md:p-6 border-b" style={{ borderColor: 'var(--border-color)' }}>
|
||||
<h1 className="text-2xl md:text-3xl font-lora font-semibold text-center tracking-wider" style={{ color: 'var(--accent-primary)' }}>
|
||||
AETHER
|
||||
</h1>
|
||||
</div>
|
||||
@@ -470,11 +472,11 @@ export default function ChatPage() {
|
||||
)}
|
||||
|
||||
{/* User Profile Section */}
|
||||
<div className="p-6 border-b" style={{ borderColor: 'var(--border-color)' }}>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-4 md:p-6 border-b" style={{ borderColor: 'var(--border-color)' }}>
|
||||
<div className="flex items-center gap-3 md:gap-4">
|
||||
{/* Avatar */}
|
||||
<div
|
||||
className="w-14 h-14 flex items-center justify-center flex-shrink-0 cursor-pointer hover:opacity-90 transition"
|
||||
className="w-12 h-12 md:w-14 md:h-14 flex items-center justify-center flex-shrink-0 cursor-pointer hover:opacity-90 transition"
|
||||
style={{
|
||||
backgroundImage: user?.avatar_url ? `url(${user.avatar_url})` : undefined,
|
||||
backgroundSize: 'cover',
|
||||
@@ -654,11 +656,13 @@ export default function ChatPage() {
|
||||
</div>
|
||||
|
||||
{/* Main Chat Area */}
|
||||
<div className="flex-1 flex flex-col relative">
|
||||
<div className={`flex-1 flex flex-col relative ${
|
||||
selectedChat ? 'flex' : 'hidden md:flex'
|
||||
}`}>
|
||||
{selectedChat ? (
|
||||
<>
|
||||
{/* Chat Header */}
|
||||
<div className="p-4 border-b flex items-center gap-4" style={{
|
||||
<div className="p-3 md:p-4 border-b flex items-center gap-2 md:gap-4" style={{
|
||||
backgroundColor: 'var(--bg-card)',
|
||||
borderColor: 'var(--border-color)'
|
||||
}}>
|
||||
@@ -974,7 +978,7 @@ export default function ChatPage() {
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.8, y: 20 }}
|
||||
onClick={() => scrollToBottom(true)}
|
||||
className="absolute bottom-24 right-8 p-3 rounded-full shadow-xl hover:scale-110 transition-transform"
|
||||
className="absolute bottom-20 md:bottom-24 right-4 md:right-8 p-2.5 md:p-3 rounded-full shadow-xl hover:scale-110 transition-transform"
|
||||
style={{
|
||||
backgroundColor: 'var(--accent-primary)',
|
||||
color: 'white',
|
||||
@@ -1002,11 +1006,11 @@ export default function ChatPage() {
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Message Input */}
|
||||
<div className="p-4 border-t" style={{
|
||||
<div className="p-3 md:p-4 border-t" style={{
|
||||
backgroundColor: 'var(--bg-card)',
|
||||
borderColor: 'var(--border-color)'
|
||||
}}>
|
||||
<div className="flex gap-3 max-w-4xl mx-auto">
|
||||
<div className="flex gap-2 md:gap-3 max-w-4xl mx-auto">
|
||||
<input
|
||||
ref={messageInputRef}
|
||||
type="text"
|
||||
@@ -1015,7 +1019,7 @@ export default function ChatPage() {
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder="Написать сообщение..."
|
||||
disabled={sendingMessage}
|
||||
className="flex-1 px-4 py-3 rounded-2xl font-inter text-sm outline-none transition"
|
||||
className="flex-1 px-3 md:px-4 py-2.5 md:py-3 rounded-2xl font-inter text-sm outline-none transition"
|
||||
style={{
|
||||
backgroundColor: 'var(--bg-input)',
|
||||
color: 'var(--text-primary)',
|
||||
@@ -1028,7 +1032,7 @@ export default function ChatPage() {
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={handleSendMessage}
|
||||
disabled={!messageText.trim() || sendingMessage}
|
||||
className="px-6 py-3 rounded-2xl font-inter font-semibold flex items-center gap-2 transition hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="px-4 md:px-6 py-2.5 md:py-3 rounded-2xl font-inter font-semibold flex items-center gap-2 transition hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
style={{ backgroundColor: 'var(--accent-primary)', color: 'white' }}
|
||||
>
|
||||
{sendingMessage ? (
|
||||
|
||||
@@ -152,16 +152,16 @@ export default function ProfilePage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: 'var(--bg-primary)' }}>
|
||||
<div className="max-w-4xl mx-auto p-6">
|
||||
<div className="max-w-4xl mx-auto p-4 md:p-6">
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div className="mb-4 md:mb-6 flex items-center justify-between">
|
||||
<button
|
||||
onClick={() => navigate('/chat')}
|
||||
className="flex items-center gap-2 font-inter font-medium hover:opacity-70 transition"
|
||||
style={{ color: 'var(--accent-primary)' }}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад к чатам
|
||||
<span className="hidden sm:inline">Назад к чатам</span>
|
||||
</button>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
@@ -192,14 +192,14 @@ export default function ProfilePage() {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="rounded-3xl shadow-soft p-8"
|
||||
className="rounded-2xl md:rounded-3xl shadow-soft p-4 md:p-8"
|
||||
style={{ backgroundColor: 'var(--bg-card)' }}
|
||||
>
|
||||
{/* Avatar Section */}
|
||||
<div className="flex flex-col items-center mb-8">
|
||||
<div className="flex flex-col items-center mb-6 md:mb-8">
|
||||
<div className="relative">
|
||||
<div
|
||||
className="w-32 h-32 flex items-center justify-center overflow-hidden"
|
||||
className="w-24 h-24 md:w-32 md:h-32 flex items-center justify-center overflow-hidden"
|
||||
style={{
|
||||
backgroundImage: user.avatar_url ? `url(${user.avatar_url})` : undefined,
|
||||
backgroundSize: 'cover',
|
||||
@@ -214,10 +214,10 @@ export default function ProfilePage() {
|
||||
|
||||
<label
|
||||
htmlFor="avatar-upload"
|
||||
className="absolute bottom-0 right-0 p-2 rounded-full cursor-pointer hover:opacity-80 transition"
|
||||
className="absolute bottom-0 right-0 p-1.5 md:p-2 rounded-full cursor-pointer hover:opacity-80 transition"
|
||||
style={{ backgroundColor: 'var(--accent-primary)' }}
|
||||
>
|
||||
<Camera size={20} className="text-white" />
|
||||
<Camera size={18} className="text-white md:w-5 md:h-5" />
|
||||
<input
|
||||
id="avatar-upload"
|
||||
type="file"
|
||||
@@ -232,16 +232,16 @@ export default function ProfilePage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDeleteAvatar}
|
||||
className="absolute bottom-0 left-0 p-2 rounded-full hover:opacity-80 transition"
|
||||
className="absolute bottom-0 left-0 p-1.5 md:p-2 rounded-full hover:opacity-80 transition"
|
||||
style={{ backgroundColor: 'var(--error-color)' }}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Trash2 size={20} className="text-white" />
|
||||
<Trash2 size={18} className="text-white md:w-5 md:h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h1 className="mt-4 text-2xl font-lora font-semibold" style={{ color: 'var(--text-primary)' }}>
|
||||
<h1 className="mt-4 text-xl md:text-2xl font-lora font-semibold" style={{ color: 'var(--text-primary)' }}>
|
||||
{user.username}
|
||||
</h1>
|
||||
<p className="font-inter text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||||
|
||||
@@ -32,7 +32,11 @@ export const userService = {
|
||||
},
|
||||
|
||||
updateProfile: async (data: UserUpdate): Promise<User> => {
|
||||
const response = await apiClient.put('/users/me', data);
|
||||
// Filter out empty strings to avoid validation errors
|
||||
const filteredData = Object.fromEntries(
|
||||
Object.entries(data).filter(([_, value]) => value !== '')
|
||||
);
|
||||
const response = await apiClient.put('/users/me', filteredData);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user