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 = (
76 db.query(User)
77 .join(User.services)
78 .where(Service.id == service.id)
79 .filter(User.removed != True)
80 )
81
82 return paginate(query)
83
84
85@router.get("/{id}/inbounds", response_model=Page[Inbound])
86def get_service_inbounds(id: int, db: DBDep, admin: SudoAdminDep):
87 """
88 Get service inbounds
89 """
90 service = crud.get_service(db, id)
91
92 if not service:
93 raise HTTPException(status_code=404, detail="Service not found")
94
95 query = (
96 db.query(Inbound)
97 .join(Inbound.services)
98 .where(Service.id == service.id)
99 )
100
101 return paginate(query)
102
103
104@router.put("/{id}", response_model=ServiceResponse)
105async def modify_service(
106 id: int, modification: ServiceModify, db: DBDep, admin: SudoAdminDep
107):
108 """
109 Modify Service
110
111 - **name** can be up to 64 characters
112 - **inbounds** list of inbound ids. if not specified no change will be applied;
113 in case of an empty list all inbounds would be removed.
114 """
115 dbservice = crud.get_service(db, id)
116 if not dbservice:
117 raise HTTPException(status_code=404, detail="Service not found")
118 old_inbounds = {(i.node_id, i.protocol, i.tag) for i in dbservice.inbounds}
119 try:
120 response = crud.update_service(db, dbservice, modification)
121 except sqlalchemy.exc.IntegrityError:
122 db.rollback()
123 raise HTTPException(
124 status_code=409, detail="problem updating the service"
125 )
126 else:
127 for user in response.users:
128 if user.activated:
129 marznode.operations.update_user(
130 user, old_inbounds=old_inbounds
131 )
132 return response
133
134
135@router.delete("/{id}")
136def remove_service(id: int, db: DBDep, admin: SudoAdminDep):
137 dbservice = crud.get_service(db, id)
138 if not dbservice:
139 raise HTTPException(status_code=404, detail="Service not found")
140
141 crud.remove_service(db, dbservice)
142 return dict()