Skip to content
Snippets Groups Projects
Commit 841a03b7 authored by Ferenc Schulcz's avatar Ferenc Schulcz
Browse files

Rewrite README

parent 1b670472
Branches
No related tags found
No related merge requests found
SFPhoton extendable web application SFPhoton modular web application
=================================== ================================
Available at: [sferi.hu](https://sferi.hu/) or [sfphoton.tk](https://sfphoton.tk/) Available at: [sferi.hu](https://sferi.hu/).
SFPhoton modular web application is a small web framework that implements a website, relying on modules for easy extension. This is the core of my personal website with a diverse portfolio of services - and my first larger project in Flask.
Looking back, I would have been better off using Flask Blueprints instead of modules, but hey, this approach works, too. I don't think many people should rely on mysolution but I decided to publish the source as only time can tell if it will help anyone.
Usage Usage
----- -----
...@@ -10,77 +14,125 @@ Usage ...@@ -10,77 +14,125 @@ Usage
- python - python
- mongodb - mongodb
- mysql (for the Minecraft module, optional)
- some python libs (install them via `pip install -r requirements.txt`) - some python libs (install them via `pip install -r requirements.txt`)
### Run ### Run
Initialize the database with the `db_manadement/init_db.py` script. Initialize the database with the `db_manadement/init_db.py` script.
In debug mode: `FLASK_APP=router FLASK_SECRET=<secret> FLASK_ENV=development AUTHSCH_ID=<id> AUTHSCH_SECRET=<secret> flask run` In debug mode: `./run-debug.sh`
In production mode: please refer to the [Flask deployment guide](https://flask.palletsprojects.com/en/2.0.x/deploying/uwsgi/) for a decent deploy. In production mode: please refer to the [Flask deployment guide](https://flask.palletsprojects.com/en/3.0.x/deploying/) for a decent deploy. After that, you should run `./run.sh`.
Modules Modules
------- -------
This web application has minimal code as its core, and most of its functionality is implemented as modules. A module can provide endpoints (e.g. /my-module-functionality) and menu items for users added to it. Authentication is automatic, but endpoints may implement authorization. The modules are stored in the `service_plugins` folder and their corresponding HTML templates in the `templates/plugins` folder. You can turn them off by removing them and add any other plugins at will. This web application has minimal code as its core, and most of its functionality is implemented as modules. A module can provide endpoints (e.g. /my-module-functionality) and menu items for users added to it. Authentication and authorization is automatic, but endpoints may implement it, too. The modules are stored in the `plugins` folder in separate subfolders. SFPhoton modular web application will add any module found in the `plugins` folder on start.
### Existing modules
For some open-source modules that I maintain, look into my projects here. Some recommended ones both for using and for reference:
### Current modules - **[Profile](https://git.sch.bme.hu/schulczf/sfphoton-web-plugin-profile)**: a module to let users change their passwords. Useful if you have local users.
- **[Minecraft](https://git.sch.bme.hu/schulczf/sfphoton-web-plugin-minecraft)**: a feature-rich web ecosystem for a Minecraft server, with live map viewing, player authorization and an admin interface.
- **[DynDNS](https://git.sch.bme.hu/schulczf/sfphoton-web-plugin-dyndns)**: frontend for [my DynDNS server](https://git.sch.bme.hu/schulczf/dyndns-server).
- **[Dummy Cat](https://git.sch.bme.hu/schulczf/sfphoton-web-plugin-dummy_cat)**: a dead simple module that showed an AI-generated cat until thiscatdoesnotexist.com went offline. Still, an easy example module.
| Module | File | Template(s) | Description | ### Installing a module
| ------ | ---- | ----------- | ----------- |
| Admin | admin.py | admin.html | Manage users and permissions Just copy its folder into `plugins` and restart SFPhoton modular web application. If you want to use an open-source module from the previous section, just navigate to `plugins` and clone the appropriate repository.
| Dummy Artwork | dummy_artwork.py | artwork.html | Dummy service. Display an AI-generated artwork
| Dummy Cat | dummy_cat.py | cat.html | Dummy service. Display an AI-generated cat
| Minecraft | minecraft.py | minecraft.html, dynmap.html | Additional services for my Minecraft server
| Minecraft Admin | minecraft_admin.py | minecraft_admin.html | Manage Minecraft users
| Profile | profile.py | profile.html | Change your password
| VM Admin | vm_service.py | vmadmin.html | Manage libvirt VMs
### Creating your module ### Creating your module
1. Add a module in the `service_plugins` directory so that it will be imported automatically. It should be named like `my_module.py`. #### Layout of a plugin
1. Modules must implement at least one `register` function that will be called to register endpoints and menu items. This function must look like this:
```python Your plugin should be a subfolder in the `plugins` folder. At least, your plugin folder should have a Python file (e. g. `myplugin.py`) to implement the logic and endpoints for your module.
def register(add_endpoint, add_menu):
add_endpoint('url1', handler_function1) Once your plugin grows, it is a good idea to add further subfolder for templates and static files. After all, your layout will probably look like this:
add_endpoint('url2', handler_function2)
add_endpoint('url3', handler_function3) ```
add_menu('my_module', 'Text1', 'url1') plugins/
add_menu('my_module', 'Text2', 'url2') |\_myplugin.py
|\_templates/
| \_myview.html
\_static
|\_image1.png
\_style.css
``` ```
Not all endpoint must have menu items and not all menu items must have an endpoint. Please be careful so that no two endpoints are the same across modules - this part needs a bit of cleanup, sorry.
This function would create the URLs `http://yourdomain.com/url1`, `http://yourdomain.com/url2` and `http://yourdomain.com/url3`, and assign the corresponding handler functions to them. It would also show two menu items for every user with permission on the `my_module` service. The two menu items would show `Text1` and `Text2` and link to the corresponding URLs. #### How to code a plugin
1. Add some HTML templates for your endpoints in the `templates/plugins` folder, if you want.
1. Write the handlers for your endpoints. A handler must take a single `**kwargs` parameter since this will hold some context for the request and some utility functions. A basic handler looks like this: Your plugin should do two things:
1. Provide endpoints for HTTP requests
1. Register those endpoints in SFPhoton modular web application
_Creating endpoints_
Every endpoint should be a plain old Python function that only takes `**kwargs` as a parameter. `**kwargs` is a dictionary which will hold 3 things:
- `kwargs['session']` is the Flask `session` object
- `kwargs['request']` is the Flask `request` object
- `kwargs['rqtools']` is an object that has several useful functions
- `url_for()` creates a URL from an endpoint description, just as in Flask. To generate URLs for plugin endpoints, use `url_for('service', servicename='endpoint_name')` where `endpoint_name` is the name for the endpoint you register (like `/endpoint_name`), the others should remain constant.
- `redirect()` is the same as Flask's `redirect()` function.
- `get_xxx()` functions are to show HTTP error pages to users, like `get_404(None)` for a simple 404 Not found screen. You can print custom error messages like `get_404(None, "Custom error message.")`
- `render_template()` can be used to render any jinja2 template like with Flask's `render_template()` function, but template locations are not limited to the framework's template folder. Instead, you have to supply the path to your template file like `render_template('/path/to/template')`. Keyword arguments are optional like with Flask. **Warning**: you should sanitize your template file locations to prevent path traversal attacks.
Endpoints are expected to return the same thing as Flask endpoints would do. Thus, an example endpoint which renders a template based on a URL parameter would look like this:
```python ```python
def handler_function1(**kwargs): import os
session = kwargs['session'] # same as the Flask session variable
request = kwargs['request'] # same as the Flask request variable def kitten_picture(**kwargs):
rqtools = kwargs['rqtools'] # helper functions to ease work in service plugins; see router.py for members request = kwargs['request']
rqtools = kwargs['rqtools']
# If no kitten name is provided as URL parameter, respond with HTTP 400
if 'kitten-name' not in request.args.keys():
return rqtools.get_400(None, "No kitten name provided!")
return rqtools.render_template('plugins/url1.html') # Render kitten template from the plugin folder's templates subfolder
# Provide kitten name as an argument for the template
template_path = os.path.join(os.path.dirname(__file__), 'templates', 'kitten.html')
return rqtools.render_template(template_path, kitten=request.args['kitten-name'])
``` ```
1. Optional: authorize your request if you want. Endpoints that do not deal with authorization are public, even if their link is not visible on the menu bar. An endpoint can do authorization in the following way:
_Registering endpoints_
Endpoints should be registered to handle requests or to show up in the menu. For this, every endpoint has to provide a `register()` function, which will be called on startup.
This function should have exactly one parameter, in which it will be provided a pointer to the `add_endpoint()` function it can call to configure endpoints.
Expected behaviour of the `register()` function is best shown on an example:
```python ```python
import services def register(add_endpoint):
import db
def handler_function2(**kwargs): # Do any initialization for you plugin here
session = kwargs['session'] ...
rqtools = kwargs['rqtools']
if 'username' not in session.keys(): # Register endpoints like this:
return rqtools.redirect(rqtools.url_for('login', next='url2'))
if not services.authorize_user(session['username'], 'my_module'): # Public endpoint, available for GET requests at /kittens
return rqtools.get_403(None) # For this to work, you should have a function called kitten_picture() that implements the enpoint (see above)
return rqtools.render_template('plugins/url2.html') add_endpoint(endpoint_id='kittenimage', handler=kitten_picture, permission_name=None)
# Public endpoint, available for PUT requests at /newkitten
add_endpoint(endpoint_id='newkitten', handler=new_kitten, permission_name=None, method='PUT')
# Public endpoint but with a menu item
add_endpoint(endpoint_id='kittenworld', handler=kittens_mainpage, permission_name=None, menutext='Kitten world')
# Endpoint only for logged-in users that have the 'kittenadmin' permission
# This also registers a menu item that is visible only for authorized users
add_endpoint(endpoint_id='kittenadmin', handler=kittens_adminpage, permission_name='kittenadmin', menutext='Kitten admin')
# Another public endpoint, but for kitten admins this menu item is grouped together with 'Kitten admin'
add_endpoint(endpoint_id='kittenchaos', handler=kittens_chaos_game, permission_name=None, menutext='Kitten chaos', menu_permission_hint='kittenadmin')
``` ```
1. Restart the server after module installation.
### Removing a module User management
---------------
1. Remove the module from the `service_plugins` folder. Look into the `db_management` folder for a bunch of scripts.
1. Remove the module's templates from the `templates/plugins` folder. \ No newline at end of file
1. Restart the server.
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment