1import sqlalchemy
2from fastapi import APIRouter, Query
3from fastapi import HTTPException
4from fastapi_pagination.ext.sqlalchemy import paginate
5from fastapi_pagination.links import Page
6
7from app import marznode
8from app.db import crud
9from app.db.models import Service, User
10from app.dependencies import DBDep, AdminDep, SudoAdminDep
11from app.models.proxy import Inbound
12from app.models.service import ServiceCreate, ServiceModify, ServiceResponse
13from app.models.user import UserResponse
14
15router = APIRouter(prefix="/services", tags=["Service"])
16
17
18@router.get("", response_model=Page[ServiceResponse])
19def get_services(db: DBDep, admin: AdminDep, name: str = Query(None)):
20 query = db.query(Service)
21
22 if name:
23 query = query.filter(Service.name.ilike(f"%{name}%"))
24
25 if not admin.is_sudo and not admin.all_services_access:
26 query = query.filter(Service.id.in_(admin.service_ids))
27
28 return paginate(query)
29
30
31@router.post("", response_model=ServiceResponse)
32def add_service(new_service: ServiceCreate, db: DBDep, admin: SudoAdminDep):
33 """
34 Add a new service
35
36 - **name** service name
37 - **inbounds** list of inbound ids
38 """
39 try:
40 return crud.create_service(db, new_service)
41 except sqlalchemy.exc.IntegrityError:
42 db.rollback()
43 raise HTTPException(
44 status_code=409, detail="Service by this name already exists"
45 )
46
47
48@router.get("/{id}", response_model=ServiceResponse)
49def get_service(id: int, db: DBDep, admin: AdminDep):
50 """
51 Get Service information with id
52 """
53 dbservice = crud.get_service(db, id)
54 if not dbservice:
55 raise HTTPException(status_code=404, detail="Service not found")
56
57 if not (
58 admin.is_sudo or admin.all_services_access or id in admin.service_ids
59 ):
60 raise HTTPException(status_code=403, detail="You're not allowed")
61
62 return dbservice
63
64
65@router.get("/{id}/users", response_model=Page[UserResponse])
66def get_service_users(id: int, db: DBDep, admin: SudoAdminDep):
67 """
68 Get service users
69 """
70 service = crud.get_service(db, id)
71
72 if not service:
73 raise HTTPException(status_code=404, detail="Service not found")
74
75 query = db.query(User).join(User.services).where(Service.id == service.id)
76
77 return paginate(query)
78
79
80@router.get("/{id}/inbounds", response_model=Page[Inbound])
81def get_service_inbounds(id: int, db: DBDep, admin: SudoAdminDep):
82 """
83 Get service inbounds
84 """
85 service = crud.get_service(db, id)
86
87 if not service:
88 raise HTTPException(status_code=404, detail="Service not found")
89
90 query = (
91 db.query(Inbound)
92 .join(Inbound.services)
93 .where(Service.id == service.id)
94 )
95
96 return paginate(query)
97
98
99@router.put("/{id}", response_model=ServiceResponse)
100async def modify_service(
101 id: int, modification: ServiceModify, db: DBDep, admin: SudoAdminDep
102):
103 """
104 Modify Service
105
106 - **name** can be up to 64 characters
107 - **inbounds** list of inbound ids. if not specified no change will be applied;
108 in case of an empty list all inbounds would be removed.
109 """
110 dbservice = crud.get_service(db, id)
111 if not dbservice:
112 raise HTTPException(status_code=404, detail="Service not found")
113 old_inbounds = {(i.node_id, i.protocol, i.tag) for i in dbservice.inbounds}
114 try:
115 response = crud.update_service(db, dbservice, modification)
116 except sqlalchemy.exc.IntegrityError:
117 db.rollback()
118 raise HTTPException(
119 status_code=409, detail="problem updating the service"
120 )
121 else:
122 for user in response.users:
123 if user.activated:
124 marznode.operations.update_user(
125 user, old_inbounds=old_inbounds
126 )
127 return response
128
129
130@router.delete("/{id}")
131def remove_service(id: int, db: DBDep, admin: SudoAdminDep):
132 dbservice = crud.get_service(db, id)
133 if not dbservice:
134 raise HTTPException(status_code=404, detail="Service not found")
135
136 crud.remove_service(db, dbservice)
137 return dict()