diff --git a/README.md b/README.md index c4ded76..5927087 100644 --- a/README.md +++ b/README.md @@ -1 +1,40 @@ -# webqueue2-api \ No newline at end of file +# webqueue2 API +A Python based parser and RESTful API for ECN's webqueue. + +## Usage +### Install via pip: +``` +pip install git+https://github.itap.purdue.edu/ECN/webqueue2-api@#egg=webqueue2-api +``` +For example, to install version 0.9.1: +``` +pip install git+https://github.itap.purdue.edu/ECN/webqueue2-api@0.9.1#egg=webqueue2-api +``` + +### Install via requirements file: +``` +git+https://github.itap.purdue.edu/ECN/webqueue2-api@#egg=webqueue2-api +``` +For example, to install version 0.9.1: +``` +git+https://github.itap.purdue.edu/ECN/webqueue2-api@0.9.1#egg=webqueue2-api +``` + +## Contributing +1. Clone the git repo: +```bash +git clone https://github.itap.purdue.edu/ECN/webqueue2-api.git +``` +2. Create and activate Python virtual environment: +```bash +python3 -m venv venv +source venv/bin/activate +``` +3. Install dependencies: +```bash +# Older versions of pip may fail to install newer packages. +pip install -U pip +# Install the webqueue2-api package in editable mode with all extra packages. +pip install -e .[all] +``` +4. Make changes and create a PR. \ No newline at end of file diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/docs/Dev Environment Setup Guide.md b/docs/Dev Environment Setup Guide.md new file mode 100644 index 0000000..6ae7118 --- /dev/null +++ b/docs/Dev Environment Setup Guide.md @@ -0,0 +1,299 @@ +# Dev Environment Setup Guide +This document will walk you through the setup and configuration of a development environment for webqueue2 using the provided development machines, SSH authentication, GitHub and VS Code. + +## Prerequisites + +### On Your Computer +- [VS Code](https://code.visualstudio.com/download) +- [Cisco AnyConnect for WebVPN](https://webvpn.purdue.edu/) + +### On The Development Computer + +!!! Note + These are already installed on the provided development machines. + +- [git](https://git-scm.com/downloads) +- [Python](https://www.python.org/downloads/) == 3.6.9 + +## Configuring Your SSH Keys +We will be using SSH keys to authenticate to both the development machines and GitHub. + +In either PowerShell on Windows or bash on macOS/Linux, run the following command and accept all defaults by pressing Enter: +```none +ssh-keygen +``` +This will create the files `id_rsa` and `id_rsa.pub` in the `.ssh` folder inside your user's folder. Your user's folder can usually be found at: + +=== "Windows" + ```none + C:\Users\ + ``` +=== "macOS" + ```none + /Users/ + ``` +=== "Linux" + ```none + /home/ + ``` + +!!! note + Most CLI shells like PowerShell and bash use the ~ (tilda) key as a shortcut for your user's folder. + +You can confirm these files were created by running: +```none +ls ~/.ssh/ +``` + +## Configuring SSH +In your editor of choice, create the file `~/.ssh/config` and add this: + +!!! tip + Replace `campb303` with your career account username and replace `w2vm1` with the name of the provided development machine you're connecting to. + +```none +Host campb303-w2vm1 + HostName w2vm1.ecn.purdue.edu + User campb303 + # Forward webqueue2 API Port + LocalForward 5000 localhost:5000 + # Forward webqueue2 API Docs Port + LocalForward 8000 localhost:8000 +``` + +The configuration above will allow you to connect to the development machine and automatically forward ports for development tools to work. Here's a bit more detail about what's going on: + +| Key | Value | +| - | - | +| Host | A friendly name to identify an SSH host by. This can be anything. | +| HostName | The DNS name or IP address of the computer you're connecting to. | +| User | The name of your user on the computer you're connecting to. | +| LocalForward | Forwards a port on your computer to a port on the computer you're connecting to. | + +Development machines are not publicly accessible. You'll need to be connected to WebVPN to connect to them. + +??? tip "SSH Jump Hosts" + WebVPN works on all platforms but requires an extra step to get to work. On macOS/Linux, `pier.ecn.purdue.edu` can be used as an [SSH jump host](https://www.tecmint.com/access-linux-server-using-a-jump-host/) allowing for development machines to be connected to automatically when openning VS Code. + + Assuming you already have an SSH key configured for `pier.ecn.purdue.edu`, you can use a jump host by adding the following to `~/.ssh/config`: + + ``` + Host campb303-pier + HostName pier.ecn.purdue.edu + User campb303 + + Host campb303-w2vm1 + HostName w2vm1.ecn.purdue.edu + ProxyJump campb303-pier + User campb303 + # Forward webqueue2 API Port + LocalForward 5000 localhost:5000 + # Forward webqueue2 API Docs Port + LocalForward 8000 localhost:8000 + ``` + +To test your configuration, run `ssh` followed by the `Host` value. When prompted for your password, enter your career account password and press Enter. + +!!! warning + Most shell environemnts like PowerShell and bash do not show your password being typed but it is being typed. + +For the configuration above you would run: + +```none +ssh campb303-w2vm1 +campb303@w2vm1's password: +``` + +## Adding SSH Keys +Now that we've generated SSH keys and configured our host entries, we need to add our SSH key to the host. This will allow us to connect to these machines without passwords later. + +!!! tip + Replace `HOST` below with the value from above. _Example:_ `campb303-w2vm1` + +=== "PowerShell on Windows" + ```powershell + type $env:USERPROFILE\.ssh\id_rsa.pub | ssh HOST "cat >> .ssh/authorized_keys" + ``` +=== "bash on macOS/Linux" + ```bash + ssh-copy-id HOST + ``` + +If the key was added successfully, you can login without entering a password by running: +``` +ssh HOST +``` + +## Installing VS Code +Download and install [VS Code](https://code.visualstudio.com/download). Be sure to add `code` to your PATH. + +=== "Windows" + + Adding `code` to your PATH on Windows is a checkbox in the installer: + + ![VS Code Install Options on Windows](https://i0.wp.com/www.techomoro.com/wp-content/uploads/2019/06/5.jpg?w=596&ssl=1) + + _Image from [this article on Techomoro](https://www.techomoro.com/installing-visual-studio-code-on-windows-10/)_ + +=== "macOS/Linux" + + Adding `code` to your PATH on macOS/Linux is accessible by searching for "PATH" in the Command Pallete. You can access the Command Pallete with the keyboard shortcut Command/Ctrl + Shift + P: + + ![VS Code Install Options on macOS/Linux](https://i.stack.imgur.com/Ng886.png) + + _Image from [this StackOverflow thread](https://stackoverflow.com/questions/30065227/run-open-vscode-from-mac-terminal)_ + +## Connecting To The Development Machine +Install the [Remote - SSH](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) plugin. After installation a new icon will appear in the sidebar: + +![Remote SSH Plugin Icon](images/Dev%20Environment%20Setup%20Guide/Remote%20SSH%20Icon.png) + +- Click on the Remote SSH icon and you'll see the SSH host you just defined +- Select the host and click the New Window icon to connect. A new window connected to that host will appear + +!!! note + This may take a couple of minutes on the first connection while VS Code installs its server. + +![Remote SSH Connection Example](images/Dev%20Environment%20Setup%20Guide/Connect%20to%20Remote%20SSH%20Host.gif) + +If prompted for a platform, select Linux: +![VS Code Select Platform](images/Dev%20Environment%20Setup%20Guide/VS%20Code%20Select%20Platform.png) + +## Adding Your SSH Keys to GitHub +Because the development machine will be the computer that connects to GitHub, we need to create another SSH key and add it to our GitHub profile: + +First, open VS Code's integrated terminal by pressing Control + ~ (tilda) + +![Open VS Code's integrated terminal](images/Dev%20Environment Setup%20Guide/Open%20VS%20Code%20Integrated%20Terminal.gif) + +Now run the following command and accept all defaults by pressing Enter: +```bash +ssh-keygen +``` + +This will create the files `id_rsa` and `id_rsa.pub` in `~/.ssh/`. You can confirm this by running: +```bash +ls ~/.ssh/ +``` + +Now copy the public key from `id_rsa.pub` by openning the file in VS Code, selecting its contents and copying it. You can open the file in VS Code by running the following in the integrated terminal: + +!!! danger + Do not copy your private key from `id_rsa`! This key should never leave your machine. + +```bash +code ~/.ssh/id_rsa.pub +``` + +Now go to [github.itap.purdue.edu/settings/keys](https://github.itap.purdue.edu/settings/keys). You may be prompted to login using your career account username and password. + +- Click the "New SSH Key" button +- Give the key a title that tells you what device the key is from (e.g. Desktop or Dev Machine) +- Paste the contents of `id_rsa.pub` into the key box +- Click the "Add SSH Key" button + +![Add SSH Key to GitHub](images/Dev%20Environment%20Setup%20Guide/Add%20SSH%20Key%20to%20GitHub.gif) + +## Cloning and Opening the Repository +Using the integrated terminal in VS Code, run: +```none +git clone git@github.itap.purdue.edu:ECN/webqueue2-api.git +``` +This will create a `webqueue2-api` folder in your user's home directory. Open this directory using the "Open Folder" button: + +![Open Repo](images/Dev%20Environment%20Setup%20Guide/Open%20Repo.gif) + +!!! note + VS Code will automatically reconnect to the last remote host used when it reopens. + +!!! tip + If you need to reconnect manually, there will now be an option to open the webqueue2-api folder directly from the Remote Hosts menu: + + ![Remote Folder Open](images/Dev%20Environment%20Setup%20Guide/Remote%20Folder%20Open.png) + +## Configuring VS Code + +### Installing Extensions +- [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) for Python language support, IntelliSense, virtual environments and debugging +- [Git Graph](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph) for a more detailed view of git info +- [Markdown Preview GitHub Styling](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-preview-github-styles) for previewing Markdown as it will appear on GitHub +- [Python Docstring Generator](https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring) for generating Google style docstrings according to section 3.8 of [Google's Python style guide](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings) + +### Configuring Extensions + +#### Python +The Python extension supports virtual environments but needs to be told where the virtual environment is. + +Create or modify the file `.vscode/settings.json` and add the following: +```none +"python.pythonPath": "./venv/bin/python3" +``` + +!!! tip + The Python extension may complain and tell you to select another interpreter. Ignore this warning for now. + +#### Python Docstring Generator +For consistentcy, we'll use a docstring template. Create or modify the file `.vscode/settings.json` and add the following: +```none +"autoDocstring.customTemplatePath": "./docstring-format.mustache" +``` + +At this point, your `.vscode/settings.json` file should look like this: +```none +{ + "python.pythonPath": "./venv/bin/python3", + "autoDocstring.customTemplatePath": "./docstring-format.mustache" +} +``` + +## Setting Up the Virtual Environment +For development, we'll use a [Python Virtual Environment](https://realpython.com/python-virtual-environments-a-primer/) to isolate out changes and have a reproducible dev environment. + +In VS Code's integrated terminal: + +Create a virtual environment at `./venv/`: +```bash +python3 -m venv venv +``` + +Activate the virtual environment: +```bash +source venv/bin/activate +``` + +!!! tip + To deactivate the virtual environment and use your system's Python interpreter, run: + + ```bash + deactivate + ``` + +Update pip within the virtual environment: +```none +pip install -U pip +``` + +Install the webqueue2 API within the virtual environemt: +```none +pip install -e .[all] +``` + +`-e` installs a package in [editable mode](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-e) which allows code changes to take effect without reinstalling a package. + +webqueue2 API has multiple [conditional dependencies](https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#id7): + +| Condition | Installation Command | Description | +| - | - | - | +| Production | `pip install webqueue2-api` | For use in production. Only installed needed packages. | +| Development | `pip install webqueue2-api[dev]` | For use in development. Installs everything for production and extra packages for development. | +| Documentation | `pip install webqueue2-api[docs]` | For use in creating documentation. Installs everything for production and extra packages for documentation. | +| All | `pip install webqueue2-api[all]` | A shortcut for installing production, development and documentation dependencies. | + +### API Commands +| Command | Description | +| - | - | +| `python3 -m webqueue2_api start-api` | Starts the API on [localhost:5000](http://localhost:5000). See [Getting Started](./api/Getting Started.md) for more info. | +| `python3 -m webqueue2_api stop-api` | Stops the API. | +| `python3 -m webqueue2_api restart-api` | Stops and starts the API. | +| `mkdocs serve` | This will start a local server on [localhost:8000](http://localhost:8000) to access the API docs. As you change API documentation files in `./docs/` you'll see your changes in the browser. | +| `mkdocs build` | This will output a static bundle of the API documentation in `./site/` that can be put on any webserver. | \ No newline at end of file diff --git a/docs/Material for mkdocs Formatting.md b/docs/Material for mkdocs Formatting.md new file mode 100644 index 0000000..3edd513 --- /dev/null +++ b/docs/Material for mkdocs Formatting.md @@ -0,0 +1,105 @@ +# Material for mkdocs Formatting + +## Admonitions +See: [Material for mkdocs: Admonitions](https://squidfunk.github.io/mkdocs-material/reference/admonitions/#admonitions) + +!!! note + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod + nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor + massa, nec semper lorem quam in massa. + +??? summary + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod + nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor + massa, nec semper lorem quam in massa. + +???+ info + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod + nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor + massa, nec semper lorem quam in massa. + +!!! success "Custom Title" + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod + nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor + massa, nec semper lorem quam in massa. + + +## Code Blocks +See: [Material for mkdocs: Code Blocks](https://squidfunk.github.io/mkdocs-material/reference/code-blocks/#highlight) + +``` python +# This is a Python code block. +print("Hello world!") if __name__ == "__main__" +``` +``` js linenums="1" +# This is a JavaScript code block with line numbers. +( _ => console.log("Hello world!"))(); +``` +``` md hl_lines="3" +# This is a Markdown code block with line highlights and nested code block. + ``` js + ( _ => console.log("Hello world!"))(); + ``` +``` + +This is an inline Java highlight `#!java system.out.println("Hello world!")` + + +## Content Tabs + +See: [Material for mkdocs: Content Tabs](https://squidfunk.github.io/mkdocs-material/reference/content-tabs/#content-tabs) + +### Basic Tabs +```md +=== "Tab 1" + Hello +=== "Tab 2" + World +``` + +=== "Tab 1" + Hello +=== "Tab 2" + World + +### Tabs in Admonitions with Code Blocks +```md +!!! example + "Hello world" in many languages: + === "Python" + ```python + print("Hello world!") if __name__ == "__main__" + ``` + === "JavaScript" + ```js + ( _ => console.log("Hello world!"))(); + ``` +``` + +!!! example + "Hello world" in many languages: + === "Python" + ```python + print("Hello world!") if __name__ == "__main__" + ``` + === "JavaScript" + ```js + ( _ => console.log("Hello world!"))(); + ``` + +## Data Tables +See: [Material for mkdocs: Data Tables](https://squidfunk.github.io/mkdocs-material/reference/data-tables/#data-tables) + +``` +| Method | Description | +| ----------- | ------------------------------------ | +| `GET` | :material-check: Fetch resource | +| `PUT` | :material-check-all: Update resource | +| `DELETE` | :material-close: Delete resource | +``` + +| Method | Description | +| ----------- | ------------------------------------ | +| `GET` | :material-check: Fetch resource | +| `PUT` | :material-check-all: Update resource | +| `DELETE` | :material-close: Delete resource | \ No newline at end of file diff --git a/docs/api/Authentication.md b/docs/api/Authentication.md new file mode 100644 index 0000000..8d1b613 --- /dev/null +++ b/docs/api/Authentication.md @@ -0,0 +1,79 @@ +# Authentication + +The webqueue2 API uses a two stage authentication system combining Active Directory and [HTTP Token](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) (or "bearer") authentication. + +## Getting an Access Token +All API calls require an access token. You can get an access token by making a POST request to the `/api/login` endpoint containing the JSON encoded username and password of a valid user. + +??? info "Who is a valid user?" + A valid user is a non-admin BoilerAD user who is in the `00000227-ECN-webqueue` group. Users cannot be added directly to this group. To be included in this group, a user must exist in one of the following groups: + + - `00000227-ECNStaff` + - `00000227-ECNStuds` + - `00000227-ECN-webqueue-misc` + +### Example +!!! example "Get an access token." + + ```js + {% include 'api/get_access_token.js' %} + ``` + ```js + // Expected Output + "{{ example_access_token }}" + ``` + +## Making Calls With Access Token +To interact with the API, add an `Authorization` header to your request with a value of `Bearer TOKEN` where `TOKEN` is your access token. + +### Example: +!!! example "Get item CE 100." + ```js + let access_token = "{{ example_access_token }}"; + let queue = "ce"; + let item_number = 100; + + fetch( + `https://engineering.purdue.edu/webqueue/webqueue2/build/api/data/${queue}/${item_number}`, + { headers: {"Authorization":`Bearer ${access_token}` }} + ) + .then( resp => resp.json() ) + .then( data => console.log( data )); + ``` + ```js + // Expected Output + {queue: "ce", number: 100, lastUpdated: "2021-03-11T07:24:00-0500" ... } + ``` + +## Refreshing Access Tokens +When you login, you'll receive an access token that expires 15 minutes after creation as well as two cookies needed to get a new access token. Those cookies are: + +Name | Value | Path | Expiration | SameSite +-- | -- | -- | -- | -- +`refresh_token_cookie` | Your refresh token. | `/api/tokens/refresh` | 30 Days | Yes +`csrf_refresh_token` | Additional verification data. (e.g. `{{ example_csrf_token }}`) | `/` | Session | Yes + +The `refresh_token_cookie` is used to generate a new access token and will be sent back to the server with every request automatically. It expires 30 days after login. The `csrf_refresh_token` is used to verify the `refresh_token_cookie` and needs sent back as an `X-CSRF-TOKEN` header. + +To refresh your access token, make a POST request to the `/api/tokens/refresh` endpoint with the value of the `csrf_refresh_token` cookies inside a `X-CSRF-TOKEN` header: + +### Example +!!! example "Get a new refresh token." + ```js + // Get this value from your cookies. + const csrf_refresh_token = "{{ example_csrf_token }}" + + fetch( + `{{ production_url }}/api/tokens/refresh`, + { + method: "POST", + headers: {'X-CSRF-TOKEN': csrf_refresh_token} + } + ) + .then( resp => resp.json() ) + .then( data => console.log( data.access_token )); + ``` + ```js + // Expected Output + {{ example_access_token }} + ``` \ No newline at end of file diff --git a/docs/api/Getting Started.md b/docs/api/Getting Started.md new file mode 100644 index 0000000..128d990 --- /dev/null +++ b/docs/api/Getting Started.md @@ -0,0 +1,42 @@ +# Getting Started + +The webqueue2 API is organized around [REST](https://en.wikipedia.org/wiki/Representational_state_transfer). The API has resource oriented URLs, accepts and returns JSON encoded request bodies, can be modified via the query string and uses standard HTTP response codes and verbs. + +You can use the webqueue2 API hosted at [{{ production_url }}/api]({{ production_url }}/api) or [install it on your own machine](Installation.md). + +## Basic Usage + +!!! example "Get the first item in CE queue." + ```javascript + let access_token = "{{ example_access_token }}"; + let queue = "ce"; + + fetch( + `https://engineering.purdue.edu/webqueue/webqueue2/build/api/data/${queue}`, + { headers: {"Authorization":`Bearer ${access_token}` }} + ) + .then( resp => resp.json() ) + .then( data => console.log( data[0].items[0] )); + ``` + ```js + // Expected Output + { queue: "ce", number: 17, lastUpdated: "2021-03-29T17:12:00-0400" ... } + ``` + +!!! example "Get the subject of an CE 1." + ```javascript + let access_token = "{{ example_access_token }}"; + let queue = "ce"; + let item_number = 1; + + fetch( + `https://engineering.purdue.edu/webqueue/webqueue2/build/api/data/${queue}/${item_number}`, + { headers: {"Authorization":`Bearer ${access_token}` }} + ) + .then( resp => resp.json() ) + .then( data => console.log( data.subject )); + ``` + ```js + // Expected Output + "Linux Server Purchase" + ``` \ No newline at end of file diff --git a/docs/api/Installation.md b/docs/api/Installation.md new file mode 100644 index 0000000..f04d7ed --- /dev/null +++ b/docs/api/Installation.md @@ -0,0 +1,31 @@ +# Installation + +The webqueue2 API is publicly available at [{{ production_url }}/api]({{ production_url }}/api) but you can install it on your own machine if you'd like. + +The code is available on [GitHub]({{ config.repo_url }}). Available package versions can be seen on the [releases page]({{ config.repo_url }}/releases) and can be installed via pip, a requirements file, or downloaded and installed manually. + +!!! warning "pip available on ECN machines needs updated." + Installation via pip is possible through pip's [VCS support](https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support) which was introduced in pip 19. The version of pip available on ECN supported machines is too old and needs updated. To update pip, run: + + ```bash + pip install -U pop + ``` + +=== "Install via pip" + To install webqueue2-api 0.9.1, you can run: + + ``` + pip install git+{{ config.repo_url }}@0.9.1#egg=webqueue2-api + ``` + + If you'd like a version other than 0.9.1 simply replace the that version number with a different version listed on the [releases page]({{ config.repo_url }}/releases). + +=== "Install via requirements file" + To install webqueue2-api 0.9.1, place the following line your requirements file: + + ```bash + echo "git+{{ config.repo_url }}@0.9.1#egg=webqueue2-api" >> requirements.txt + pip install -r requirements.txt + ``` + + If you'd like a version other than 0.9.1 simply replace the that version number with a different version listed on the [releases page]({{ config.repo_url }}/releases). \ No newline at end of file diff --git a/docs/api/Items.md b/docs/api/Items.md new file mode 100644 index 0000000..9d14e8f --- /dev/null +++ b/docs/api/Items.md @@ -0,0 +1,443 @@ +# Items + +An item is a chronological representation of an interaction with a user. + +## Endpoint +``` +GET /api/{queue}/{number} +``` +### Parameters +Name | Value +- | - +`queue` | The queue of the item. +`number` | The number of the item. + +### Query String Options +Name | Description | Possible Values +- | - | - +`headersOnly` | When `"True"`, only meta data will be loaded. When `"False"` content will be parsed. (Defaults to `"False"`.) | `"True"` \| `"False"` + +### Return Codes +Code | Description +- | - +`200 - Ok` | On success. +`500 - Internal Service Error` | On failure. + + +## Properties + +### `Item.lastUpdated` +Type | Description +- | - +`String` | An ISO 8601 formatted time string showing the last time the file was updated according to the filesystem. + +!!! example + ```js + console.log(Item.lastUpdated) + // Expected Output + "2021-04-03T00:48:38+00:00" + ``` + + +### `Item.headers` +Type | Description +- | - +`Array` | An array of objects containing parsed item headers in `type`/`value` pairs. + +!!! example + ```js + console.log(Item.headers[0]) + // Expexted Output + { type: "From", content: "\"Campbell, Justin Tyler\" \n"} + ``` + +### `Item.content` +Type | Description +- | - +`Array` | A chronological array of objects representing 1 of 9 possible actions taken on an item. + +**Possible Actions** +??? example "Directory Information" + Information about the user info collected from ECNDB including but not limited to alias, phone number and office location. + + ??? info "Properties" + | Key | Value | + | - | - | + |`type`|`directory_information`| + | `Name` | The real name of the sender. | + | `Login` | The career account alias of the sender. | + | `Computer` | The computer the item is related to. Formatting may vary. | + | `Location` | Where the computer is located. | + | `Email` | The email address of the sender. | + | `Phone` | The phone number of the sender. | + | `Office` | The office location of the sender. | + | `UNIX Dir` | The home directory for the user on non-Windows systems | + | `Zero Dir` | The home directory for the user via Active Directory | + | `User ECNDB` | Link to the sender's username report in ECNDB | + | `Host ECNDB` | Link to the computer report in ECNDB | + | `Subject` | The subject of the email sent to the queue | + + ??? quote "Parsed Example" + ```js + { + "type": "directory_information", + "Name": "Nestor Fabian Rodriguez Buitrago", + "Login": "rodri563", + "Computer": "ce-205-38 (128.46.205.67)", + "Location": "HAMP G230", + "Email": "rodri563@purdue.edu", + "Phone": "7654766893", + "Office": "HAMP G230", + "UNIX Dir": "/home/bridge/b/rodri563", + "Zero Dir": "U=\\\\bridge.ecn.purdue.edu\\rodri563", + "User ECNDB": "http://eng.purdue.edu/jump/2e8399a", + "Host ECNDB": "http://eng.purdue.edu/jump/2e83999", + "Subject": "Autocad installation" + } + ``` +??? example "Initial Message" + The body of the email the item originated from. + + ??? info "Properties" + | Key | Value | + | - | - | + | `type` | `initial_message` | + | `datetime` | RFC 8061 formatted datetime string. | + | `from_name` | The sender's real name. Formatting may vary. This can be empty. | + | `from_email` | The sender's email address. | + | `to` | A list of names(s) and email(s) of people this message was sent to. | + | `cc` | A list of name(s) and email(s) of people who were CC'd. This can be empty. | + | `subject` | The subject of the initial message. | + | `content` | The content of the message as an list of strings. | + + ??? quote "Parsed Example" + ```js + { + "type": "initial_message", + "datetime": "2020-09-11T01:26:45+00:00", + "from_name": "Justin Campbell", + "from_email": "campb303@purdue.edu", + "to": [ + { "name": "John Doe", "email": "johndoe@example.com" }, + ], + "cc": [ + { "name": "", "email": "janesmith@example.com" } + ], + "subject": Maps to item.subject, + "content": [ + "I need some help with something.\n" + ] + } + ``` +??? example "Edit" + Information added by someone at ECN, usually for internal use and/or communication. + + ??? info "Properties" + | Key | Value | + | - | - | + | `type` | `edit` | + | `datetime` | RFC 8061 formatted datetime string. | + | `by` | The career account alias of the person who added the edit. | + | `content` | The content of the edit as a list of strings. | + + ??? quote "Parsed Example" + ```js + { + "type": "edit", + "datetime": "2020-04-22T16:39:51", + "by": "knewell", + "content": [ + "This is related to another item. I need to do X next.\n" + ] + } + ``` +??? example "Status" + A short message about the progress of the item. + + ??? info "Properties" + | Key | Value | + | - | - | + | `type` | `status` | + | `datetime` | RFC 8061 formatted datetime string. | + | `by` | The career account alias of the person who updated the status. | + | `content` | The content of the status as a list of strings. | + + ??? quote "Parsed Example" + ```js + { + "type": "status", + "datetime": "2020-04-23T10:35:47", + "by": "knewell", + "content": [ + "Doing X thing." + ] + } + ``` +??? example "Assignment" + Assigning the item to someone. + + ??? info "Properties" + | Key | Value | + | - | - | + | `type` | `assignment` | + | `datetime` | RFC 8061 formatted datetime string. | + | `by` | The career account alias of the person who changed the |assignment. | + | `to` | The career account alias of the person who the item was assigned to. | + + ??? quote "Parsed Example" + ```js + { + "type": "assignment", + "datetime": "2020-06-23T13:27:00", + "by": "harley", + "to": "campb303", + } + ``` +??? example "Reply To User" + A message from ECN to the user and/or related parties. + + ??? info "Properties" + | Key | Value | + | - | - | + | `type` | `reply_to_user` | + | `datetime` | RFC 8061 formatted datetime string. | + | `by` | The sender's real name. Formatting may vary. This can be empty. | + | `content` | The content of the message as an list of strings | + + ??? quote "Parsed Example" + ```js + { + "type": "reply_to_user", + "datetime": "2020-05-08T09:21:43", + "by": "ewhile", + "content": [ + "Sascha,\n", + "\n", + "Chicken kevin biltong, flank jowl prosciutto shoulder meatball meatloaf sirloin.\n", + "\n", + "Ethan White\n", + "ECN" + ] + } + ``` +??? example "Reply To User" + A message from ECN to the user and/or related parties. + + ??? info "Properties" + | Key | Value | + | - | - | + | `type` | `reply_to_user` | + | `datetime` | RFC 8061 formatted datetime string. | + | `by` | The sender's real name. Formatting may vary. This can be empty. | + | `content` | The content of the message as an list of strings | + + ??? quote "Parsed Example" + ```js + { + "type": "reply_to_user", + "datetime": "2020-05-08T09:21:43", + "by": "ewhile", + "content": [ + "Sascha,\n", + "\n", + "Chicken kevin biltong, flank jowl prosciutto shoulder meatball meatloaf sirloin.\n", + "\n", + "Ethan White\n", + "ECN" + ] + } + ``` +??? example "Reply From User" + A message from the user and/or related parties. + + ??? info "Properties" + | Key | Value | + | - | - | + | `type` | `reply_from_user` | + | `datetime` | RFC 8061 formatted datetime string. | + | `from_name` | The sender's real name. Formatting may vary. This can be empty. | + | `from_email` | The sender's email address. | + | `cc` | A list of name(s) and email(s) of people who were CC'd. This can be empty. | + | `headers` | A dictionary of headers from the reply. | + | `subject` | The subject of the reply. | + | `content` | The content of the message as an list of strings | + + ??? quote "Parsed Example" + ```js + { + "type": "reply_from_user", + "datetime": "2020-05-08T13:57:18+00:00", + "from_name": "Reckowsky, Michael J.", + "from_email": "mreckowsky@purdue.edu", + "cc": [ + { "name": "John Doe", "email": "johndoe@example.com" }, + { "name": "", "email": "janesmith@example.com" } + ], + "headers" : [ + { + "type": "Subject", + "content": "RE: New Computer Deploy" + }, + { + "type": "From", + "content": "\"Reckowsky, Michael J.\" " + }, + { + "type": "Date", + "content": "Fri, 8 May 2020 13:57:17 +0000" + }, + ], + "subject": "RE: New Computer Deploy", + "content": [ + "Ethan,\n", + "\n", + "Biltong beef ribs doner chuck, pork chop jowl salami cow filet mignon pork.\n", + "\n", + "Mike\n", + ] + } + ``` +??? example "Parse Error" + An error caused by a malformed delimiter or nested delimiters. + + ??? info "Properties" + | Key | Value | + | - | - | + |`type`|`parse_error`| + | `datetime` | RFC 8061 formatted datetime string. | + | `file_path` | Full path of the item with the error. | + | `expected` | Description of what the parser was expecting. | + | `got` | Line that cause the parse error. | + | `line_num` | The line number in the item that caused the parse error. | + + ??? quote "Parsed Example" + ```js + { + 'type': 'parse_error', + 'datetime': '2020-10-16T10:44:45', + 'file_path': '/home/pier/e/benne238/webqueue2/q-snapshot/aae/2', + 'expected': 'Did not encounter a reply-from-user ending delimiter', + 'got': 'Kris', + 'line_num': 468 + } + ``` + +### `Item.isLocked` +Type | Description +- | - +`Boolean` | A boolean showing whether or not a lockfile for the item is present. + +!!! example + ```js + console.log(Item.isLocked) + // Expexted Output + true + ``` + +### `Item.userEmail` +Type | Description +- | - +`String` | The email address of the person who this item is from. + +!!! example + ```js + console.log(Item.userEmail) + // Expexted Output + "campb303@purdue.edu" + ``` + +### `Item.userName` +Type | Description +- | - +`String` | The real name of the person who this item is from. + +!!! example + ```js + console.log(Item.userName) + // Expexted Output + "Justin Campbell" + ``` + +### `Item.userAlias`: +Type | Description +- | - +`String` | The Purdue career account alias of the person this item is from. + +!!! example + ```js + console.log(Item.userAlias) + // Expexted Output + "campb303" + ``` + +### `Item.assignedTo`: +Type | Description +- | - +`String` | The Purdue career account alias of the person this item is assigned to. + +!!! example + ```js + console.log(Item.assignedTo) + // Expexted Output + "sundeep" + ``` + +### `Item.subject:` +Type | Description +- | - +`String` | The subject of the original message for this item. + +!!! example + ```js + console.log(Item.subject) + // Expexted Output + "Can't Access Outlook" + ``` + +### `Item.status` +Type | Description +- | - +`String` | The most recent status update for the item. + +!!! example + ```js + console.log(Item.status) + // Expexted Output + "Waiting for Reply" + ``` + +### `Item.priority:` +Type | Description +- | - +`String` | The most recent priority for this item. + +!!! example + ```js + console.log(Item.priority) + // Expexted Output + "COVID" + ``` + +### `Item.department` +Type | Description +- | - +`String` | The most recent department for this item. + +!!! example + ```js + console.log(Item.department) + // Expexted Output + "Bussiness Office" + ``` + +### `Item.dateReceived` +Type | Description +- | - +`String` | An ISO 8601 formatted time string showing the date this item was created. + +!!! example + ```js + console.log(Item.dateReceived) + // Expexted Output + "2021-04-03T00:48:38+00:00" + ``` \ No newline at end of file diff --git a/docs/api/Queues.md b/docs/api/Queues.md new file mode 100644 index 0000000..0ef4ce9 --- /dev/null +++ b/docs/api/Queues.md @@ -0,0 +1,53 @@ +# Queues + +A collection of [Items](/api/Items/). + +## Endpoint +``` +GET /api/{queue} +``` +### Parameters +Name | Value +- | - +`queue` | The name of the queue. + +### Query String Options +Name | Description | Possible Values +- | - | - +`headersOnly` | When `"True"`, only meta data will be loaded. When `"False"` content will be parsed. (Defaults to `"False"`.) | `"True"` \| `"True"` + +### Return Codes +Code | Description +- | - +`200 - Ok` | On success. +`500 - Internal Service Error` | On failure. + +## Properties + +### `Queue.name` +Type | Description +- | - +`String` | The name of the queue. + +!!! example + ```js + console.log(Queue.name) + // Expected Output + "ce" + ``` + +### `Queue.items` +Type | Description +- | - +`Array` | The [Items](/api/Items/) in the queue. + +!!! example + ```js + console.log(Queue.items) + // Expected Output + [ + { queue: "ce", number: 01, lastUpdated: "2021-03-26T17:12:00-0400" ... } + { queue: "ce", number: 02, lastUpdated: "2021-05-29T17:12:00-0400" ... } + { queue: "ce", number: 03, lastUpdated: "2020-12-29T17:12:00-0400" ... } + ] + ``` \ No newline at end of file diff --git a/docs/api/awesome-pages.yaml b/docs/api/awesome-pages.yaml new file mode 100644 index 0000000..4bfc79f --- /dev/null +++ b/docs/api/awesome-pages.yaml @@ -0,0 +1,9 @@ +# YAML Configuration for Awesome Pages mkdocs Plugin +# See: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin +nav: + - Getting Started.md + - Installation.md + - Authentication.md + - Items.md + - Queues.md + - ... \ No newline at end of file diff --git a/docs/api/get_access_token.js b/docs/api/get_access_token.js new file mode 100644 index 0000000..c3b5929 --- /dev/null +++ b/docs/api/get_access_token.js @@ -0,0 +1,10 @@ +fetch( + "{{ production_url }}/api/login", + { + method: "POST", + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ "username": USERNAME, "password": PASSWORD}) + } + ) + .then( resp => resp.json() ) + .then( data => console.log( data.access_token )) \ No newline at end of file diff --git a/docs/api/making-docs.md b/docs/api/making-docs.md new file mode 100644 index 0000000..0e2ee6b --- /dev/null +++ b/docs/api/making-docs.md @@ -0,0 +1,10 @@ +## Making Docs + +The api documentation uses mkdocs to create documenation, which is based in markdown. + +All of the doucmentation can be found in the `docs` directory in the `webqueue2-api` repo, and all of the api specific documentation can be found in the `docs/api` directory. + + +## Mkdocs configuration + +A basic version of mkdocs can be installed by using `pip install mkdocs`, however, this is not recomened as the current implementation makes use of several plugins that enahance the basic features of mkdocs. To install all of the dependencies for this, follow the steps for setting up the dev environment. \ No newline at end of file diff --git a/docs/awesome-pages.yaml b/docs/awesome-pages.yaml new file mode 100644 index 0000000..5e7d488 --- /dev/null +++ b/docs/awesome-pages.yaml @@ -0,0 +1,6 @@ +# YAML Configuration for Awesome Pages mkdocs Plugin +# See: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin +nav: + - index.md + - 'Dev Environment Setup Guide.md' + - API: api \ No newline at end of file diff --git a/docs/css/custom_css.css b/docs/css/custom_css.css new file mode 100644 index 0000000..f1ca50a --- /dev/null +++ b/docs/css/custom_css.css @@ -0,0 +1,9 @@ +/* Make Tables Fullwidth */ +.md-typeset__table { + width: 100% !important; +} +table { + width: 100% !important; + display: table !important; + border-spacing: 0px !important; +} \ No newline at end of file diff --git a/docs/images/Dev Environment Setup Guide/Add SSH Key to GitHub.gif b/docs/images/Dev Environment Setup Guide/Add SSH Key to GitHub.gif new file mode 100644 index 0000000..b812c5b Binary files /dev/null and b/docs/images/Dev Environment Setup Guide/Add SSH Key to GitHub.gif differ diff --git a/docs/images/Dev Environment Setup Guide/Connect to Remote SSH Host.gif b/docs/images/Dev Environment Setup Guide/Connect to Remote SSH Host.gif new file mode 100644 index 0000000..3d644dd Binary files /dev/null and b/docs/images/Dev Environment Setup Guide/Connect to Remote SSH Host.gif differ diff --git a/docs/images/Dev Environment Setup Guide/Open Repo.gif b/docs/images/Dev Environment Setup Guide/Open Repo.gif new file mode 100644 index 0000000..08cd8e3 Binary files /dev/null and b/docs/images/Dev Environment Setup Guide/Open Repo.gif differ diff --git a/docs/images/Dev Environment Setup Guide/Open VS Code Integrated Terminal.gif b/docs/images/Dev Environment Setup Guide/Open VS Code Integrated Terminal.gif new file mode 100644 index 0000000..505b847 Binary files /dev/null and b/docs/images/Dev Environment Setup Guide/Open VS Code Integrated Terminal.gif differ diff --git a/docs/images/Dev Environment Setup Guide/Remote Folder Open.png b/docs/images/Dev Environment Setup Guide/Remote Folder Open.png new file mode 100644 index 0000000..6a53201 Binary files /dev/null and b/docs/images/Dev Environment Setup Guide/Remote Folder Open.png differ diff --git a/docs/images/Dev Environment Setup Guide/Remote SSH Icon.png b/docs/images/Dev Environment Setup Guide/Remote SSH Icon.png new file mode 100644 index 0000000..32f210c Binary files /dev/null and b/docs/images/Dev Environment Setup Guide/Remote SSH Icon.png differ diff --git a/docs/images/Dev Environment Setup Guide/VS Code Select Platform.png b/docs/images/Dev Environment Setup Guide/VS Code Select Platform.png new file mode 100644 index 0000000..f69c3ed Binary files /dev/null and b/docs/images/Dev Environment Setup Guide/VS Code Select Platform.png differ diff --git a/docs/images/favicon.ico b/docs/images/favicon.ico new file mode 100755 index 0000000..f444752 Binary files /dev/null and b/docs/images/favicon.ico differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..80934d4 --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +See the [API Docs](api/Getting Started/). \ No newline at end of file diff --git a/docs/javascript/tablesort.js b/docs/javascript/tablesort.js new file mode 100644 index 0000000..394f8cf --- /dev/null +++ b/docs/javascript/tablesort.js @@ -0,0 +1,11 @@ +/* + * Configure Tablesort for sortable tables + * See: https://squidfunk.github.io/mkdocs-material/reference/data-tables/#sortable-tables + */ + +app.document$.subscribe(function() { + var tables = document.querySelectorAll("article table") + tables.forEach(function(table) { + new Tablesort(table) + }) +}) diff --git a/docs/package-structure.md b/docs/package-structure.md new file mode 100644 index 0000000..e2450bb --- /dev/null +++ b/docs/package-structure.md @@ -0,0 +1,63 @@ +## API package structure + +The api can be broken down into 4 distinct parts: +1. The logger +2. The api +3. The ECNQueue +4. The initialization scripts + +### Logger + +The logger is its own subpackage and script which is used for outputting log information to both the terminal and a specified file. The logger uses the logging module to create log outputs with specified format. + +### API + +The api is the link between the ECNQueue and the frontend. The api continuously runs in the background, and takes urls matching a specified format from the front end and translates those as functions to be executed in the ECNQueue. The api uses flask and flask restful to do this + +### ECNQueue + +The ECNQueue subpackage is a parser in the backend for all of the items in stored in the queue directory. The ECNQueue parses the text documents in the queue and outputs structured json, which is then transfered to the fronted via the api. + +### The initialization scripts + +The initialization scripts take in user input (either via command line or wrapper script) and initialize the logger, ECNQueue, and API according to the user input. After initializing and implementing all of the necessary configurations, the initialization scripts start the api. These scripts use argparse to get user input and subprocess to launch the api via gunicorn. While not its own subpackage, the scripts are located directly in the `webqueue2_api` directory. + + +## Structure + +```none +webqueue2_api/ # webqueue2_api parent package +│ +├── api/ # api subpackage +│ ├── __init__.py +│ ├── auth.py +│ └── resources/ +│ ├── __init__.py +│ ├── item.py +│ ├── login.py +│ ├── queue.py +│ ├── queue_list.py +│ └── refresh_access_token.py +│ +├── ECNQueue/ # ecnqueue subpackge +│ ├── __init__.py +│ ├── Item.py +│ ├── parser/ +│ │ ├── __init__.py +│ │ └── parser.py +│ ├── Queue.py +│ └── utils.py +│ +├── logger/ # logger subpackage +│ └─── __init__.py +│ +├── utils/ # utils subpackage +│ ├── __init__.py +│ └── tree.py +│ +├── __init__.py +├── __main__.py # parses user input +├── global_configs.py # stores the config file location +├── start.py # returns a flask object ater initializing the user configs +└── validate_arguments.py # validates all of teh user passed arguments +``` \ No newline at end of file diff --git a/docstring-format.mustache b/docstring-format.mustache new file mode 100644 index 0000000..7fc2a20 --- /dev/null +++ b/docstring-format.mustache @@ -0,0 +1,38 @@ +{{! Google Docstring Template }} +{{summaryPlaceholder}} + +{{extendedSummaryPlaceholder}} + +Example: + [example] + +{{#parametersExist}} +Args: +{{#args}} + {{var}} ({{typePlaceholder}}): {{descriptionPlaceholder}} +{{/args}} +{{#kwargs}} + {{var}} ({{typePlaceholder}}, optional): {{descriptionPlaceholder}}. Defaults to {{&default}}. +{{/kwargs}} +{{/parametersExist}} + +{{#exceptionsExist}} +Raises: +{{#exceptions}} + {{type}}: {{descriptionPlaceholder}} +{{/exceptions}} +{{/exceptionsExist}} + +{{#returnsExist}} +Returns: +{{#returns}} + {{typePlaceholder}}: {{descriptionPlaceholder}} +{{/returns}} +{{/returnsExist}} + +{{#yieldsExist}} +Yields: +{{#yields}} + {{typePlaceholder}}: {{descriptionPlaceholder}} +{{/yields}} +{{/yieldsExist}} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..2cda46b --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,67 @@ +################################################################################ +# mkdocs Settings +# See: https://www.mkdocs.org/user-guide/configuration/ +################################################################################ + +site_name: webqueue2 API +repo_url: https://github.itap.purdue.edu/ECN/webqueue2-api +plugins: + - search + # Awesome Pages Settings + # See: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin + - awesome-pages: + filename: awesome-pages.yaml + # Macros Settings + # See: https://squidfunk.github.io/mkdocs-material/reference/variables/ + - macros +markdown_extensions: + - toc: + permalink: ⚓︎ + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.tabbed + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + + + +################################################################################ +# Material for mkdocs Settings +# See: https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/ +################################################################################ + +theme: + name: material + palette: + # Used to toggle dark mode. + # scheme: slate + primary: deep purple + accent: teal + icon: + logo: material/book-open + favicon: images/favicon.ico + features: + - navigation.sections + +extra: + social: + - icon: fontawesome/brands/slack + link: https://purdueecn.slack.com/archives/C019K7PSPNW + name: webqueue2 on Slack + - icon: fontawesome/brands/github + link: https://github.itap.purdue.edu/ECN/webqueue2-api + name: webqueue2-API on GitHub + # Variables for Macros + production_url: "https://engineering.purdue.edu/webqueue/webqueue2/build" + example_access_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTY1NTIyMDIsIm5iZiI6MTYxNjU1MjIwMiwianRpIjoiZDgyNGM1MWItM2JmNy00ZDUzLWE0YTgtY2VhZWQ5ZmVjNGYzIiwiZXhwIjoxNjE2NTUzMTAyLCJzdWIiOiJjYW1wYjMwMyIsImZyZXNoIjpmYWxzZSwidHlwZSI6ImFjY2VzcyIsImNzcmYiOiI1Yjk5NWQ5OS05YjIzLTQyMjYtYTc0OC1lMmQ5OTA4MDkzOTQifQ.6z7EReDfhPkBkuAMHEvDuMDV4wVbqrWSjQXdRyv_5hE" + example_csrf_token: "7b7c1ea8-f6bb-4204-99af-cd4124a69d89" + +extra_javascript: + - https://cdnjs.cloudflare.com/ajax/libs/tablesort/5.2.1/tablesort.min.js + - javascript/tablesort.js +extra_css: + - css/custom_css.css \ No newline at end of file diff --git a/setup.py b/setup.py index bc5fc39..b22ce74 100644 --- a/setup.py +++ b/setup.py @@ -1,46 +1,33 @@ -import setuptools, logging -from pathlib import Path - -# Configure the logger -logger_name = "webqueueapi_install_log" -logger = logging.getLogger(logger_name) -logger.setLevel(logging.DEBUG) - -# See Formatting Details: https://docs.python.org/3/library/logging.html#logrecord-attributes -# Example: Jan 28 2021 12:19:28 venv-manager : [INFO] Message -log_message_format = "%(asctime)s %(name)s : [%(levelname)s] %(message)s" -# See Time Formatting Details: https://docs.python.org/3.6/library/time.html#time.strftime -# Example: Jan 28 2021 12:19:28 -log_time_format = "%b %d %Y %H:%M:%S" -log_formatter = logging.Formatter(log_message_format, log_time_format) - -# Configure output to stdout -stream_handler = logging.StreamHandler() -stream_handler.setFormatter(log_formatter) -stream_handler.setLevel(logging.INFO) -logger.addHandler(stream_handler) - -# Configure out to logfile, located in '/tmp/webqueueapi install log.log' -log_file_path = Path(f"/tmp/{logger_name}.log") -file_handler = logging.FileHandler(log_file_path) -file_handler.setFormatter(log_formatter) -logger.addHandler(file_handler) - -logger.debug("Attempting to install webqueue2-api package") - -setuptools.setup( - name="webqueue2-api", +from setuptools import setup, find_packages + +# Define Dependencies for Extra Requires +conditional_dependencies = { + "dev":[ + "pylint" + ], + "docs": [ + "mkdocs", + "mkdocs-material", + "mkdocs-awesome-pages-plugin", + "mkdocs-macros-plugin" + ], +} + +def get_all_dependencies(): + """Returns a single array of all dependencies.""" + dependencies = [] + for condition in conditional_dependencies.keys(): + dependencies += conditional_dependencies[condition] + return dependencies + +setup( + name="webqueue2_api", version="0.9.1", description="A library for managing Purdue ECN's queue system.", - py_modules=['api', 'ECNQueue'], python_requires='>=3.6', + packages=find_packages(), install_requires = [ - # General Utilities - "pipdeptree", "gunicorn", - "pylint", - - # API "python-dotenv", "Flask-RESTful", "python-dateutil", @@ -49,12 +36,11 @@ "PyJWT == 1.*", # Custom version of python-ldap without SASL requirements "python-ldap @ git+https://github.itap.purdue.edu/ECN/python-ldap/@python-ldap-3.3.1", - - # API Documentation - "mkdocs", - "mkdocs-material", - "mkautodoc" - ] -) - -logger.info("webqueue2-api package installed sucessfully") \ No newline at end of file + "easyad", + ], + extras_require={ + "dev": conditional_dependencies["dev"], + "docs": conditional_dependencies["docs"], + "all": get_all_dependencies() + } +) \ No newline at end of file diff --git a/ECNQueue.py b/webqueue2_api/ECNQueue.py similarity index 78% rename from ECNQueue.py rename to webqueue2_api/ECNQueue.py index 17d9c5a..4fbcd65 100644 --- a/ECNQueue.py +++ b/webqueue2_api/ECNQueue.py @@ -85,25 +85,31 @@ def isValidItemName(name: str) -> bool: class Item: """A single issue. - Example: - # Create an Item (ce100) - >>> item = Item("ce", 100) - - Attributes: - lastUpdated: An ISO 8601 formatted time string showing the last time the file was updated according to the filesystem. - headers: A list of dictionaries containing header keys and values. - content: A list of section dictionaries. - isLocked: A boolean showing whether or not a lockfile for the item is present. - userEmail: The email address of the person who this item is from. - userName: The real name of the person who this item is from. - userAlias: The Purdue career account alias of the person this item is from. - assignedTo: The Purdue career account alias of the person this item is assigned to - subject: The subject of the original message for this item. - status: The most recent status update for the item. - priority: The most recent priority for this item. - department: The most recent department for this item. - dateReceived: The date this item was created. - jsonData: A JSON serializable representation of the Item. + **Example:** + + ``` + # Create an Item (ce100) + >>> item = Item("ce", 100) + ``` + + **Attributes:** + + ``` + lastUpdated: An ISO 8601 formatted time string showing the last time the file was updated according to the filesystem. + headers: A list of dictionaries containing header keys and values. + content: A list of section dictionaries. + isLocked: A boolean showing whether or not a lockfile for the item is present. + userEmail: The email address of the person who this item is from. + userName: The real name of the person who this item is from. + userAlias: The Purdue career account alias of the person this item is from. + assignedTo: The Purdue career account alias of the person this item is assigned to + subject: The subject of the original message for this item. + status: The most recent status update for the item. + priority: The most recent priority for this item. + department: The most recent department for this item. + dateReceived: The date this item was created. + jsonData: A JSON serializable representation of the Item. + ``` """ def __init__(self, queue: str, number: int) -> None: @@ -155,11 +161,15 @@ def __init__(self, queue: str, number: int) -> None: def __getLastUpdated(self) -> str: """Returns last modified time of item reported by the filesystem in mm-dd-yy hh:mm am/pm format. - Example: - 07-23-20 10:34 AM + **Example:** + ``` + 07-23-20 10:34 AM + ``` - Returns: - str: last modified time of item reported by the filesystem in mm-dd-yy hh:mm am/pm format. + **Returns:** + ``` + str: last modified time of item reported by the filesystem in mm-dd-yy hh:mm am/pm format. + ``` """ # TODO: Simplify this code block by allowing __getFormattedDate to accept milliseconds since the epoch. unixTime = os.path.getmtime(self.__path) @@ -170,8 +180,10 @@ def __getLastUpdated(self) -> str: def __getRawItem(self) -> list: """Returns a list of all lines in the item file - Returns: - list: List of all the lines in the item file + **Returns:** + ``` + list: List of all the lines in the item file + ``` """ with open(self.__path, errors="replace") as file: return file.readlines() @@ -179,13 +191,18 @@ def __getRawItem(self) -> list: def __getHeaderBoundary(self) -> int: """Returns the 0 based line number where the Item headers stop. - Example: The header end would be on line 13 - 12: X-ECN-Queue-Original-URL: - 13: - 14: I need help. - - Returns: - int: line number where the Item headers end + **Example:** + ``` + The header end would be on line 13 + 12: X-ECN-Queue-Original-URL: + 13: + 14: I need help. + ``` + + **Returns:** + ``` + int: line number where the Item headers end + ``` """ for lineNumber, line in enumerate(self.__rawItem): if line == "\n": @@ -195,16 +212,20 @@ def __parseHeaders(self) -> list: """Returns a list containing dictionaries of header type and data. Removes queue prefixes and whitespace. - Examples: - "[ce] QStatus: Dont Delete\\nFrom: Justin Campbell \\n" - becomes - [ - {"QStatus": "Don't Delete"}, - {"From": "Justin Campbell "} - ] + **Examples:** + ``` + "[ce] QStatus: Dont Delete\\nFrom: Justin Campbell \\n" + becomes + [ + {"QStatus": "Don't Delete"}, + {"From": "Justin Campbell "} + ] + ``` - Returns: - list: Header dicts + **Returns:** + ``` + list: Header dicts + ``` """ headerString = "" @@ -413,25 +434,31 @@ def __parseSections(self) -> list: def __directoryParsing(self, directoryStartLine: int) -> dict: """Returns a dictionary with directory information - Example: - Name: Nestor Fabian Rodriguez Buitrago - Login: rodri563 - Computer: ce-205-38 (128.46.205.67) - Location: HAMP G230 - Email: rodri563@purdue.edu - Phone: 7654766893 - Office: HAMP G230 - UNIX Dir: /home/bridge/b/rodri563 - Zero Dir: U=\\bridge.ecn.purdue.edu\rodri563 - User ECNDB: http://eng.purdue.edu/jump/2e8399a - Host ECNDB: http://eng.purdue.edu/jump/2e83999 - Subject: Autocad installation - - Args: - directoryStartLine (int): line number within the item that the directory starts on - - Returns: - dict: dictionary that splits each line within the directory into a key and a value + **Example:** + + ``` + Name: Nestor Fabian Rodriguez Buitrago + Login: rodri563 + Computer: ce-205-38 (128.46.205.67) + Location: HAMP G230 + Email: rodri563@purdue.edu + Phone: 7654766893 + Office: HAMP G230 + UNIX Dir: /home/bridge/b/rodri563 + Zero Dir: U=\\\\bridge.ecn.purdue.edu\\rodri563 + User ECNDB: http://eng.purdue.edu/jump/2e8399a + Host ECNDB: http://eng.purdue.edu/jump/2e83999 + Subject: Autocad installation + ``` + + **Args:** + + `directoryStartLine (int)`: line number within the item that the directory starts on + + **Returns:** + ``` + dict: dictionary that splits each line within the directory into a key and a value + ``` """ directoryInformation = {"type": "directory_information"} @@ -525,22 +552,29 @@ def __directoryParsing(self, directoryStartLine: int) -> dict: def __assignmentParsing(self, contentStart: int) -> list: """Returns a list with assignment information dictionaries - Example: - Assigned-To: campb303 - Assigned-To-Updated-Time: Tue, 23 Jun 2020 13:27:00 EDT - Assigned-To-Updated-By: campb303 + **Example:** - Args: - contentStart (int): line number where the content starts + ``` + Assigned-To: campb303 + Assigned-To-Updated-Time: Tue, 23 Jun 2020 13:27:00 EDT + Assigned-To-Updated-By: campb303 + ``` - Returns: - list: [ - {"type": "assignment", - "datetime": datetime of the assignment, - "by": user who initiated the assignment, - "to": user who was assigned - }, - ] + **Args:** + + `contentStart (int)`: line number where the content starts + + **Returns:** + + ``` + list: [ + {"type": "assignment", + "datetime": datetime of the assignment, + "by": user who initiated the assignment, + "to": user who was assigned + }, + ] + ``` """ assignmentList = [] @@ -587,24 +621,28 @@ def __assignmentParsing(self, contentStart: int) -> list: def __initialMessageParsing(self, content: list) -> dict: """Returns a dictionary with initial message information - Example: - \n - Testtest\n - \n - - Args: - content (list): content of the initial message - - Returns: - dict: - "type": "initial_message", - "datetime": datetime the initial message was sent, - "from_name": from_name, - "from_email": user_email, - "to": [{email, name}], - "cc": [{email, name}], - "subject": initial message subject - "content": content of the initial message + **Example:** + ``` + \n + Testtest\n + \n + ``` + + **Args:** + `content (list)`: content of the initial message + + **Returns:** + ``` + dict: + "type": "initial_message", + "datetime": datetime the initial message was sent, + "from_name": from_name, + "from_email": user_email, + "to": [{email, name}], + "cc": [{email, name}], + "subject": initial message subject + "content": content of the initial message + ``` """ initialMessageDictionary = {} @@ -665,24 +703,27 @@ def __initialMessageParsing(self, content: list) -> dict: def __editParsing(self, content: list, lineNum: int) -> dict: """Returns a dictionary with edit information - Example: - *** Edited by: campb303 at: 06/23/20 13:27:56 ***\n - \n - This be an edit my boy\n - \n - \n - \n + **Example:** + ``` + \*\*\* Edited by: campb303 at: 06/23/20 13:27:56 \*\*\* + This be an edit my boy - Args: - content (list): content of an edit - lineNum (int): line number of an edit within an item - Returns: - dict: a dictionary with these keys, - "type": "edi", - "by": initiator of the edit, - "datetime": datetime of the edit, - "content": content of the edit + ``` + **Args:** + + `content (list)`: content of an edit + + `lineNum (int)`: line number of an edit within an item + + **Returns:** + ``` + dict: a dictionary with these keys: + "type": "edit", + "by": initiator of the edit, + "datetime": datetime of the edit, + "content": content of the edit + ``` """ # Edit Info dictionary @@ -724,25 +765,30 @@ def __editParsing(self, content: list, lineNum: int) -> dict: def __replyToParsing(self, content: list, lineNum: int) -> dict: """Returns a dictionary with reply to user information - Example: - *** Replied by: campb303 at: 06/23/20 13:28:18 ***\n - \n - This be a reply my son\n - \n - Justin\n - ECN\n - \n + **Example:** + ``` + \*\*\* Replied by: campb303 at: 06/23/20 13:28:18 \*\*\* - Args: - content (list): content of a reply to user - lineNum (int): line number of a reply to user in an item + This be a reply my son + + Justin + ECN + ``` - Returns: - dict: a dictionary with these keys, - "type": "reply_to_user", - "by": initiator of the reply to user, - "datetime": datetime of the reply to user, - "content": content of the reply to user + **Args:** + + `content (list)`: content of a reply to user + + `lineNum (int)`: line number of a reply to user in an item + + **Returns:** + ``` + dict: a dictionary with these keys, + "type": "reply_to_user", + "by": initiator of the reply to user, + "datetime": datetime of the reply to user, + "content": content of the reply to user + ``` """ replyInfo = {} @@ -781,20 +827,27 @@ def __replyToParsing(self, content: list, lineNum: int) -> dict: def __statusParsing(self, content: list, lineNum: int) -> dict: """Returns a dictionary with status information - Example: - *** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\n - Dont Delete\n + **Example:** + ``` + \*\*\* Status updated by: campb303 at: 6/23/2020 13:26:55 \*\*\* + Dont Delete + \n + ``` - Args: - content (list): The content of a status update - lineNum (int): The line number of a status update in an item + **Args:** - Returns: - dict: a dictionary with these keys, - "type": "status", - "by": initiator of the status update, - "datetime": datetime of the status update, - "content": content of the status update + `content (list)`: The content of a status update + + `lineNum (int)`: The line number of a status update in an item + + **Returns:** + ``` + dict: a dictionary with these keys, + "type": "status", + "by": initiator of the status update, + "datetime": datetime of the status update, + "content": content of the status update + ``` """ statusInfo = {} @@ -836,41 +889,47 @@ def __statusParsing(self, content: list, lineNum: int) -> dict: def __userReplyParsing(self, replyContent: list, lineNumber: int) -> dict: """Returns a dictionary with user reply information - Example: - === Additional information supplied by user ===\n - \n - Subject: Re: Beepboop\n - From: Justin Campbell \n - Date: Tue, 23 Jun 2020 13:30:45 -0400\n - X-ECN-Queue-Original-Path: /home/pier/e/queue/Attachments/inbox/2020-06-23/212-original.txt\n - X-ECN-Queue-Original-URL: https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/212-original.txt\n - \n - Huzzah!\n - \n - ===============================================\n - \n - Args: - replyContent (list): The entire section of a reply-from-user - lineNumber (int): The line number of the begining of a reply-from-user section within and item + **Example:** + ``` + === Additional information supplied by user === + + Subject: Re: Beepboop\n + From: Justin Campbell \n + Date: Tue, 23 Jun 2020 13:30:45 -0400\n + X-ECN-Queue-Original-Path: /home/pier/e/queue/Attachments/inbox/2020-06-23/212-original.txt\n + X-ECN-Queue-Original-URL: https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/212-original.txt\n + + Huzzah! + + =============================================== + \n + ``` + **Args:** + + `replyContent (list)`: The entire section of a reply-from-user + + `lineNumber (int)`: The line number of the begining of a reply-from-user section within and item Returns: - dict: a dictionary with these keys, - "type": "reply_from_user", - "from_name": name of the user that sent the reply, - "from_email": email of the user that sent the reply, - "subject": subject of the reply, - "datetime": the datetime of the reply, - "cc": [ - {"name": name of the carbon copied recipient, - "email": email of the carbon copied recipient - }, - ] - "content": content of the reply - "headers": [ - {"type": headerType, - "content": content - }, - ] + ``` + dict: a dictionary with these keys, + "type": "reply_from_user", + "from_name": name of the user that sent the reply, + "from_email": email of the user that sent the reply, + "subject": subject of the reply, + "datetime": the datetime of the reply, + "cc": [ + {"name": name of the carbon copied recipient, + "email": email of the carbon copied recipient + }, + ] + "content": content of the reply + "headers": [ + {"type": headerType, + "content": content + }, + ] + ``` """ replyFromInfo = {} @@ -987,22 +1046,23 @@ def __userReplyParsing(self, replyContent: list, lineNumber: int) -> dict: def __getFormattedSectionContent(self, sectionContent: list) -> list: """Returns a list with message content that is stripped of unnecessary newlines and begining delimiters - Example: - *** Edited by: mph at: 02/21/20 10:27:16 ***\n - \n - Still need to rename machines - but the networking issue now seems to \n - be resolved via another ticket.\n - \n - \n - \n - \n - \n + **Example:** + ``` + \*\*\* Edited by: mph at: 02/21/20 10:27:16 \*\*\* + + Still need to rename machines - but the networking issue now seems to \n + be resolved via another ticket. + \n + ``` - Args: - sectionContent (list): The section content of a parsed section + **Args:** - Returns: - list: the section content of a parsed section without any delimiters and unnecessary newlines + `sectionContent (list)`: The section content of a parsed section + + **Returns:** + ``` + list: the section content of a parsed section without any delimiters and unnecessary newlines + ``` """ # Continually removes the first line of sectionContent if it is a newline or delimiter in each iteration while len(sectionContent) > 1: @@ -1037,22 +1097,29 @@ def __getFormattedSectionContent(self, sectionContent: list) -> list: def __errorParsing(self, line: str, lineNum: int, expectedSyntax: str) -> dict: """Returns a dictionary with error parse information when a line is malformed - Example: - "*** Status updated by: ewhile at: 5/7/2020 10:59:11 *** sharing between\n" + **Example:** + ``` + \*\*\* Status updated by: ewhile at: 5/7/2020 10:59:11 \*\*\* + ``` - Args: - line (str): line of that threw error - lineNum (int): line number in the item that threw error - expectedSyntax (str): a message stating the syntax the line should follow + **Args:** - Returns: - dict: a dictionary with these keys, - "type": "parse_error", - "datetime": time the error was encountered, - "file_path": path of the item with erroneos line, - "expected": expectedSyntax, - "got": line, - "line_num": lineNum + `line (str)`: line of that threw error + + `lineNum (int)`: line number in the item that threw error + + `expectedSyntax (str)`: a message stating the syntax the line should follow + + **Returns:** + ``` + dict: a dictionary with these keys, + "type": "parse_error", + "datetime": time the error was encountered, + "file_path": path of the item with erroneos line, + "expected": expectedSyntax, + "got": line, + "line_num": lineNum + ``` """ errorDictionary = {} @@ -1081,14 +1148,19 @@ def __errorParsing(self, line: str, lineNum: int, expectedSyntax: str) -> dict: def __getSortedSections(self, sectionsList: list) -> list: """Sorts the sections chronologically by datetime - Example: - [example] need to do + **Example:** + ``` + [example] need to do + ``` - Args: - sections (list): the list of sections to be sorted + **Args:** - Returns: - list: a list of sections sorted by datetime + `sections (list)`: the list of sections to be sorted + + **Returns:** + ``` + list: a list of sections sorted by datetime + ``` """ sectionsLength = len(sectionsList) sortedSections = [] @@ -1120,14 +1192,21 @@ def __getSortedSections(self, sectionsList: list) -> list: def __isLocked(self) -> Union[str, bool]: """Returns a string info about the lock if true and a bool False if false - Example: A file is locked - "CE 100 is locked by campb303 using qvi" + **Example:** + ``` + A file is locked + "CE 100 is locked by campb303 using qvi" + ``` - Example: a file is not locked - False + **Example:** + ``` + a file is not locked + False + ``` - Returns: - Union[str, bool]: String with info about lock if true, bool False if false + **Returns:** + + `Union[str, bool]`: String with info about lock if true, bool False if false """ lockFile = self.__path + ".lck" if os.path.exists(lockFile): @@ -1143,19 +1222,28 @@ def __getMostRecentHeaderByType(self, headerType: str) -> str: """Return the data of most recent header of the given type. If no header of that type exists, return an empty string. - Example: Requesting a Status header that does exist - __getMostRecentHeaderByType("Status") - becomes "Waiting for Reply" + **Example:** + ``` + Requesting a Status header that does exist + __getMostRecentHeaderByType("Status") + becomes "Waiting for Reply" + ``` - Example: Requesting a Status header that doesn't exist - __getMostRecentHeaderByType("Status") - becomes "" + **Example:** + ``` + Requesting a Status header that doesn't exist + __getMostRecentHeaderByType("Status") + becomes "" + ``` - Args: - headerType (str): Type of header to return. + **Args:** - Returns: - str: data of most recent header of the given type or empty string. + `headerType (str)`: Type of header to return. + + **Returns:** + ``` + str: data of most recent header of the given type or empty string. + ``` """ for header in self.headers: if header["type"] == headerType: @@ -1166,15 +1254,21 @@ def __parseFromData(self, data: str) -> str: """Parse From header and return requested data. Returns empty string if requested data is unavailable. - Examples: From data is "From: Campbell, Justin " - __parseFromData(data="userName") returns "Campbell, Justin" - __parseFromData(data="userEmail") returns "campb303@purdue.edu" + **Examples:** + ``` + From data is "From: Campbell, Justin " + __parseFromData(data="userName") returns "Campbell, Justin" + __parseFromData(data="userEmail") returns "campb303@purdue.edu" + ``` - Args: - data (str): The data desired; can be "userName" or "userEmail". + **Args:** - Returns: - str: userName, userEmail or empty string. + `data (str)`: The data desired; can be "userName" or "userEmail". + + **Returns:** + ``` + str: userName, userEmail or empty string. + ``` """ fromHeader = self.__getMostRecentHeaderByType("From") userName, userEmail = email.utils.parseaddr(fromHeader) @@ -1191,14 +1285,22 @@ def __getUserAlias(self) -> str: """Returns user's Career Account alias if present. If Career Account alias isn't present, returns empty string. - Example: Email from campb303@purdue.edu - userAlias = "campb303" - - Example: Email from spam@spammer.net - userAlias = "" - - Returns: - str: User's Career Account alias if present or empty string + **Example:** + ``` + Email from campb303@purdue.edu + userAlias = "campb303" + ``` + + **Example:** + ``` + Email from spam@spammer.net + userAlias = "" + ``` + + **Returns:** + ``` + str: User's Career Account alias if present or empty string + ``` """ @@ -1220,8 +1322,10 @@ def __getFormattedDate(self, date: str) -> str: Returns empty string if the string argument passed to the function is not a datetime. See: https://en.wikipedia.org/wiki/ISO_8601 - Returns: - str: Properly formatted date/time recieved or empty string. + **Returns:** + ``` + str: Properly formatted date/time recieved or empty string. + ``` """ try: # This date is never meant to be used. The default attribute is just to set timezone. @@ -1237,8 +1341,10 @@ def __getFormattedDate(self, date: str) -> str: def toJson(self) -> dict: """Returns a JSON safe representation of the item. - Returns: - dict: JSON safe representation of the item. + **Returns:** + ``` + dict: JSON safe representation of the item. + ``` """ return self.jsonData @@ -1249,14 +1355,20 @@ def __repr__(self) -> str: class Queue: """A collection of items. - Example: - # Create a queue (ce) - >>> queue = Queue("ce") + **Example:** + + ``` + # Create a queue (ce) + >>> queue = Queue("ce") + ``` + + **Attributes:** - Attributes: - name: The name of the queue. - items: A list of Items in the queue. - jsonData: A JSON serializable representation of the Queue. + ``` + name: The name of the queue. + items: A list of Items in the queue. + jsonData: A JSON serializable representation of the Queue. + ``` """ def __init__(self, name: str) -> None: @@ -1272,8 +1384,10 @@ def __init__(self, name: str) -> None: def __getItems(self) -> list: """Returns a list of items for this Queue - Returns: - list: a list of items for this Queue + **Returns:** + ``` + list: a list of items for this Queue + ``` """ items = [] @@ -1293,8 +1407,10 @@ def toJson(self) -> dict: The JSON representation of every item in the Queue is added to the Queue's JSON data then the Queue's JSON data is returned. - Returns: - dict: JSON safe representation of the Queue + **Returns:** + ``` + dict: JSON safe representation of the Queue + ``` """ items = [] for item in self.items: @@ -1312,11 +1428,15 @@ def __repr__(self) -> str: def getValidQueues() -> list: """Returns a list of queues on the filesystem excluding ignored queues. - Example: - ["bidc", "me", "ce"] + **Example:** + ``` + ["bidc", "me", "ce"] + ``` - Returns: - list: Valid queues + **Returns:** + ``` + list: Valid queues + ``` """ queues = [] @@ -1333,20 +1453,23 @@ def getValidQueues() -> list: def getQueueCounts() -> list: """Returns a list of dictionaries with the number of items in each queue. - Example: - [ - { - name: "me", - number_of_items: 42 - }, - { - name: "bidc", - number_of_items: 3 - } - ] - - Returns: - list: Dictionaries with the number of items in each queue. + **Example:** + ``` + [ + { + name: "me", + number_of_items: 42 + }, + { + name: "bidc", + number_of_items: 3 + } + ] + ``` + **Returns:** + ``` + list: Dictionaries with the number of items in each queue. + ``` """ queueInfo = [] for queue in getValidQueues(): @@ -1363,8 +1486,10 @@ def getQueueCounts() -> list: def loadQueues() -> list: """Return a list of Queues for each queue. - Returns: - list: list of Queues for each queue. + **Returns:** + ``` + list: list of Queues for each queue. + ``` """ queues = [] diff --git a/webqueue2_api/__init__.py b/webqueue2_api/__init__.py new file mode 100644 index 0000000..3bec1e8 --- /dev/null +++ b/webqueue2_api/__init__.py @@ -0,0 +1 @@ +from . import api, ECNQueue \ No newline at end of file diff --git a/api.py b/webqueue2_api/api.py similarity index 70% rename from api.py rename to webqueue2_api/api.py index 73128f6..e8a0618 100644 --- a/api.py +++ b/webqueue2_api/api.py @@ -10,7 +10,7 @@ from ldap.filter import escape_filter_chars # pylint says this is an error but it works so ¯\_(ツ)_/¯ from ldap import INVALID_CREDENTIALS as LDAP_INVALID_CREDENTIALS -import ECNQueue +from . import ECNQueue # Load envrionment variables for ./.env dotenv.load_dotenv() @@ -40,8 +40,6 @@ app.config["JWT_COOKIE_SECURE"] = False if os.environ.get("ENVIRONMENT") == "dev" else True # Restrict cookies using SameSite=strict flag app.config["JWT_COOKIE_SAMESITE"] = "strict" -# Restrict refresh tokens to /token/refresh endpoint -app.config["JWT_REFRESH_COOKIE_PATH"] = '/tokens/refresh' # Set the cookie key for CRSF validation string # This is the default value. Adding it for easy reference app.config["JWT_REFRESH_CSRF_HEADER_NAME"] = "X-CSRF-TOKEN" @@ -65,6 +63,10 @@ def user_is_valid(username: str, password: str) -> bool: if (username == "" or password == ""): return False + # Check for adm account + if username.endswith("adm"): + return False; + # Initialize EasyAD config = { "AD_SERVER": "boilerad.purdue.edu", @@ -105,20 +107,24 @@ class Login(Resource): def post(self) -> tuple: """Validates username/password and returns both access and refresh tokens. - Return Codes: - 200 (OK): On success. - 401 (Unauthroized): When username or password are incorrect. - 422 (Unprocessable Entitiy): When the username or password can't be parsed. - - Example: - curl -X POST - -H "Content-Type: application/json" - -d '{"username": "bob", "password": "super_secret"}' - - { "access_token": fjr09hfp09h932jp9ruj3.3r8ihf8h0w8hr08ifhj804h8i.8h48ith08ity409hip0t4 } - - Returns: - tuple: Response containing tokens and HTTP response code. + **Return Codes:** + ``` + 200 (OK): On success. + 401 (Unauthroized): When username or password are incorrect. + 422 (Unprocessable Entitiy): When the username or password can't be parsed. + ``` + **Example:** + ``` + curl -X POST + -H "Content-Type: application/json" + -d '{"username": "bob", "password": "super_secret"}' + + { "access_token": fjr09hfp09h932jp9ruj3.3r8ihf8h0w8hr08ifhj804h8i.8h48ith08ity409hip0t4 } + ``` + **Returns:** + ``` + tuple: Response containing tokens and HTTP response code. + ``` """ if not request.is_json: return ({ "message": "JSON missing from request body"}, 422) @@ -158,34 +164,41 @@ class Item(Resource): def get(self, queue: str, number: int) -> tuple: """Returns the JSON representation of the item requested. - Return Codes: - 200 (OK): On success. - - Example: - /api/ce/100 returns: - { - "lastUpdated": "07-23-20 10:11 PM", - "headers": [...], - "content": [...], - "isLocked": "ce 100 is locked by knewell using qvi", - "userEmail": "campb303@purdue.edu", - "userName": "Justin Campbell", - "userAlias": "campb303", - "assignedTo": "campb303", - "subject": "Beepboop", - "status": "Dont Delete", - "priority": "", - "deparment": "", - "building": "", - "dateReceived": "Tue, 23 Jun 2020 13:25:51 -0400" - } - - Args: - queue (str): The queue of the item requested. - item (int): The number of the item requested. - - Returns: - tuple: Item as JSON and HTTP response code. + **Return Codes:** + ``` + 200 (OK): On success. + ``` + + **Example:** + ``` + /api/ce/100 returns: + { + "lastUpdated": "07-23-20 10:11 PM", + "headers": [...], + "content": [...], + "isLocked": "ce 100 is locked by knewell using qvi", + "userEmail": "campb303@purdue.edu", + "userName": "Justin Campbell", + "userAlias": "campb303", + "assignedTo": "campb303", + "subject": "Beepboop", + "status": "Dont Delete", + "priority": "", + "deparment": "", + "building": "", + "dateReceived": "Tue, 23 Jun 2020 13:25:51 -0400" + } + ``` + **Args:** + ``` + queue (str): The queue of the item requested. + item (int): The number of the item requested. + ``` + + **Returns:** + ``` + tuple: Item as JSON and HTTP response code. + ``` """ return (ECNQueue.Item(queue, number).toJson(), 200) @@ -194,14 +207,20 @@ class Queue(Resource): def get(self, queues: str) -> tuple: """Returns the JSON representation of the queue requested. - Return Codes: - 200 (OK): On success. + **Return Codes:** + ``` + 200 (OK): On success. + ``` - Args: - queues (str): Plus (+) deliminited list of queues. + **Args:** + ``` + queues (str): Plus (+) deliminited list of queues. + ``` - Returns: - tuple: Queues as JSON and HTTP response code. + **Returns:** + ``` + tuple: Queues as JSON and HTTP response code. + ``` """ queues_requested = queues.split("+") @@ -216,23 +235,28 @@ class QueueList(Resource): def get(self) -> tuple: """Returns a list of dictionaries with the number of items in each queue. - Return Codes: - 200 (OK): On success. - - Example: - [ - { - name: "me", - number_of_items: 42 - }, - { - name: "bidc", - number_of_items: 3 - } - ] - - Returns: - tuple: Queues and item counts as JSON and HTTP response code. + **Return Codes:** + ``` + 200 (OK): On success. + ``` + + **Example:** + ``` + [ + { + name: "me", + number_of_items: 42 + }, + { + name: "bidc", + number_of_items: 3 + } + ] + ``` + **Returns:** + ``` + tuple: Queues and item counts as JSON and HTTP response code. + ``` """ return (ECNQueue.getQueueCounts(), 200) @@ -245,4 +269,4 @@ def get(self) -> tuple: api.add_resource(QueueList, "/api/get_queues") if __name__ == "__main__": - app.run() \ No newline at end of file + app.run()