Skip to content

Commit 2d77299

Browse files
committed
Improvement: Integrate Versions into Projects API
Now we don't have to load any data for each project, greatly reducing the number of requests and improving responsiveness. We now also return hidden versions, but with the property "hidden" to distinguish them in the UI, which fixes a bug where you couldn't look at hidden versions because they weren't found, but they aren't returned by default. fixes: #367
1 parent 5eadd16 commit 2d77299

17 files changed

+356
-143
lines changed

docat/docat/app.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ def update_index(index_db: TinyDB = Depends(get_index_db)):
8888

8989

9090
@app.get("/api/projects", response_model=Projects, status_code=status.HTTP_200_OK)
91-
def get_projects():
91+
def get_projects(include_hidden: bool = False):
9292
if not DOCAT_UPLOAD_FOLDER.exists():
9393
return Projects(projects=[])
94-
return get_all_projects(DOCAT_UPLOAD_FOLDER)
94+
return get_all_projects(DOCAT_UPLOAD_FOLDER, include_hidden)
9595

9696

9797
@app.get(
@@ -106,8 +106,8 @@ def get_projects():
106106
status_code=status.HTTP_200_OK,
107107
responses={status.HTTP_404_NOT_FOUND: {"model": ApiResponse}},
108108
)
109-
def get_project(project):
110-
details = get_project_details(DOCAT_UPLOAD_FOLDER, project)
109+
def get_project(project, include_hidden: bool = False):
110+
details = get_project_details(DOCAT_UPLOAD_FOLDER, project, include_hidden)
111111

112112
if not details:
113113
return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content={"message": f"Project {project} does not exist"})

docat/docat/models.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,20 @@ class ClaimResponse(ApiResponse):
1717
token: str
1818

1919

20-
class ProjectWithVersionCount(BaseModel):
20+
class ProjectVersion(BaseModel):
2121
name: str
22-
logo: bool
23-
versions: int
22+
tags: list[str]
23+
hidden: bool
2424

2525

26-
class Projects(BaseModel):
27-
projects: list[ProjectWithVersionCount]
26+
class Project(BaseModel):
27+
name: str
28+
logo: bool
29+
versions: list[ProjectVersion]
2830

2931

30-
class ProjectVersion(BaseModel):
31-
name: str
32-
tags: list[str]
32+
class Projects(BaseModel):
33+
projects: list[Project]
3334

3435

3536
class ProjectDetail(BaseModel):

docat/docat/utils.py

+27-24
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from bs4.element import Comment
1212
from tinydb import Query, TinyDB
1313

14-
from docat.models import ProjectDetail, Projects, ProjectVersion, ProjectWithVersionCount
14+
from docat.models import Project, ProjectDetail, Projects, ProjectVersion
1515

1616
NGINX_CONFIG_PATH = Path("/etc/nginx/locations.d")
1717
UPLOAD_FOLDER = "doc"
@@ -109,36 +109,32 @@ def is_forbidden_project_name(name: str) -> bool:
109109
return name in ["upload", "claim", "delete", "search", "help"]
110110

111111

112-
def get_all_projects(upload_folder_path: Path) -> Projects:
112+
def get_all_projects(upload_folder_path: Path, include_hidden: bool) -> Projects:
113113
"""
114114
Returns all projects in the upload folder.
115115
"""
116+
projects: list[Project] = []
116117

117-
def count_not_hidden_versions(project) -> int:
118-
path = upload_folder_path / project
119-
versions = [
120-
version
121-
for version in (upload_folder_path / project).iterdir()
122-
if (path / version).is_dir() and not (path / version).is_symlink() and not (path / version / ".hidden").exists()
123-
]
124-
return len(versions)
118+
for project in upload_folder_path.iterdir():
119+
if not project.is_dir():
120+
continue
125121

126-
projects: list[ProjectWithVersionCount] = []
122+
details = get_project_details(upload_folder_path, project.name, include_hidden)
127123

128-
for project in upload_folder_path.iterdir():
129-
if project.is_dir():
130-
versions = count_not_hidden_versions(project)
131-
if versions < 1:
132-
continue
124+
if details is None:
125+
continue
133126

134-
project_name = str(project.relative_to(upload_folder_path))
135-
project_has_logo = (upload_folder_path / project / "logo").exists()
136-
projects.append(ProjectWithVersionCount(name=project_name, logo=project_has_logo, versions=versions))
127+
if len(details.versions) < 1:
128+
continue
129+
130+
project_name = str(project.relative_to(upload_folder_path))
131+
project_has_logo = (upload_folder_path / project / "logo").exists()
132+
projects.append(Project(name=project_name, logo=project_has_logo, versions=details.versions))
137133

138134
return Projects(projects=projects)
139135

140136

141-
def get_project_details(upload_folder_path: Path, project_name: str) -> ProjectDetail | None:
137+
def get_project_details(upload_folder_path: Path, project_name: str, include_hidden: bool) -> ProjectDetail | None:
142138
"""
143139
Returns all versions and tags for a project.
144140
"""
@@ -149,16 +145,23 @@ def get_project_details(upload_folder_path: Path, project_name: str) -> ProjectD
149145

150146
tags = [x for x in docs_folder.iterdir() if x.is_dir() and x.is_symlink()]
151147

148+
def should_include(name: str) -> bool:
149+
if include_hidden:
150+
return True
151+
152+
return not (docs_folder / name / ".hidden").exists()
153+
152154
return ProjectDetail(
153155
name=project_name,
154156
versions=sorted(
155157
[
156158
ProjectVersion(
157159
name=str(x.relative_to(docs_folder)),
158160
tags=[str(t.relative_to(docs_folder)) for t in tags if t.resolve() == x],
161+
hidden=(docs_folder / x.name / ".hidden").exists(),
159162
)
160163
for x in docs_folder.iterdir()
161-
if x.is_dir() and not x.is_symlink() and not (docs_folder / x.name / ".hidden").exists()
164+
if x.is_dir() and not x.is_symlink() and should_include(x.name)
162165
],
163166
key=lambda k: k.name,
164167
reverse=True,
@@ -179,7 +182,7 @@ def index_all_projects(
179182
index_db.table("projects")
180183
index_db.table("files")
181184

182-
all_projects = get_all_projects(upload_folder_path).projects
185+
all_projects = get_all_projects(upload_folder_path, include_hidden=False).projects
183186

184187
for project in all_projects:
185188
update_version_index_for_project(upload_folder_path, index_db, project.name)
@@ -193,7 +196,7 @@ def update_file_index_for_project(upload_folder_path: Path, index_db: TinyDB, pr
193196
files_table = index_db.table("files")
194197
files_table.remove(Query().project == project)
195198

196-
project_details = get_project_details(upload_folder_path, project)
199+
project_details = get_project_details(upload_folder_path, project, include_hidden=False)
197200

198201
if not project_details:
199202
return
@@ -233,7 +236,7 @@ def update_version_index_for_project(upload_folder_path: Path, index_db: TinyDB,
233236
Project = Query()
234237
project_table.remove(Project.name == project)
235238

236-
details = get_project_details(upload_folder_path, project)
239+
details = get_project_details(upload_folder_path, project, include_hidden=False)
237240

238241
if not details:
239242
return

docat/tests/test_hide_show.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
def test_hide(client_with_claimed_project):
88
"""
9-
Tests that the version is no longer returned when getting the details after hiding
9+
Tests that the version is marked as hidden when getting the details after hiding
1010
"""
1111
# create a version
1212
create_response = client_with_claimed_project.post(
@@ -19,7 +19,7 @@ def test_hide(client_with_claimed_project):
1919
assert project_details_response.status_code == 200
2020
assert project_details_response.json() == {
2121
"name": "some-project",
22-
"versions": [{"name": "1.0.0", "tags": []}],
22+
"versions": [{"name": "1.0.0", "tags": [], "hidden": False}],
2323
}
2424

2525
# hide the version
@@ -50,7 +50,7 @@ def test_hide_only_version_not_listed_in_projects(client_with_claimed_project):
5050
projects_response = client_with_claimed_project.get("/api/projects")
5151
assert projects_response.status_code == 200
5252
assert projects_response.json() == {
53-
"projects": [{"name": "some-project", "logo": False, "versions": 1}],
53+
"projects": [{"name": "some-project", "logo": False, "versions": [{"name": "1.0.0", "tags": [], "hidden": False}]}],
5454
}
5555

5656
# hide the only version
@@ -189,7 +189,7 @@ def test_hide_fails_invalid_token(client_with_claimed_project):
189189

190190
def test_show(client_with_claimed_project):
191191
"""
192-
Tests that the version is returned again after requesting show.
192+
Tests that the version is no longer marked as hidden after requesting show.
193193
"""
194194
# create a version
195195
create_response = client_with_claimed_project.post(
@@ -220,7 +220,7 @@ def test_show(client_with_claimed_project):
220220
assert project_details_response.status_code == 200
221221
assert project_details_response.json() == {
222222
"name": "some-project",
223-
"versions": [{"name": "1.0.0", "tags": []}],
223+
"versions": [{"name": "1.0.0", "tags": [], "hidden": False}],
224224
}
225225

226226

@@ -356,7 +356,7 @@ def test_show_fails_invalid_token(client_with_claimed_project):
356356

357357
def test_hide_and_show_with_tag(client_with_claimed_project):
358358
"""
359-
Tests that the version is returned again after requesting show on a tag.
359+
Tests that the version is no longer marked as hidden after requesting show on a tag.
360360
"""
361361
# create a version
362362
create_response = client_with_claimed_project.post(
@@ -392,5 +392,5 @@ def test_hide_and_show_with_tag(client_with_claimed_project):
392392
assert project_details_response.status_code == 200
393393
assert project_details_response.json() == {
394394
"name": "some-project",
395-
"versions": [{"name": "1.0.0", "tags": ["latest"]}],
395+
"versions": [{"name": "1.0.0", "tags": ["latest"], "hidden": False}],
396396
}

0 commit comments

Comments
 (0)