Skip to content

Commit

Permalink
Refactor(docat): Apply Requests from PR, improve Tests
Browse files Browse the repository at this point in the history
Docat now only creates the folders on startup, which has simplified the
tests. The Search tests now also test for file results (content and name
matches).
Models with internal uses were renamed-
  • Loading branch information
reglim committed Nov 22, 2022
1 parent acd213b commit 6bc6e80
Show file tree
Hide file tree
Showing 13 changed files with 676 additions and 220 deletions.
26 changes: 5 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,10 @@ you can optionally use volumes to persist state:
```sh
# run container in background and persist data (docs, nginx configs and tokens database as well as the content index)
# use 'ghcr.io/docat-org/docat:unstable' to get the latest changes
mkdir -p docat-run/db && touch docat-run/db/db.json && touch docat-run/db/index.json
mkdir -p docat-run/
docker run \
--detach \
--volume $PWD/docat-run/doc:/var/docat/doc/ \
--volume $PWD/docat-run/db/:/app/docat/ \
--publish 8000:80 \
ghcr.io/docat-org/docat
```

*Alternative:* Mount a dedicated directory to host `db.json` and `index.json`:

```sh
# run container in background and persist data (docs, nginx configs and tokens database as well as the content index)
# use 'ghcr.io/docat-org/docat:unstable' to get the latest changes
mkdir -p docat-run/db && touch docat-run/db/db.json && touch docat-run/db/index.json
docker run \
--detach \
--volume $PWD/docat-run/doc:/var/docat/doc/ \
--volume $PWD/docat-run/db:/var/docat/db/ \
--env DOCAT_DB_DIR=/var/docat/db/
--volume $PWD/docat-run/doc:/var/docat/ \
--publish 8000:80 \
ghcr.io/docat-org/docat
```
Expand All @@ -53,7 +37,7 @@ DEV_DOC_PATH="$(mktemp -d)"
poetry install

# run the local development version
DOCAT_SERVE_FILES=1 DOCAT_INDEX_FILES=1 DOCAT_DOC_PATH="$DEV_DOC_PATH" poetry run python -m docat
DOCAT_SERVE_FILES=1 DOCAT_DOC_PATH="$DEV_DOC_PATH" poetry run python -m docat
```

After this you need to start the frontend (inside the `web/` folder):
Expand Down Expand Up @@ -116,13 +100,13 @@ It is possible to configure some things after the fact.

Supported config options:

* headerHTML
- headerHTML

## Advanced Usage

### Hide Controls

If you would like to send link to a specific version of the documentation without the option to change the version, you can do so by clicking on the `Hide Controls` button. This will hide the control buttons and change the link, which can then be copied as usual.
If you would like to send link to a specific version of the documentation without the option to change the version, you can do so by clicking on the `Hide Controls` button. This will hide the control buttons and change the link, which can then be copied as usual.

### Indexing

Expand Down
2 changes: 1 addition & 1 deletion docat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ poetry install

* **DOCAT_SERVE_FILES**: Serve static documentation instead of a nginx (for testing)
* **DOCAT_INDEX_FILES**: Index files on start for searching
* **DOCAT_DOC_PATH**: Upload directory for static files (needs to match nginx config)
* **DOCAT_STORAGE_PATH**: Upload directory for static files (needs to match nginx config)
* **FLASK_DEBUG**: Start flask in debug mode

## Usage
Expand Down
66 changes: 30 additions & 36 deletions docat/docat/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import secrets
import shutil
from pathlib import Path
from typing import Optional, Tuple
from typing import Optional

import magic
from fastapi import Depends, FastAPI, File, Header, Response, UploadFile, status
Expand All @@ -22,8 +22,8 @@
from docat.models import (
ApiResponse,
ClaimResponse,
ProjectDetailResponse,
ProjectsResponse,
ProjectDetail,
Projects,
SearchResponse,
SearchResultFile,
SearchResultProject,
Expand Down Expand Up @@ -56,36 +56,23 @@
redoc_url="/api/redoc",
)

DOCAT_DB_DIR_STR = os.getenv("DOCAT_DB_DIR")
DOCAT_STORAGE_PATH = Path(os.getenv("DOCAT_STORAGE_PATH") or Path("/var/docat"))
DOCAT_DB_PATH = DOCAT_STORAGE_PATH / DB_PATH
DOCAT_INDEX_PATH = DOCAT_STORAGE_PATH / INDEX_PATH
DOCAT_UPLOAD_FOLDER = DOCAT_STORAGE_PATH / UPLOAD_FOLDER

if not DOCAT_DB_DIR_STR:
# Default Database locations
DOCAT_DB_DIR = Path.cwd()
DOCAT_DB_PATH = Path(DB_PATH)
DOCAT_INDEX_PATH = Path(INDEX_PATH)
else:
# Custom Database locations
DOCAT_DB_DIR = Path(DOCAT_DB_DIR_STR)
DOCAT_DB_PATH = DOCAT_DB_DIR / "db.json"
DOCAT_INDEX_PATH = DOCAT_DB_DIR / "index.json"


DOCAT_DB_DIR.mkdir(parents=True, exist_ok=True)
DOCAT_DB_PATH.touch()
DOCAT_INDEX_PATH.touch()

#: Holds the static base path where the uploaded documentation artifacts are stored
DOCAT_UPLOAD_FOLDER = Path(os.getenv("DOCAT_DOC_PATH", UPLOAD_FOLDER))

if not DOCAT_DB_PATH.exists():
@app.on_event("startup")
def startup_create_folders():
# Create the folders if they don't exist
DOCAT_UPLOAD_FOLDER.mkdir(parents=True, exist_ok=True)

db = TinyDB(DOCAT_DB_PATH)
DOCAT_DB_PATH.touch()
DOCAT_INDEX_PATH.touch()


def get_db():
"""Return the cached TinyDB instance."""
return db
return TinyDB(DOCAT_DB_PATH)


@app.post("/api/index/update", response_model=ApiResponse, status_code=status.HTTP_200_OK)
Expand All @@ -95,22 +82,22 @@ def update_index():
return ApiResponse(message="Successfully updated search index")


@app.get("/api/projects", response_model=ProjectsResponse, status_code=status.HTTP_200_OK)
@app.get("/api/projects", response_model=Projects, status_code=status.HTTP_200_OK)
def get_projects():
if not DOCAT_UPLOAD_FOLDER.exists():
return ProjectsResponse(projects=[])
return Projects(projects=[])
return get_all_projects(DOCAT_UPLOAD_FOLDER)


@app.get(
"/api/projects/{project}",
response_model=ProjectDetailResponse,
response_model=ProjectDetail,
status_code=status.HTTP_200_OK,
responses={status.HTTP_404_NOT_FOUND: {"model": ApiResponse}},
)
@app.get(
"/api/projects/{project}/",
response_model=ProjectDetailResponse,
response_model=ProjectDetail,
status_code=status.HTTP_200_OK,
responses={status.HTTP_404_NOT_FOUND: {"model": ApiResponse}},
)
Expand All @@ -127,14 +114,14 @@ def get_project(project):
@app.get("/api/search/", response_model=SearchResponse, status_code=status.HTTP_200_OK)
def search(query: str):
query = query.lower()
found_projects: list[SearchResultProject] = list()
found_versions: list[SearchResultVersion] = list()
found_files: list[SearchResultFile] = list()
found_projects: list[SearchResultProject] = []
found_versions: list[SearchResultVersion] = []
found_files: list[SearchResultFile] = []

index_db = TinyDB(DOCAT_INDEX_PATH)
project_table = index_db.table("projects")
projects = project_table.all()
all_versions: list[Tuple] = list()
all_versions: list[tuple] = []

# Collect all projects that contain the query
for project in projects:
Expand Down Expand Up @@ -271,6 +258,9 @@ def hide_version(
with open(hidden_file, "w") as f:
f.close()

update_version_index_for_project(DOCAT_UPLOAD_FOLDER, DOCAT_INDEX_PATH, project)
remove_file_index_from_db(DOCAT_INDEX_PATH, project, version)

return ApiResponse(message=f"Version {version} is now hidden")


Expand Down Expand Up @@ -306,6 +296,9 @@ def show_version(

os.remove(hidden_file)

update_version_index_for_project(DOCAT_UPLOAD_FOLDER, DOCAT_INDEX_PATH, project)
update_file_index_for_project_version(DOCAT_UPLOAD_FOLDER, DOCAT_INDEX_PATH, project, version)

return ApiResponse(message=f"Version {version} is now shown")


Expand All @@ -331,7 +324,7 @@ def upload(
if base_path.exists():
token_status = check_token_for_project(db, docat_api_key, project)
if token_status.valid:
remove_docs(project, version)
remove_docs(project, version, DOCAT_UPLOAD_FOLDER)
else:
response.status_code = status.HTTP_401_UNAUTHORIZED
return ApiResponse(message=token_status.reason)
Expand Down Expand Up @@ -441,7 +434,7 @@ def rename(project: str, new_project_name: str, response: Response, docat_api_ke
def delete(project: str, version: str, response: Response, docat_api_key: str = Header(None), db: TinyDB = Depends(get_db)):
token_status = check_token_for_project(db, docat_api_key, project)
if token_status.valid:
message = remove_docs(project, version)
message = remove_docs(project, version, DOCAT_UPLOAD_FOLDER)
if message:
response.status_code = status.HTTP_404_NOT_FOUND
return ApiResponse(message=message)
Expand Down Expand Up @@ -471,6 +464,7 @@ def check_token_for_project(db, token, project) -> TokenStatus:

# serve_local_docs for local testing without a nginx
if os.environ.get("DOCAT_SERVE_FILES"):
DOCAT_UPLOAD_FOLDER.mkdir(parents=True, exist_ok=True)
app.mount("/doc", StaticFiles(directory=DOCAT_UPLOAD_FOLDER, html=True), name="docs")

# index local files on start
Expand Down
7 changes: 3 additions & 4 deletions docat/docat/models.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from dataclasses import dataclass
from typing import Optional

from pydantic import BaseModel


@dataclass(frozen=True)
class TokenStatus:
valid: bool
reason: Optional[str] = None
reason: str | None = None


class ApiResponse(BaseModel):
Expand All @@ -18,7 +17,7 @@ class ClaimResponse(ApiResponse):
token: str


class ProjectsResponse(BaseModel):
class Projects(BaseModel):
projects: list[str]


Expand All @@ -27,7 +26,7 @@ class ProjectVersion(BaseModel):
tags: list[str]


class ProjectDetailResponse(BaseModel):
class ProjectDetail(BaseModel):
name: str
versions: list[ProjectVersion]

Expand Down
Loading

0 comments on commit 6bc6e80

Please sign in to comment.