Changes log:

feat: add user URI API endpoint
feat: integrate show_user_uri_api in users page
refactor: remove URI generation from viewmodel
This commit is contained in:
Whispering Wind
2025-04-19 18:18:01 +03:30
committed by GitHub
parent 325c130fd0
commit fb221d350e
4 changed files with 133 additions and 153 deletions

View File

@ -1,4 +1,4 @@
from pydantic import BaseModel
from typing import Optional
from pydantic import BaseModel, RootModel
@ -37,3 +37,9 @@ class EditUserInputBody(BaseModel):
renew_password: bool = False
renew_creation_date: bool = False
blocked: bool = False
class UserUriResponse(BaseModel):
username: str
ipv4: str | None = None
ipv6: str | None = None
normal_sub: str | None = None

View File

@ -150,11 +150,33 @@ async def reset_user_api(username: str):
except Exception as e:
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
# TODO implement show user uri endpoint
# @router.get('/{username}/uri', response_model=TODO)
# async def show_user_uri(username: str):
# try:
# res = cli_api.show_user_uri(username)
# return res
# except Exception as e:
# raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
@router.get('/{username}/uri', response_model=UserUriResponse)
async def show_user_uri_api(username: str):
"""
Get the URI information for a user in JSON format.
Args:
username: The username of the user.
Returns:
UserUriResponse: An object containing URI information for the user.
Raises:
HTTPException: 404 if the user is not found, 400 if another error occurs.
"""
try:
uri_data_list = cli_api.show_user_uri_json([username])
if not uri_data_list:
raise HTTPException(status_code=404, detail=f'URI for user {username} not found.')
uri_data = uri_data_list[0]
if uri_data.get('error'):
raise HTTPException(status_code=404, detail=f"{uri_data['error']}")
return uri_data
except cli_api.ScriptNotFoundError as e:
raise HTTPException(status_code=500, detail=f'Server script error: {str(e)}')
except cli_api.CommandExecutionError as e:
raise HTTPException(status_code=400, detail=f'Error executing script: {str(e)}')
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=400, detail=f'Unexpected error: {str(e)}')

View File

@ -4,57 +4,6 @@ from datetime import datetime, timedelta
import cli_api
class Config(BaseModel):
type: str
link: str
@staticmethod
def from_username(username: str) -> list['Config']:
raw_uri = Config.__get_user_configs_uri(username)
if not raw_uri:
return []
res = []
for line in raw_uri.splitlines():
config = Config.__parse_user_configs_uri_line(line)
if config:
res.append(config)
return res
@staticmethod
def __get_user_configs_uri(username: str) -> str:
# This command is equivalent to `show-user-uri --username $username --ipv 4 --all --singbox --normalsub`
raw_uri = cli_api.show_user_uri(username, False, 4, True, True, True)
return raw_uri.strip() if raw_uri else ''
@staticmethod
def __parse_user_configs_uri_line(line: str) -> "Config | None":
config_type = ''
config_link = ''
line = line.strip()
if line.startswith("hy2://"):
if "@" in line:
ip_version = "IPv6" if line.split("@")[1].count(":") > 1 else "IPv4"
config_type = ip_version
config_link = line
else:
return None
elif line.startswith("https://"):
if "singbox" in line.lower():
config_type = "Singbox"
elif "normal" in line.lower():
config_type = "Normal-SUB"
else:
return None
config_link = line
else:
return None
return Config(type=config_type, link=config_link)
class User(BaseModel):
username: str
status: str
@ -63,7 +12,6 @@ class User(BaseModel):
expiry_date: datetime
expiry_days: int
enable: bool
configs: list[Config]
@staticmethod
def from_dict(username: str, user_data: dict):
@ -107,7 +55,6 @@ class User(BaseModel):
'expiry_date': expiry_date,
'expiry_days': expiration_days,
'enable': False if user_data.get('blocked', False) else True,
'configs': Config.from_username(user_data['username'])
}
@staticmethod

View File

@ -114,18 +114,6 @@
data-username="{{ user.username }}">
<i class="fas fa-qrcode"></i>
</a>
<div id="userConfigs-{{ user.username }}" style="display: none;">
{% for config in user.configs %}
<div class="config-container" data-link="{{ config.link }}">
<span class="config-type">{{ config.type }}:</span>
{% if config.type == "Singbox" or config.type == "Normal-SUB" %}
<span class="config-link-text">{{ config.link }}</span>
{% else %}
<span class="config-link-text">{{ config.link }}</span>
{% endif %}
</div>
{% endfor %}
</div>
</td>
<td class="text-nowrap">
<button type="button" class="btn btn-sm btn-info edit-user"
@ -653,28 +641,31 @@
// QR Code Modal
$("#qrcodeModal").on("show.bs.modal", function (event) {
const button = $(event.relatedTarget);
const configContainer = $(`#userConfigs-${button.data("username")}`);
const username = button.data("username");
const qrcodesContainer = $("#qrcodesContainer");
qrcodesContainer.empty();
configContainer.find(".config-container").each(function () {
const configLink = $(this).data("link");
const configType = $(this).find(".config-type").text().replace(":", "");
const userUriApiUrl = "{{ url_for('show_user_uri_api', username='USERNAME_PLACEHOLDER') }}";
const url = userUriApiUrl.replace("USERNAME_PLACEHOLDER", encodeURIComponent(username));
let displayType = configType;
$.ajax({
url: url,
method: "GET",
dataType: 'json',
success: function (response) {
// console.log("API Response:", response);
const hashMatch = configLink.match(/#(.+)$/);
if (hashMatch && hashMatch[1]) {
const hashValue = hashMatch[1];
if (hashValue.includes("IPv4") || hashValue.includes("IPv6")) {
displayType = hashValue;
}
} else if (configLink.includes("ipv4") || configLink.includes("IPv4")) {
displayType = "IPv4";
} else if (configLink.includes("ipv6") || configLink.includes("IPv6")) {
displayType = "IPv6";
}
const configs = [
{ type: "IPv4", link: response.ipv4 },
{ type: "IPv6", link: response.ipv6 },
{ type: "Normal-SUB", link: response.normal_sub }
];
configs.forEach(config => {
if (config.link) {
const displayType = config.type;
const configLink = config.link;
const qrCodeId = `qrcode-${displayType}-${Math.random().toString(36).substring(2, 10)}`;
const card = $(`
@ -686,7 +677,6 @@
</div>
</div>
`);
qrcodesContainer.append(card);
const qrCodeStyling = new QRCodeStyling({
@ -709,7 +699,6 @@
hideBackgroundDots: true,
}
});
qrCodeStyling.append(document.getElementById(qrCodeId));
card.on("click", function () {
@ -731,7 +720,23 @@
});
});
});
}
});
},
error: function (error) {
console.error("Error fetching user URI:", error);
Swal.fire({
title: "Error!",
text: "Failed to fetch user configuration URIs.",
icon: "error",
confirmButtonText: "OK",
});
}
});
});
$("#qrcodeModal .modal-content").on("click", function (e) {