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.
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.
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.
defregister(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
defhandler_function1(**kwargs):
importos
session=kwargs['session']# same as the Flask session variable
request=kwargs['request']# same as the Flask request variable
defkitten_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'notinrequest.args.keys():
returnrqtools.get_400(None,"No kitten name provided!")
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: