Skip to content

Commit

Permalink
Merge pull request #351 from docat-org/324-migrate-to-react
Browse files Browse the repository at this point in the history
Migrate to react
  • Loading branch information
randombenj authored Dec 19, 2022
2 parents a8d7cab + 37c2882 commit 23d46b7
Show file tree
Hide file tree
Showing 94 changed files with 9,818 additions and 8,645 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ docat/__pycache__
docat/upload
docat/.tox
web/node_modules
web/build
web/.env*
2 changes: 1 addition & 1 deletion .github/workflows/docat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:

- name: run test suite
working-directory: web
run: yarn jest
run: yarn test

container-image:
runs-on: ubuntu-latest
Expand Down
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "none"
}
21 changes: 11 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
# building frontend
FROM node:16.14 as build-deps
FROM node:16.14 as frontend
WORKDIR /app/frontend
COPY web ./

# fix docker not following symlinks
COPY doc/getting-started.md ./src/assets/

RUN yarn install --frozen-lockfile
RUN yarn lint
RUN yarn run test:unit

# fix test not exiting by default
ARG CI=true
RUN yarn test

RUN yarn build

# setup Python
# TODO(Fliiiix): FastApi is broken in Python 3.11
# We need to wait for a fix:
# https://github.com/tiangolo/fastapi/issues/5048
FROM python:3.11.0-alpine3.15 AS backend

# configure docker container
Expand All @@ -29,12 +33,9 @@ COPY /docat/pyproject.toml /docat/poetry.lock /app/

# Install the application
WORKDIR /app/docat
RUN poetry install --no-root --no-ansi --no-dev
RUN poetry install --no-root --no-ansi --only main

# production
# TODO(Fliiiix): FastApi is broken in Python 3.11
# We need to wait for a fix:
# https://github.com/tiangolo/fastapi/issues/5048
FROM python:3.11.0-alpine3.15

# set up the system
Expand All @@ -46,7 +47,7 @@ RUN mkdir -p /var/docat/doc

# install the application
RUN mkdir -p /var/www/html
COPY --from=build-deps /dist /var/www/html
COPY --from=frontend /app/frontend/build /var/www/html
COPY docat /app/docat
WORKDIR /app/docat

Expand Down
16 changes: 15 additions & 1 deletion docat/docat/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
get_all_projects,
get_project_details,
index_all_projects,
is_forbidden_project_name,
remove_docs,
remove_file_index_from_db,
remove_version_from_version_index,
Expand Down Expand Up @@ -113,7 +114,12 @@ def get_project(project):
@app.get("/api/search", response_model=SearchResponse, status_code=status.HTTP_200_OK)
@app.get("/api/search/", response_model=SearchResponse, status_code=status.HTTP_200_OK)
def search(query: str):
query = query.lower()
query = query.lower().strip()

# an empty string would match almost everything
if not query:
return SearchResponse(projects=[], versions=[], files=[])

found_projects: list[SearchResultProject] = []
found_versions: list[SearchResultVersion] = []
found_files: list[SearchResultFile] = []
Expand Down Expand Up @@ -312,6 +318,10 @@ def upload(
docat_api_key: Optional[str] = Header(None),
db: TinyDB = Depends(get_db),
):
if is_forbidden_project_name(project):
response.status_code = status.HTTP_400_BAD_REQUEST
return ApiResponse(message=f'Project name "{project}" is forbidden, as it conflicts with pages in docat web.')

project_base_path = DOCAT_UPLOAD_FOLDER / project
base_path = project_base_path / version
target_file = base_path / file.filename
Expand Down Expand Up @@ -391,6 +401,10 @@ def claim(project: str, db: TinyDB = Depends(get_db)):
@app.put("/api/{project}/rename/{new_project_name}", response_model=ApiResponse, status_code=status.HTTP_200_OK)
@app.put("/api/{project}/rename/{new_project_name}/", response_model=ApiResponse, status_code=status.HTTP_200_OK)
def rename(project: str, new_project_name: str, response: Response, docat_api_key: str = Header(None), db: TinyDB = Depends(get_db)):
if is_forbidden_project_name(new_project_name):
response.status_code = status.HTTP_400_BAD_REQUEST
return ApiResponse(message=f'New project name "{new_project_name}" is forbidden, as it conflicts with pages in docat web.')

project_base_path = DOCAT_UPLOAD_FOLDER / project
new_project_base_path = DOCAT_UPLOAD_FOLDER / new_project_name

Expand Down
10 changes: 10 additions & 0 deletions docat/docat/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ def calculate_token(password, salt):
return hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 100000).hex()


def is_forbidden_project_name(name: str) -> bool:
"""
Checks if the given project name is forbidden.
The project name is forbidden if it conflicts with
a page on the docat website.
"""
name = name.lower().strip()
return name in ["upload", "claim", "delete", "search", "help"]


def get_all_projects(upload_folder_path: Path) -> Projects:
"""
Returns all projects in the upload folder.
Expand Down
23 changes: 23 additions & 0 deletions docat/tests/test_rename.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,26 @@ def test_rename_success(client_with_claimed_project):
assert len(claims_with_old_name) == 0
claims_with_new_name = table.search(Project.name == "new-project-name")
assert len(claims_with_new_name) == 1


def test_rename_rejects_forbidden_project_name(client_with_claimed_project):
"""
Names that conflict with pages in docat web are forbidden,
and renaming a project to such a name should fail.
"""

create_response = client_with_claimed_project.post(
"/api/some-project/1.0.0", files={"file": ("index.html", io.BytesIO(b"<h1>Hello World</h1>"), "plain/text")}
)
assert create_response.status_code == 201

with patch("os.rename") as rename_mock:
for project_name in ["upload", "claim", "delete", "Search ", "help"]:

rename_response = client_with_claimed_project.put(f"/api/some-project/rename/{project_name}", headers={"Docat-Api-Key": "1234"})
assert rename_response.status_code == 400
assert rename_response.json() == {
"message": f'New project name "{project_name}" is forbidden, as it conflicts with pages in docat web.'
}

assert rename_mock.mock_calls == []
19 changes: 19 additions & 0 deletions docat/tests/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,25 @@ def test_search_project_by_name_negative(client_with_claimed_project):
assert search_response.json() == {"projects": [], "versions": [], "files": []}


def test_search_ignores_empty_query(client_with_claimed_project):
"""
Search should return an empty result if the query is empty.
"""
create_project_response = client_with_claimed_project.post(
"/api/some-project/1.0.0",
files={"file": ("index.html", io.BytesIO(b"<h1>Hello World</h1>"), "plain/text")},
)
assert create_project_response.status_code == 201

search_response = client_with_claimed_project.get("/api/search?query=%20")
assert search_response.status_code == 200
assert search_response.json() == {"projects": [], "versions": [], "files": []}

search_response = client_with_claimed_project.get("/api/search?query=&")
assert search_response.status_code == 200
assert search_response.json() == {"projects": [], "versions": [], "files": []}


def test_search_finds_tag(client_with_claimed_project):
"""
Search should find a tag by name. (Partial match)
Expand Down
17 changes: 17 additions & 0 deletions docat/tests/test_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,20 @@ def test_fails_with_invalid_token(client_with_claimed_project):
assert response_data["message"] == "Docat-Api-Key token is not valid for some-project"

assert remove_mock.mock_calls == []


def test_upload_rejects_forbidden_project_name(client_with_claimed_project):
"""
Names that conflict with pages in docat web are forbidden,
and creating a project with such a name should fail.
"""

with patch("docat.app.remove_docs") as remove_mock:
for project_name in ["upload", "claim", "delete", "Search ", "help"]:
response = client_with_claimed_project.post(
f"/api/{project_name}/1.0.0", files={"file": ("index.html", io.BytesIO(b"<h1>Hello World</h1>"), "plain/text")}
)
assert response.status_code == 400
assert response.json() == {"message": f'Project name "{project_name}" is forbidden, as it conflicts with pages in docat web.'}

assert remove_mock.mock_calls == []
45 changes: 45 additions & 0 deletions web/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"plugin:react/recommended",
"standard-with-typescript"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"parserOptions": {
"project": [
"./tsconfig.json"
]
},
"rules": {
"@typescript-eslint/space-before-function-paren": "off"
}
},
{
"files": [
"src/react-app-env.d.ts"
],
"rules": {
"@typescript-eslint/triple-slash-reference": "off"
}
}
],
"parserOptions": {
"ecmaVersion": "latest"
},
"plugins": [
"react"
],
"rules": {}
}
36 changes: 22 additions & 14 deletions web/.gitignore
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
.DS_Store
node_modules
/dist
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# local env files
# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.prettierrc
.DS_Store
.env.local
.env.*.local
.env.development.local
.env.test.local
.env.production.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# local env files
.env
.env.local
.env.*.local
39 changes: 22 additions & 17 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@ yarn install [--pure-lockfile]

### Compiles and hot-reloads for development

Configure the backend connection by setting
port and host in `.env.development.local`.
Like this configuration for the host `127.0.0.1`
and the port `1337`.

Configure both the frontend port and the backend connection
by setting them in `.env.development.local`.
```sh
VUE_APP_BACKEND_PORT=1337
VUE_APP_BACKEND_HOST=127.0.0.1
PORT=8080
BACKEND_HOST=127.0.0.1
BACKEND_PORT=5000
```

```sh
yarn serve
yarn start
```

### Compiles and minifies for production
Expand All @@ -34,7 +32,13 @@ yarn build
yarn lint
```

### Basic Header Theeming
### Tests

```sh
yarn test
```

### Basic Header Theming

Not happy with the default Docat logo and header?
Just add your custom html header to the `/var/www/html/config.json` file.
Expand All @@ -43,21 +47,22 @@ Just add your custom html header to the `/var/www/html/config.json` file.
{ "headerHTML": "<h1>MyCompany</h1>" }
```

### Customize configuration

See [Configuration Reference](https://cli.vuejs.org/config/).


## Development

To mount the development `dist/` folder while working on the
web frontend, you can mount the `dist/` folder as a docker volume:

```sh
sudo docker run \
--detach \
--volume /path/to/doc:/var/docat/doc/ \
--volume /path/to/docat/web/dist:/var/www/html/ \
--publish 8000:80 \
docat
```

## Errors

If you get a 403 response when trying to read a version,
try changing the permissions of your docs folder on your host.

```sh
sudo chmod 777 /path/to/doc -r
```
5 changes: 0 additions & 5 deletions web/babel.config.js

This file was deleted.

3 changes: 0 additions & 3 deletions web/jest.config.js

This file was deleted.

Loading

0 comments on commit 23d46b7

Please sign in to comment.