feat: integrations manager endpoints + sync-to-habits; add guild models/endpoints
This commit is contained in:
parent
1d9a02f92f
commit
71994e0c09
|
|
@ -5,6 +5,7 @@ from .oauth import router as oauth_router
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
|
from fastapi import Body
|
||||||
|
|
||||||
app = FastAPI(title='LifeRPG Modern Backend')
|
app = FastAPI(title='LifeRPG Modern Backend')
|
||||||
|
|
||||||
|
|
@ -74,3 +75,153 @@ def google_events(integration_id: int):
|
||||||
return resp.json()
|
return resp.json()
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/api/v1/guilds')
|
||||||
|
def create_guild(payload: dict = Body({})):
|
||||||
|
name = payload.get('name')
|
||||||
|
owner_id = payload.get('owner_id', 1)
|
||||||
|
if not name:
|
||||||
|
raise HTTPException(status_code=400, detail='name required')
|
||||||
|
db = models.SessionLocal()
|
||||||
|
try:
|
||||||
|
g = models.Guild(name=name, description=payload.get('description'), owner_id=owner_id)
|
||||||
|
db.add(g)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(g)
|
||||||
|
return {'id': g.id, 'name': g.name}
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/guilds')
|
||||||
|
def list_guilds():
|
||||||
|
db = models.SessionLocal()
|
||||||
|
try:
|
||||||
|
rows = db.query(models.Guild).all()
|
||||||
|
return [{'id': r.id, 'name': r.name, 'owner_id': r.owner_id} for r in rows]
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/api/v1/guilds/{guild_id}/members')
|
||||||
|
def add_guild_member(guild_id: int, payload: dict = Body({})):
|
||||||
|
user_id = payload.get('user_id')
|
||||||
|
role = payload.get('role', 'member')
|
||||||
|
if not user_id:
|
||||||
|
raise HTTPException(status_code=400, detail='user_id required')
|
||||||
|
db = models.SessionLocal()
|
||||||
|
try:
|
||||||
|
gm = models.GuildMember(guild_id=guild_id, user_id=user_id, role=role)
|
||||||
|
db.add(gm)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(gm)
|
||||||
|
return {'id': gm.id, 'guild_id': gm.guild_id, 'user_id': gm.user_id}
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/guilds/{guild_id}/members')
|
||||||
|
def list_guild_members(guild_id: int):
|
||||||
|
db = models.SessionLocal()
|
||||||
|
try:
|
||||||
|
rows = db.query(models.GuildMember).filter_by(guild_id=guild_id).all()
|
||||||
|
return [{'id': r.id, 'user_id': r.user_id, 'role': r.role} for r in rows]
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/users/{user_id}/integrations')
|
||||||
|
def list_user_integrations(user_id: int):
|
||||||
|
db = models.SessionLocal()
|
||||||
|
try:
|
||||||
|
rows = db.query(models.Integration).filter_by(user_id=user_id).all()
|
||||||
|
out = [
|
||||||
|
{"id": r.id, "provider": r.provider, "external_id": r.external_id, "created_at": r.created_at.isoformat() if r.created_at else None}
|
||||||
|
for r in rows
|
||||||
|
]
|
||||||
|
return out
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/api/v1/integrations')
|
||||||
|
def list_integrations():
|
||||||
|
db = models.SessionLocal()
|
||||||
|
try:
|
||||||
|
rows = db.query(models.Integration).all()
|
||||||
|
out = [
|
||||||
|
{"id": r.id, "user_id": r.user_id, "provider": r.provider, "external_id": r.external_id, "created_at": r.created_at.isoformat() if r.created_at else None}
|
||||||
|
for r in rows
|
||||||
|
]
|
||||||
|
return out
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete('/api/v1/integrations/{integration_id}')
|
||||||
|
def delete_integration(integration_id: int):
|
||||||
|
db = models.SessionLocal()
|
||||||
|
try:
|
||||||
|
row = db.query(models.Integration).filter_by(id=integration_id).first()
|
||||||
|
if not row:
|
||||||
|
raise HTTPException(status_code=404, detail='integration not found')
|
||||||
|
db.delete(row)
|
||||||
|
db.commit()
|
||||||
|
return {'ok': True}
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/api/v1/integrations/{integration_id}/sync_to_habits')
|
||||||
|
def sync_integration_to_habits(integration_id: int, payload: dict = Body({})):
|
||||||
|
"""Fetch events from the integration and create Habit + Log entries.
|
||||||
|
|
||||||
|
Demo mapping: create a Habit per event with title 'Event: <summary>' and a Log entry.
|
||||||
|
"""
|
||||||
|
db = models.SessionLocal()
|
||||||
|
try:
|
||||||
|
integration = db.query(models.Integration).filter_by(id=integration_id).first()
|
||||||
|
if not integration:
|
||||||
|
raise HTTPException(status_code=404, detail='integration not found')
|
||||||
|
|
||||||
|
# Fetch events via existing events endpoint logic
|
||||||
|
# Reuse token refresh + decrypt logic from oauth module
|
||||||
|
token_row = db.query(models.OAuthToken).filter_by(integration_id=integration_id).order_by(models.OAuthToken.id.desc()).first()
|
||||||
|
if not token_row:
|
||||||
|
raise HTTPException(status_code=404, detail='no token found for integration')
|
||||||
|
|
||||||
|
from .oauth import refresh_google_token_if_needed
|
||||||
|
refreshed = refresh_google_token_if_needed(token_row)
|
||||||
|
if refreshed:
|
||||||
|
token_row = refreshed
|
||||||
|
|
||||||
|
from .crypto import decrypt_text
|
||||||
|
access = decrypt_text(token_row.access_token)
|
||||||
|
if not access:
|
||||||
|
raise HTTPException(status_code=500, detail='unable to decrypt access token')
|
||||||
|
|
||||||
|
headers = {'Authorization': f'Bearer {access}'}
|
||||||
|
params = {'maxResults': 25, 'singleEvents': True, 'orderBy': 'startTime', 'timeMin': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())}
|
||||||
|
resp = requests.get('https://www.googleapis.com/calendar/v3/calendars/primary/events', headers=headers, params=params, timeout=10)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise HTTPException(status_code=502, detail='google api error')
|
||||||
|
events = resp.json().get('items', [])
|
||||||
|
|
||||||
|
created = []
|
||||||
|
for ev in events:
|
||||||
|
title = ev.get('summary') or 'Untitled Event'
|
||||||
|
# Create habit and log
|
||||||
|
habit = models.Habit(project_id=None, user_id=integration.user_id, title=f'Event: {title}', notes=str(ev), cadence='once')
|
||||||
|
db.add(habit)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(habit)
|
||||||
|
|
||||||
|
log = models.Log(habit_id=habit.id, user_id=integration.user_id, action='imported_event')
|
||||||
|
db.add(log)
|
||||||
|
db.commit()
|
||||||
|
created.append({'habit_id': habit.id, 'title': habit.title})
|
||||||
|
|
||||||
|
return {'created': created, 'count': len(created)}
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,23 @@ class ChangeLog(Base):
|
||||||
created_at = Column(DateTime, server_default=func.current_timestamp())
|
created_at = Column(DateTime, server_default=func.current_timestamp())
|
||||||
|
|
||||||
|
|
||||||
|
class Guild(Base):
|
||||||
|
__tablename__ = 'guilds'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
description = Column(Text)
|
||||||
|
owner_id = Column(Integer, ForeignKey('users.id'))
|
||||||
|
created_at = Column(DateTime, server_default=func.current_timestamp())
|
||||||
|
|
||||||
|
|
||||||
|
class GuildMember(Base):
|
||||||
|
__tablename__ = 'guild_members'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
guild_id = Column(Integer, ForeignKey('guilds.id'))
|
||||||
|
user_id = Column(Integer, ForeignKey('users.id'))
|
||||||
|
role = Column(String, default='member')
|
||||||
|
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user