File size: 22,544 Bytes
01a6877
8ea6426
802c23e
 
 
 
0678429
802c23e
 
01a6877
 
 
802c23e
 
 
 
15d515e
802c23e
01a6877
e7b3e2b
01a6877
 
802c23e
0678429
 
802c23e
 
0678429
802c23e
 
 
 
 
01a6877
 
 
 
 
 
 
8ea6426
 
 
802c23e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0678429
802c23e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0678429
802c23e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8ea6426
 
802c23e
 
 
 
 
 
 
0678429
802c23e
 
 
0678429
802c23e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0678429
802c23e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8ea6426
 
 
 
 
 
 
 
 
 
802c23e
 
 
 
 
 
 
 
 
 
 
01a6877
 
 
fe33638
 
 
 
 
 
 
 
 
 
 
01a6877
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
802c23e
 
 
 
0678429
802c23e
 
 
 
 
 
 
0678429
802c23e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0678429
802c23e
 
 
 
 
 
 
 
 
 
 
0678429
 
 
 
 
 
 
 
 
 
 
802c23e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a4ce35f
73578f3
 
 
 
 
 
 
 
 
802c23e
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
from fastapi import FastAPI, HTTPException, Depends, status, APIRouter, UploadFile, File
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
from fastapi.middleware.cors import CORSMiddleware
from datetime import datetime, timedelta, timezone
from passlib.context import CryptContext
from typing import Optional, List
from pydantic import BaseModel, Field, validator
import jwt
import database
import cloudinary
import cloudinary.uploader
import os

# JWT configuration
SECRET_KEY = "06J3LND9NFH"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 43200

CLOUD_NAME = os.getenv('CLOUD_NAME')
API_KEY = os.getenv('API_KEY')
API_SECRET = os.getenv('API_SECRET')

app = FastAPI()

# Uncomment and adjust CORS if needed:
# app.add_middleware(
#     CORSMiddleware,
#     allow_origins=["http://localhost:5173", "http://localhost:*", "192.168.*.*:*"],
#     allow_credentials=True,
#     allow_methods=["*"],
#     allow_headers=["*"],
# )

# Configure Cloudinary (ensure you have set your credentials accordingly)
cloudinary.config(
    cloud_name=CLOUD_NAME,
    api_key=API_KEY,
    api_secret=API_SECRET
)

# Define the OAuth2 scheme for OpenAPI docs.
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")

# Password hashing utilities
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

# ---------------------#
#  Pydantic Models     #
# ---------------------#

# User models
class UserCreate(BaseModel):
    username: str
    email: str
    password: str
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    bio: Optional[str] = None
    profile_picture: Optional[str] = None

class UserUpdate(BaseModel):
    username: Optional[str] = None
    email: Optional[str] = None
    password: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    bio: Optional[str] = None
    profile_picture: Optional[str] = None

class UserOut(BaseModel):
    user_id: int
    username: str
    email: Optional[str] = None
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    bio: Optional[str] = None
    profile_picture: Optional[str] = None

# Auth token model
class Token(BaseModel):
    access_token: str
    token_type: str

# Role models
class RoleCreate(BaseModel):
    role_name: str
    description: Optional[str] = None

class RoleOut(BaseModel):
    role_id: int
    role_name: str
    description: Optional[str] = None

# Permission models
class PermissionCreate(BaseModel):
    permission_name: str
    description: Optional[str] = None

class PermissionOut(BaseModel):
    permission_id: int
    permission_name: str
    description: Optional[str] = None

# Location models
class LocationCreate(BaseModel):
    name: str
    address: str
    city: str
    state: Optional[str] = None
    country: Optional[str] = None
    latitude: float
    longitude: float

class LocationOut(BaseModel):
    location_id: int
    name: str
    address: str
    city: str
    state: Optional[str] = None
    country: Optional[str] = None
    latitude: float
    longitude: float

# Event models
class EventCreate(BaseModel):
    host_id: int
    location_id: int
    title: str
    start_time: datetime
    end_time: datetime
    description: Optional[str] = None
    category: Optional[str] = None
    max_participants: Optional[int] = None
    event_picture: Optional[str] = None
    is_recurring: bool = False
    recurrence_type: Optional[str] = None  # daily, weekly, monthly, yearly, custom
    recurrence_interval: Optional[int] = None
    recurrence_end_date: Optional[datetime] = None
    custom_recurrence_pattern: Optional[str] = None

    @validator("start_time", "end_time", "recurrence_end_date", pre=True, always=True)
    def ensure_utc(cls, value):
        if value is None:
            return value
        if isinstance(value, str):
            value = datetime.fromisoformat(value.replace("Z", "+00:00"))
        if value.tzinfo is None:
            value = value.replace(tzinfo=timezone.utc)
        return value.astimezone(timezone.utc)

    class Config:
        json_encoders = {
            datetime: lambda v: v.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
        }
        json_schema_extra = {
            "example": {
                "host_id": 1,
                "location_id": 1,
                "title": "Test Party",
                "start_time": "2025-03-22 06:29:49",
                "end_time": "2025-03-22 06:29:49",
                "description": "This is a test party",
                "category": "party",
                "max_participants": 100,
                "event_picture": "https://example.com/image.jpg",
                "is_recurring": False,
                "recurrence_type": "",
                "recurrence_interval": 0,
                "recurrence_end_date": "2025-03-22 06:29:49",
                "custom_recurrence_pattern": ""
            }
        }

class EventUpdate(BaseModel):
    host_id: Optional[int] = None
    location_id: Optional[int] = None
    title: Optional[str] = None
    start_time: Optional[datetime] = None
    end_time: Optional[datetime] = None
    description: Optional[str] = None
    category: Optional[str] = None
    max_participants: Optional[int] = None
    event_picture: Optional[str] = None
    is_recurring: Optional[bool] = None
    recurrence_type: Optional[str] = None
    recurrence_interval: Optional[int] = None
    recurrence_end_date: Optional[datetime] = None
    custom_recurrence_pattern: Optional[str] = None

    class Config:
        json_encoders = {
            datetime: lambda v: (v if v.tzinfo else v.replace(tzinfo=timezone.utc)).isoformat()
        }

class EventOut(BaseModel):
    event_id: int
    host_id: int
    location_id: int
    title: str
    start_time: datetime
    end_time: datetime
    description: Optional[str] = None
    category: Optional[str] = None
    max_participants: Optional[int] = None
    event_picture: Optional[str] = None
    is_recurring: bool = False
    recurrence_type: Optional[str] = None
    recurrence_interval: Optional[int] = None
    recurrence_end_date: Optional[datetime] = None
    custom_recurrence_pattern: Optional[str] = None

    class Config:
        json_encoders = {
            datetime: lambda v: (v if v.tzinfo else v.replace(tzinfo=timezone.utc)).isoformat()
        }

# ---------------------#
#    Auth Functions    #
# ---------------------#
def authenticate_user(username: str, password: str):
    user = database.get_user_by_username(username)
    if not user or not verify_password(password, user["password_hash"]):
        return None
    return user

def create_access_token(data: dict, expires_delta: timedelta = None):
    to_encode = data.copy()
    expire = datetime.utcnow().replace(tzinfo=timezone.utc) + (expires_delta if expires_delta else timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# Updated get_current_user now uses OAuth2PasswordBearer for token extraction.
async def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
    except jwt.PyJWTError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
    
    user = database.get_user_by_username(username)
    if user is None:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
    
    return user

def admin_required(current_user: dict = Depends(get_current_user)):
    roles = database.get_roles_by_user(current_user["user_id"])
    if not any(role["role_name"] == "admin" for role in roles):
        raise HTTPException(status_code=403, detail="Admin privileges required")
    return current_user

# ---------------------#
#       Startup        #
# ---------------------#
@app.on_event("startup")
def startup():
    # Create all tables
    database.create_tables()

    # Create default admin user and role if they do not exist
    admin_username = "admin"
    admin_email = "admin@example.com"
    admin_password = "admin123"  # default password; change after first login

    admin_user = database.get_user_by_username(admin_username)
    if not admin_user:
        admin_user_id = database.create_user(admin_username, admin_email, hash_password(admin_password))
        admin_user = database.get_user_by_id(admin_user_id)

    admin_role = database.get_role_by_name("admin")
    if not admin_role:
        admin_role_id = database.create_role("admin", "Administrator with full privileges")
        admin_role = database.get_role_by_id(admin_role_id)

    # Assign admin role to admin user if not already assigned
    roles = database.get_roles_by_user(admin_user["user_id"])
    if not any(role["role_name"] == "admin" for role in roles):
        database.add_user_role(admin_user["user_id"], admin_role["role_id"])

# ---------------------#
#       Routers        #
# ---------------------#

# User Router
user_router = APIRouter(prefix="/api/v1/user", tags=["User"])

@user_router.get("/all", response_model=List[UserOut])
async def all_users(page: int = 1, per_page: int = 100, current_user: dict = Depends(get_current_user)):
    users = database.get_all_users(page=page, per_page=per_page)
    return users

@user_router.post("/create", response_model=UserOut)
def create_new_user(user: UserCreate):
    if database.get_user_by_username(user.username):
        raise HTTPException(status_code=400, detail="Username already registered")
    password_hash = hash_password(user.password)
    user_id = database.create_user(user.username, user.email, password_hash, user.first_name, user.last_name, user.bio, user.profile_picture)
    new_user = database.get_user_by_id(user_id)
    return new_user

@user_router.get("/{user_id}", response_model=UserOut)
def read_user(user_id: int, current_user: dict = Depends(get_current_user)):
    user = database.get_user_by_id(user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

@user_router.put("/{user_id}", response_model=UserOut)
def update_existing_user(user_id: int, user_update: UserUpdate, current_user: dict = Depends(get_current_user)):
    user = database.get_user_by_id(user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    new_username = user_update.username if user_update.username else None
    new_email = user_update.email if user_update.email else None
    new_password_hash = hash_password(user_update.password) if user_update.password else None
    database.update_user(
        user_id,
        username=new_username,
        email=new_email,
        password_hash=new_password_hash,
        first_name=user_update.first_name,
        last_name=user_update.last_name,
        bio=user_update.bio,
        profile_picture=user_update.profile_picture
    )
    updated_user = database.get_user_by_id(user_id)
    return updated_user

@user_router.delete("/{user_id}")
def delete_existing_user(user_id: int, current_user: dict = Depends(get_current_user)):
    user = database.get_user_by_id(user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    database.delete_user(user_id)
    return {"detail": "User deleted"}

@user_router.post("/upload-profile-picture", response_model=UserOut)
async def upload_profile_picture(file: UploadFile = File(...), current_user: dict = Depends(get_current_user)):
    try:
        # Optimize the image: resize to 512x512, convert to JPEG, and apply eco quality for compression
        result = cloudinary.uploader.upload(
            file.file,
            transformation=[{
                "width": 512,
                "height": 512,
                "crop": "limit",
                "quality": "auto:eco",
                "fetch_format": "jpg"
            }]
        )
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Image upload failed: {str(e)}")
    image_url = result.get("secure_url")
    if not image_url:
        raise HTTPException(status_code=400, detail="Failed to obtain image URL from Cloudinary")
    # Update the user's profile picture in the database
    database.update_user(
        current_user["user_id"],
        username=None,
        email=None,
        password_hash=None,
        first_name=None,
        last_name=None,
        bio=None,
        profile_picture=image_url
    )
    updated_user = database.get_user_by_id(current_user["user_id"])
    return updated_user

# Authentication Router
auth_router = APIRouter(prefix="/api/v1/auth", tags=["Authentication"])

@auth_router.post("/login", response_model=Token)
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    access_token = create_access_token(data={"sub": user["username"]})
    return {"access_token": access_token, "token_type": "bearer"}

@auth_router.get("/me", response_model=UserOut)
def read_current_user_endpoint(current_user: dict = Depends(get_current_user)):
    return current_user

# Admin Router for Roles and Permissions management
admin_router = APIRouter(prefix="/api/v1/admin", tags=["Admin"])

@admin_router.post("/roles", response_model=RoleOut, dependencies=[Depends(admin_required)])
def create_role(role: RoleCreate):
    role_id = database.create_role(role.role_name, role.description)
    new_role = database.get_role_by_id(role_id)
    return new_role

@admin_router.get("/roles", response_model=List[RoleOut], dependencies=[Depends(admin_required)])
def get_all_roles(page: int = 1, per_page: int = 100):
    roles = database.get_all_roles(page=page, per_page=per_page)
    return roles

@admin_router.get("/roles/{role_id}", response_model=RoleOut, dependencies=[Depends(admin_required)])
def get_role(role_id: int):
    role = database.get_role_by_id(role_id)
    if not role:
        raise HTTPException(status_code=404, detail="Role not found")
    return role

@admin_router.put("/roles/{role_id}", response_model=RoleOut, dependencies=[Depends(admin_required)])
def update_role(role_id: int, role: RoleCreate):
    database.update_role(role_id, role.role_name, role.description)
    updated_role = database.get_role_by_id(role_id)
    return updated_role

@admin_router.delete("/roles/{role_id}", dependencies=[Depends(admin_required)])
def delete_role(role_id: int):
    database.delete_role(role_id)
    return {"detail": "Role deleted"}

@admin_router.post("/permissions", response_model=PermissionOut, dependencies=[Depends(admin_required)])
def create_permission(permission: PermissionCreate):
    permission_id = database.create_permission(permission.permission_name, permission.description)
    new_permission = database.get_permission_by_id(permission_id)
    return new_permission

@admin_router.get("/permissions", response_model=List[PermissionOut], dependencies=[Depends(admin_required)])
def get_all_permissions(page: int = 1, per_page: int = 100):
    permissions = database.get_all_permissions(page=page, per_page=per_page)
    return permissions

@admin_router.get("/permissions/{permission_id}", response_model=PermissionOut, dependencies=[Depends(admin_required)])
def get_permission(permission_id: int):
    permission = database.get_permission_by_id(permission_id)
    if not permission:
        raise HTTPException(status_code=404, detail="Permission not found")
    return permission

@admin_router.put("/permissions/{permission_id}", response_model=PermissionOut, dependencies=[Depends(admin_required)])
def update_permission(permission_id: int, permission: PermissionCreate):
    database.update_permission(permission_id, permission.permission_name, permission.description)
    updated_permission = database.get_permission_by_id(permission_id)
    return updated_permission

@admin_router.delete("/permissions/{permission_id}", dependencies=[Depends(admin_required)])
def delete_permission(permission_id: int):
    database.delete_permission(permission_id)
    return {"detail": "Permission deleted"}

# Locations Router
location_router = APIRouter(prefix="/api/v1/locations", tags=["Locations"])

@location_router.post("/", response_model=LocationOut)
def create_location(location: LocationCreate, current_user: dict = Depends(get_current_user)):
    location_id = database.create_location(location.name, location.address, location.city, location.state, location.country, location.latitude, location.longitude)
    new_location = database.get_location_by_id(location_id)
    return new_location

@location_router.get("/", response_model=List[LocationOut])
def get_all_locations(page: int = 1, per_page: int = 100, current_user: dict = Depends(get_current_user)):
    locations = database.get_all_locations(page=page, per_page=per_page)
    return locations

@location_router.get("/{location_id}", response_model=LocationOut)
def get_location(location_id: int, current_user: dict = Depends(get_current_user)):
    location = database.get_location_by_id(location_id)
    if not location:
        raise HTTPException(status_code=404, detail="Location not found")
    return location

@location_router.put("/{location_id}", response_model=LocationOut)
def update_location(location_id: int, location: LocationCreate, current_user: dict = Depends(get_current_user)):
    database.update_location(location_id, location.name, location.address, location.city, location.state, location.country, location.latitude, location.longitude)
    updated_location = database.get_location_by_id(location_id)
    return updated_location

@location_router.delete("/{location_id}")
def delete_location(location_id: int, current_user: dict = Depends(get_current_user)):
    database.delete_location(location_id)
    return {"detail": "Location deleted"}

# Events Router
event_router = APIRouter(prefix="/api/v1/events", tags=["Events"])

@event_router.post("/create", response_model=EventOut)
def create_event(event: EventCreate, current_user: dict = Depends(get_current_user)):
    recurrence_type = event.recurrence_type.strip() if event.recurrence_type and event.recurrence_type.strip() else None
    recurrence_interval = event.recurrence_interval if event.recurrence_interval and event.recurrence_interval != 0 else None
    recurrence_end_date = event.recurrence_end_date if event.recurrence_end_date else None
    custom_recurrence_pattern = event.custom_recurrence_pattern.strip() if event.custom_recurrence_pattern and event.custom_recurrence_pattern.strip() else None

    event_id = database.create_event(
        event.host_id,
        event.location_id,
        event.title,
        event.start_time,
        event.end_time,
        event.description,
        event.category,
        event.max_participants,
        event.event_picture,
        event.is_recurring,
        recurrence_type,
        recurrence_interval,
        recurrence_end_date,
        custom_recurrence_pattern
    )
    new_event = database.get_event_by_id(event_id)
    return new_event

@event_router.get("/", response_model=List[EventOut])
def get_all_events(page: int = 1, per_page: int = 100, current_user: dict = Depends(get_current_user)):
    events = database.get_all_events(page=page, per_page=per_page)
    return events

@event_router.get("/{event_id}", response_model=EventOut)
def get_event(event_id: int, current_user: dict = Depends(get_current_user)):
    event = database.get_event_by_id(event_id)
    if not event:
        raise HTTPException(status_code=404, detail="Event not found")
    return event

@event_router.put("/{event_id}", response_model=EventOut)
def update_event(event_id: int, event: EventUpdate, current_user: dict = Depends(get_current_user)):
    database.update_event(
        event_id,
        host_id=event.host_id,
        location_id=event.location_id,
        title=event.title,
        start_time=event.start_time,
        end_time=event.end_time,
        description=event.description,
        category=event.category,
        max_participants=event.max_participants,
        event_picture=event.event_picture,
        is_recurring=event.is_recurring,
        recurrence_type=event.recurrence_type,
        recurrence_interval=event.recurrence_interval,
        recurrence_end_date=event.recurrence_end_date,
        custom_recurrence_pattern=event.custom_recurrence_pattern
    )
    updated_event = database.get_event_by_id(event_id)
    return updated_event

@event_router.delete("/{event_id}")
def delete_event(event_id: int, current_user: dict = Depends(get_current_user)):
    database.delete_event(event_id)
    return {"detail": "Event deleted"}

@event_router.post("/{event_id}/join")
def join_event(event_id: int, current_user: dict = Depends(get_current_user)):
    result = database.add_event_participant(event_id, current_user["user_id"])
    if result == 0:
        raise HTTPException(status_code=400, detail="Could not join event")
    return {"detail": "Joined event successfully"}

@event_router.post("/{event_id}/leave")
def leave_event(event_id: int, current_user: dict = Depends(get_current_user)):
    result = database.remove_event_participant(event_id, current_user["user_id"])
    if result == 0:
        raise HTTPException(status_code=400, detail="Could not leave event")
    return {"detail": "Left event successfully"}

@event_router.get("/categories/all")
def get_all_event_categories(current_user: dict = Depends(get_current_user)):
    categories = database.get_all_event_categories()
    return categories

@event_router.get("/category/{category}")
def get_events_by_category(category: str, current_user: dict = Depends(get_current_user)):
    events = database.get_events_by_category(category)
    return events

# Include routers in the main app
app.include_router(user_router)
app.include_router(auth_router)
app.include_router(admin_router)
app.include_router(location_router)
app.include_router(event_router)