GitHub Action commited on
Commit
3cfe75a
·
1 Parent(s): bd43a11

deploy from github actions

Browse files
app/models/lyrics.py CHANGED
@@ -89,6 +89,8 @@ class Lyric(Base):
89
  group_id = Column(Integer, ForeignKey("groups.id"))
90
  created_by = Column(Integer, ForeignKey("users.id"))
91
  transpose_offset = Column(Integer, default=0, nullable=True)
 
 
92
  created_at = Column(DateTime, default=datetime.utcnow)
93
  updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
94
 
 
89
  group_id = Column(Integer, ForeignKey("groups.id"))
90
  created_by = Column(Integer, ForeignKey("users.id"))
91
  transpose_offset = Column(Integer, default=0, nullable=True)
92
+ is_repertoire_only = Column(Boolean, default=False, nullable=True)
93
+ parent_lyric_id = Column(Integer, ForeignKey("lyrics.id", ondelete="SET NULL"), nullable=True)
94
  created_at = Column(DateTime, default=datetime.utcnow)
95
  updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
96
 
app/routers/alabanza.py CHANGED
@@ -556,6 +556,15 @@ def expulsar_miembro(grupo_id: int, miembro_id: int, db: Session = Depends(get_d
556
  if l.group_id == grupo_id:
557
  l.group_id = l.groups_shared[0].id if l.groups_shared else None
558
 
 
 
 
 
 
 
 
 
 
559
  kicked_tasks = db.query(Task).filter(Task.user_id == miembro_id, Task.groups_shared.any(Group.id == grupo_id)).all()
560
  for t in kicked_tasks:
561
  group_to_remove = db.query(Group).filter(Group.id == grupo_id).first()
@@ -606,6 +615,15 @@ def salir_del_grupo(grupo_id: int, db: Session = Depends(get_db), current_user:
606
  if l.group_id == grupo_id:
607
  l.group_id = l.groups_shared[0].id if l.groups_shared else None
608
 
 
 
 
 
 
 
 
 
 
609
  left_tasks = db.query(Task).filter(Task.user_id == current_user.id, Task.groups_shared.any(Group.id == grupo_id)).all()
610
  for t in left_tasks:
611
  group_to_remove = db.query(Group).filter(Group.id == grupo_id).first()
@@ -869,7 +887,11 @@ def get_grupo_letras(grupo_id: int, db: Session = Depends(get_db), current_user:
869
  if ug.role != "admin" and not ug.perm_ver_letras:
870
  raise HTTPException(status_code=403, detail="No tienes permisos para ver las letras de este grupo")
871
 
872
- lyrics = db.query(Lyric).filter(Lyric.groups_shared.any(Group.id == grupo_id)).all()
 
 
 
 
873
  return [
874
  {
875
  "id": l.id,
 
556
  if l.group_id == grupo_id:
557
  l.group_id = l.groups_shared[0].id if l.groups_shared else None
558
 
559
+ # Eliminar referencias en los repertorios de este grupo para las letras del usuario expulsado
560
+ kicked_lyric_ids = [l.id for l in db.query(Lyric.id).filter(Lyric.created_by == miembro_id).all()]
561
+ group_rep_ids = [r.id for r in db.query(Repertorio.id).filter(Repertorio.group_id == grupo_id).all()]
562
+ if kicked_lyric_ids and group_rep_ids:
563
+ db.query(RepertorioLyric).filter(
564
+ RepertorioLyric.repertorio_id.in_(group_rep_ids),
565
+ RepertorioLyric.lyric_id.in_(kicked_lyric_ids)
566
+ ).delete(synchronize_session=False)
567
+
568
  kicked_tasks = db.query(Task).filter(Task.user_id == miembro_id, Task.groups_shared.any(Group.id == grupo_id)).all()
569
  for t in kicked_tasks:
570
  group_to_remove = db.query(Group).filter(Group.id == grupo_id).first()
 
615
  if l.group_id == grupo_id:
616
  l.group_id = l.groups_shared[0].id if l.groups_shared else None
617
 
618
+ # Eliminar referencias en los repertorios de este grupo para las letras del usuario que sale
619
+ left_lyric_ids = [l.id for l in db.query(Lyric.id).filter(Lyric.created_by == current_user.id).all()]
620
+ group_rep_ids = [r.id for r in db.query(Repertorio.id).filter(Repertorio.group_id == grupo_id).all()]
621
+ if left_lyric_ids and group_rep_ids:
622
+ db.query(RepertorioLyric).filter(
623
+ RepertorioLyric.repertorio_id.in_(group_rep_ids),
624
+ RepertorioLyric.lyric_id.in_(left_lyric_ids)
625
+ ).delete(synchronize_session=False)
626
+
627
  left_tasks = db.query(Task).filter(Task.user_id == current_user.id, Task.groups_shared.any(Group.id == grupo_id)).all()
628
  for t in left_tasks:
629
  group_to_remove = db.query(Group).filter(Group.id == grupo_id).first()
 
887
  if ug.role != "admin" and not ug.perm_ver_letras:
888
  raise HTTPException(status_code=403, detail="No tienes permisos para ver las letras de este grupo")
889
 
890
+ from sqlalchemy import or_
891
+ lyrics = db.query(Lyric).filter(
892
+ Lyric.groups_shared.any(Group.id == grupo_id),
893
+ or_(Lyric.is_repertoire_only == False, Lyric.is_repertoire_only.is_(None))
894
+ ).all()
895
  return [
896
  {
897
  "id": l.id,
app/routers/lyrics.py CHANGED
@@ -1,6 +1,6 @@
1
  from fastapi import APIRouter, Depends, HTTPException, status
2
  from sqlalchemy.orm import Session
3
- from typing import List
4
  from app.database import get_db
5
  from app.models.lyrics import Lyric, Group, UserGroup, Repertorio, RepertorioLyric, Invitation
6
  from app.models.user import User
@@ -43,8 +43,12 @@ def create_lyric(lyric: LyricCreate, db: Session = Depends(get_db), current_user
43
 
44
  @router.get("/all", response_model=List[LyricOut])
45
  def list_lyrics(db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
46
- # Listar letras creadas por el usuario actual
47
- return db.query(Lyric).filter(Lyric.created_by == current_user.id).all()
 
 
 
 
48
 
49
  # --- GROUPS ENDPOINTS ---
50
 
@@ -116,7 +120,15 @@ def get_lyric(lyric_id: int, db: Session = Depends(get_db), current_user: User =
116
  return db_lyric
117
 
118
  @router.put("/{lyric_id}", response_model=LyricOut)
119
- def update_lyric(lyric_id: int, lyric_update: LyricCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
 
 
 
 
 
 
 
 
120
  db_lyric = db.query(Lyric).filter(Lyric.id == lyric_id).first()
121
  if not db_lyric:
122
  raise HTTPException(status_code=404, detail="Lyric not found")
@@ -141,24 +153,222 @@ def update_lyric(lyric_id: int, lyric_update: LyricCreate, db: Session = Depends
141
 
142
  if not is_authorized:
143
  raise HTTPException(status_code=403, detail="Not authorized to edit this lyric")
144
-
145
- for key, value in lyric_update.dict().items():
146
- setattr(db_lyric, key, value)
147
-
148
- if db_lyric.group_id is not None:
149
- group = db.query(Group).filter(Group.id == db_lyric.group_id).first()
150
- if group and group not in db_lyric.groups_shared:
151
- db_lyric.groups_shared.append(group)
152
-
153
- db.commit()
154
- db.refresh(db_lyric)
155
- for gid in db_lyric.group_ids:
156
- try:
157
- from app.routers.alabanza import broadcast_to_group
158
- broadcast_to_group(gid, {"type": "group_updated", "grupo_id": gid}, db)
159
- except Exception:
160
- pass
161
- return db_lyric
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  @router.delete("/{lyric_id}")
164
  def delete_lyric(lyric_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
 
1
  from fastapi import APIRouter, Depends, HTTPException, status
2
  from sqlalchemy.orm import Session
3
+ from typing import List, Optional
4
  from app.database import get_db
5
  from app.models.lyrics import Lyric, Group, UserGroup, Repertorio, RepertorioLyric, Invitation
6
  from app.models.user import User
 
43
 
44
  @router.get("/all", response_model=List[LyricOut])
45
  def list_lyrics(db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
46
+ # Listar letras creadas por el usuario actual que no sean exclusivamente de repertorio
47
+ from sqlalchemy import or_
48
+ return db.query(Lyric).filter(
49
+ Lyric.created_by == current_user.id,
50
+ or_(Lyric.is_repertoire_only == False, Lyric.is_repertoire_only.is_(None))
51
+ ).all()
52
 
53
  # --- GROUPS ENDPOINTS ---
54
 
 
120
  return db_lyric
121
 
122
  @router.put("/{lyric_id}", response_model=LyricOut)
123
+ def update_lyric(
124
+ lyric_id: int,
125
+ lyric_update: LyricCreate,
126
+ edit_context_group_id: Optional[int] = None,
127
+ edit_context_repertoire_id: Optional[int] = None,
128
+ update_references: Optional[bool] = None,
129
+ db: Session = Depends(get_db),
130
+ current_user: User = Depends(get_current_user)
131
+ ):
132
  db_lyric = db.query(Lyric).filter(Lyric.id == lyric_id).first()
133
  if not db_lyric:
134
  raise HTTPException(status_code=404, detail="Lyric not found")
 
153
 
154
  if not is_authorized:
155
  raise HTTPException(status_code=403, detail="Not authorized to edit this lyric")
156
+
157
+ # --- CASO 1: Edición con Contexto de Repertorio ---
158
+ if edit_context_repertoire_id is not None:
159
+ repertoire = db.query(Repertorio).filter(Repertorio.id == edit_context_repertoire_id).first()
160
+ if not repertoire:
161
+ raise HTTPException(status_code=404, detail="Repertorio no encontrado")
162
+
163
+ if db_lyric.is_repertoire_only:
164
+ # Actualizar directamente si ya es exclusivo de repertorios
165
+ for key, value in lyric_update.dict().items():
166
+ setattr(db_lyric, key, value)
167
+ db.commit()
168
+ db.refresh(db_lyric)
169
+ return db_lyric
170
+ else:
171
+ # Crear clon para el repertorio
172
+ cloned_lyric = Lyric(
173
+ title=lyric_update.title,
174
+ artist=lyric_update.artist,
175
+ content=lyric_update.content,
176
+ key=lyric_update.key,
177
+ bpm=lyric_update.bpm,
178
+ transpose_offset=lyric_update.transpose_offset,
179
+ group_id=repertoire.group_id,
180
+ created_by=db_lyric.created_by,
181
+ is_repertoire_only=True,
182
+ parent_lyric_id=db_lyric.id
183
+ )
184
+ db.add(cloned_lyric)
185
+ db.flush()
186
+
187
+ # Compartir con el grupo si el repertorio pertenece a uno
188
+ if repertoire.group_id is not None:
189
+ group = db.query(Group).filter(Group.id == repertoire.group_id).first()
190
+ if group:
191
+ cloned_lyric.groups_shared.append(group)
192
+
193
+ # Actualizar relación en repertorio_lyrics
194
+ rep_lyric = db.query(RepertorioLyric).filter(
195
+ RepertorioLyric.repertorio_id == edit_context_repertoire_id,
196
+ RepertorioLyric.lyric_id == db_lyric.id
197
+ ).first()
198
+ if rep_lyric:
199
+ order = rep_lyric.order
200
+ db.delete(rep_lyric)
201
+ db.flush()
202
+ new_rep_lyric = RepertorioLyric(
203
+ repertorio_id=edit_context_repertoire_id,
204
+ lyric_id=cloned_lyric.id,
205
+ order=order
206
+ )
207
+ db.add(new_rep_lyric)
208
+
209
+ db.commit()
210
+ db.refresh(cloned_lyric)
211
+
212
+ if repertoire.group_id is not None:
213
+ try:
214
+ from app.routers.alabanza import broadcast_to_group
215
+ broadcast_to_group(repertoire.group_id, {"type": "group_updated", "grupo_id": repertoire.group_id}, db)
216
+ except Exception:
217
+ pass
218
+ return cloned_lyric
219
+
220
+ # --- CASO 2: Edición con Contexto de Grupo ---
221
+ elif edit_context_group_id is not None:
222
+ group = db.query(Group).filter(Group.id == edit_context_group_id).first()
223
+ if not group:
224
+ raise HTTPException(status_code=404, detail="Grupo no encontrado")
225
+
226
+ is_exclusive_to_group = db_lyric.group_id == edit_context_group_id and len(db_lyric.group_ids) <= 1 and not db_lyric.is_repertoire_only
227
+ if is_exclusive_to_group:
228
+ for key, value in lyric_update.dict().items():
229
+ setattr(db_lyric, key, value)
230
+ db.commit()
231
+ db.refresh(db_lyric)
232
+ try:
233
+ from app.routers.alabanza import broadcast_to_group
234
+ broadcast_to_group(edit_context_group_id, {"type": "group_updated", "grupo_id": edit_context_group_id}, db)
235
+ except Exception:
236
+ pass
237
+ return db_lyric
238
+ else:
239
+ # Crear clon para el grupo
240
+ cloned_lyric = Lyric(
241
+ title=lyric_update.title,
242
+ artist=lyric_update.artist,
243
+ content=lyric_update.content,
244
+ key=lyric_update.key,
245
+ bpm=lyric_update.bpm,
246
+ transpose_offset=lyric_update.transpose_offset,
247
+ group_id=edit_context_group_id,
248
+ created_by=db_lyric.created_by,
249
+ is_repertoire_only=False,
250
+ parent_lyric_id=db_lyric.id
251
+ )
252
+ cloned_lyric.groups_shared.append(group)
253
+ db.add(cloned_lyric)
254
+ db.flush()
255
+
256
+ # Desvincular original del grupo
257
+ if group in db_lyric.groups_shared:
258
+ db_lyric.groups_shared.remove(group)
259
+ if db_lyric.group_id == edit_context_group_id:
260
+ db_lyric.group_id = db_lyric.groups_shared[0].id if db_lyric.groups_shared else None
261
+
262
+ # Actualizar todos los repertorios de este grupo que apuntaban a la original
263
+ group_rep_ids = [r.id for r in db.query(Repertorio.id).filter(Repertorio.group_id == edit_context_group_id).all()]
264
+ if group_rep_ids:
265
+ rep_lyrics = db.query(RepertorioLyric).filter(
266
+ RepertorioLyric.repertorio_id.in_(group_rep_ids),
267
+ RepertorioLyric.lyric_id == db_lyric.id
268
+ ).all()
269
+ for rl in rep_lyrics:
270
+ order = rl.order
271
+ rep_id = rl.repertorio_id
272
+ db.delete(rl)
273
+ db.flush()
274
+ new_rl = RepertorioLyric(
275
+ repertorio_id=rep_id,
276
+ lyric_id=cloned_lyric.id,
277
+ order=order
278
+ )
279
+ db.add(new_rl)
280
+
281
+ db.commit()
282
+ db.refresh(cloned_lyric)
283
+ try:
284
+ from app.routers.alabanza import broadcast_to_group
285
+ broadcast_to_group(edit_context_group_id, {"type": "group_updated", "grupo_id": edit_context_group_id}, db)
286
+ except Exception:
287
+ pass
288
+ return cloned_lyric
289
+
290
+ # --- CASO 3: Edición desde el Catálogo Personal (Creador original) ---
291
+ else:
292
+ has_shared_references = len(db_lyric.groups_shared) > 0
293
+ if has_shared_references and update_references is False:
294
+ # Clonar la letra original con el contenido actual (viejo) para los grupos y repertorios
295
+ old_cloned_lyric = Lyric(
296
+ title=db_lyric.title,
297
+ artist=db_lyric.artist,
298
+ content=db_lyric.content,
299
+ key=db_lyric.key,
300
+ bpm=db_lyric.bpm,
301
+ transpose_offset=db_lyric.transpose_offset,
302
+ group_id=db_lyric.group_id,
303
+ created_by=db_lyric.created_by,
304
+ is_repertoire_only=False,
305
+ parent_lyric_id=db_lyric.id
306
+ )
307
+ shared_groups = list(db_lyric.groups_shared)
308
+ for g in shared_groups:
309
+ old_cloned_lyric.groups_shared.append(g)
310
+ db.add(old_cloned_lyric)
311
+ db.flush()
312
+
313
+ # Desvincular original de los grupos
314
+ db_lyric.groups_shared.clear()
315
+ db_lyric.group_id = None
316
+
317
+ # Actualizar referencias de repertorios de esos grupos para que usen el clon viejo
318
+ shared_group_ids = [g.id for g in shared_groups]
319
+ if shared_group_ids:
320
+ group_rep_ids = [r.id for r in db.query(Repertorio.id).filter(Repertorio.group_id.in_(shared_group_ids)).all()]
321
+ if group_rep_ids:
322
+ rep_lyrics = db.query(RepertorioLyric).filter(
323
+ RepertorioLyric.repertorio_id.in_(group_rep_ids),
324
+ RepertorioLyric.lyric_id == db_lyric.id
325
+ ).all()
326
+ for rl in rep_lyrics:
327
+ order = rl.order
328
+ rep_id = rl.repertorio_id
329
+ db.delete(rl)
330
+ db.flush()
331
+ new_rl = RepertorioLyric(
332
+ repertorio_id=rep_id,
333
+ lyric_id=old_cloned_lyric.id,
334
+ order=order
335
+ )
336
+ db.add(new_rl)
337
+
338
+ # Guardar los nuevos cambios en la original (personal)
339
+ for key, value in lyric_update.dict().items():
340
+ if key != "group_id":
341
+ setattr(db_lyric, key, value)
342
+
343
+ db.commit()
344
+ db.refresh(db_lyric)
345
+
346
+ for gid in shared_group_ids:
347
+ try:
348
+ from app.routers.alabanza import broadcast_to_group
349
+ broadcast_to_group(gid, {"type": "group_updated", "grupo_id": gid}, db)
350
+ except Exception:
351
+ pass
352
+ return db_lyric
353
+ else:
354
+ # Edición directa (in-place)
355
+ for key, value in lyric_update.dict().items():
356
+ setattr(db_lyric, key, value)
357
+
358
+ if db_lyric.group_id is not None:
359
+ group = db.query(Group).filter(Group.id == db_lyric.group_id).first()
360
+ if group and group not in db_lyric.groups_shared:
361
+ db_lyric.groups_shared.append(group)
362
+
363
+ db.commit()
364
+ db.refresh(db_lyric)
365
+ for gid in db_lyric.group_ids:
366
+ try:
367
+ from app.routers.alabanza import broadcast_to_group
368
+ broadcast_to_group(gid, {"type": "group_updated", "grupo_id": gid}, db)
369
+ except Exception:
370
+ pass
371
+ return db_lyric
372
 
373
  @router.delete("/{lyric_id}")
374
  def delete_lyric(lyric_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
app/schemas/lyrics.py CHANGED
@@ -11,6 +11,8 @@ class LyricBase(BaseModel):
11
  bpm: Optional[int] = None
12
  group_id: Optional[int] = None
13
  transpose_offset: Optional[int] = 0
 
 
14
 
15
  class LyricCreate(LyricBase):
16
  pass
 
11
  bpm: Optional[int] = None
12
  group_id: Optional[int] = None
13
  transpose_offset: Optional[int] = 0
14
+ is_repertoire_only: Optional[bool] = False
15
+ parent_lyric_id: Optional[int] = None
16
 
17
  class LyricCreate(LyricBase):
18
  pass
migrate_contextual_lyrics.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import psycopg2
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ # Usar DATABASE_URL del archivo .env
8
+ DATABASE_URL = os.getenv("DATABASE_URL")
9
+
10
+ # Fallback
11
+ if not DATABASE_URL:
12
+ DATABASE_URL = "postgresql://postgres.egkydqberhebzkqtltic:Isaac%23105%2Amay%40@aws-1-us-east-1.pooler.supabase.com:6543/postgres"
13
+
14
+ def migrate():
15
+ print("Conectando a la base de datos...")
16
+ conn = psycopg2.connect(DATABASE_URL)
17
+ cur = conn.cursor()
18
+
19
+ print("Agregando columnas 'is_repertoire_only' y 'parent_lyric_id' a la tabla 'lyrics'...")
20
+ try:
21
+ cur.execute("ALTER TABLE lyrics ADD COLUMN IF NOT EXISTS is_repertoire_only BOOLEAN DEFAULT FALSE;")
22
+ cur.execute("ALTER TABLE lyrics ADD COLUMN IF NOT EXISTS parent_lyric_id INTEGER NULL REFERENCES lyrics(id) ON DELETE SET NULL;")
23
+ conn.commit()
24
+ print("Columnas agregadas exitosamente.")
25
+ except Exception as e:
26
+ print("Error al agregar columnas:", e)
27
+ conn.rollback()
28
+
29
+ cur.close()
30
+ conn.close()
31
+
32
+ if __name__ == "__main__":
33
+ migrate()