structables 0.3.16__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {structables-0.3.16 → structables-0.4.1}/.forgejo/workflows/docker.yml +4 -7
- {structables-0.3.16 → structables-0.4.1}/.forgejo/workflows/release.yml +1 -1
- {structables-0.3.16 → structables-0.4.1}/.gitignore +2 -0
- {structables-0.3.16 → structables-0.4.1}/Dockerfile +2 -4
- {structables-0.3.16 → structables-0.4.1}/PKG-INFO +27 -11
- {structables-0.3.16 → structables-0.4.1}/README.md +25 -9
- {structables-0.3.16 → structables-0.4.1}/docker-compose-example-dev.yml +2 -1
- {structables-0.3.16 → structables-0.4.1}/docker-compose-example.yml +3 -2
- structables-0.4.1/instances.json +54 -0
- {structables-0.3.16 → structables-0.4.1}/pyproject.toml +2 -2
- structables-0.4.1/renovate.json +6 -0
- structables-0.4.1/requirements-dev.txt +6 -0
- structables-0.4.1/src/structables/config.py +59 -0
- structables-0.4.1/src/structables/main.py +65 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/routes/category.py +21 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/routes/contest.py +39 -4
- {structables-0.3.16 → structables-0.4.1}/src/structables/routes/main.py +125 -49
- {structables-0.3.16 → structables-0.4.1}/src/structables/routes/member.py +22 -3
- structables-0.4.1/src/structables/routes/proxy.py +358 -0
- structables-0.4.1/src/structables/static/css/iframe.css +114 -0
- structables-0.4.1/src/structables/static/css/style.css +1190 -0
- structables-0.4.1/src/structables/static/img/magnifying-glass-solid.svg +2 -0
- structables-0.4.1/src/structables/templates/base.html +58 -0
- structables-0.4.1/src/structables/templates/category.html +35 -0
- structables-0.4.1/src/structables/templates/footer.html +17 -0
- structables-0.4.1/src/structables/templates/header.html +49 -0
- structables-0.4.1/src/structables/templates/iframe.html +23 -0
- structables-0.4.1/src/structables/templates/index.html +37 -0
- structables-0.4.1/src/structables/templates/projects.html +49 -0
- structables-0.4.1/src/structables/utils/data.py +76 -0
- structables-0.4.1/src/structables/utils/helpers.py +551 -0
- structables-0.3.16/src/structables/config.py +0 -13
- structables-0.3.16/src/structables/main.py +0 -43
- structables-0.3.16/src/structables/routes/proxy.py +0 -60
- structables-0.3.16/src/structables/static/css/style.css +0 -282
- structables-0.3.16/src/structables/static/dist/css/bootstrap.min.css +0 -6
- structables-0.3.16/src/structables/static/dist/css/bootstrap.min.css.map +0 -1
- structables-0.3.16/src/structables/static/img/magnifying-glass-solid.svg +0 -1
- structables-0.3.16/src/structables/templates/base.html +0 -27
- structables-0.3.16/src/structables/templates/category.html +0 -68
- structables-0.3.16/src/structables/templates/footer.html +0 -21
- structables-0.3.16/src/structables/templates/header.html +0 -39
- structables-0.3.16/src/structables/templates/iframe.html +0 -12
- structables-0.3.16/src/structables/templates/index.html +0 -45
- structables-0.3.16/src/structables/templates/projects.html +0 -56
- structables-0.3.16/src/structables/utils/data.py +0 -56
- structables-0.3.16/src/structables/utils/helpers.py +0 -463
- {structables-0.3.16 → structables-0.4.1}/.env.example +0 -0
- {structables-0.3.16 → structables-0.4.1}/LICENSE +0 -0
- {structables-0.3.16 → structables-0.4.1}/cronjob.txt +0 -0
- {structables-0.3.16 → structables-0.4.1}/entrypoint.sh +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/__init__.py +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/routes/__init__.py +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/static/img/logo.png +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/static/img/logo_lg.png +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/400.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/404.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/429.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/500.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/archives.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/article-review.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/article.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/collection.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/contest.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/contests.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/member-instructables.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/member.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/privacypolicy.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/sitemap.html +0 -0
- {structables-0.3.16 → structables-0.4.1}/src/structables/templates/style.html +0 -0
|
@@ -4,17 +4,14 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
tags:
|
|
6
6
|
- "*"
|
|
7
|
+
workflow_dispatch:
|
|
7
8
|
|
|
8
9
|
jobs:
|
|
9
10
|
docker:
|
|
10
11
|
name: Docker Build and Push to Docker Hub
|
|
11
12
|
container:
|
|
12
|
-
image:
|
|
13
|
+
image: catthehacker/ubuntu:act-latest
|
|
13
14
|
steps:
|
|
14
|
-
- name: Install dependencies
|
|
15
|
-
run: |
|
|
16
|
-
apt update
|
|
17
|
-
apt install -y docker.io
|
|
18
15
|
- name: Set up QEMU
|
|
19
16
|
uses: docker/setup-qemu-action@v3
|
|
20
17
|
- name: Set up Docker Buildx
|
|
@@ -25,9 +22,9 @@ jobs:
|
|
|
25
22
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
26
23
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
27
24
|
- name: Build and push to Docker Hub
|
|
28
|
-
uses: docker/build-push-action@
|
|
25
|
+
uses: docker/build-push-action@v6
|
|
29
26
|
with:
|
|
30
27
|
push: true
|
|
31
28
|
tags: |
|
|
32
29
|
privatecoffee/structables:latest
|
|
33
|
-
privatecoffee/structables:${{
|
|
30
|
+
privatecoffee/structables:${{ github.ref_name }}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM alpine:3.
|
|
1
|
+
FROM alpine:3.21
|
|
2
2
|
|
|
3
3
|
ENV APP_ENV=/opt/venv
|
|
4
4
|
ENV PATH="${APP_ENV}/bin:$PATH"
|
|
@@ -12,10 +12,8 @@ RUN $APP_ENV/bin/pip install --no-cache-dir pip && \
|
|
|
12
12
|
$APP_ENV/bin/pip install /app && \
|
|
13
13
|
adduser -S -D -H structables
|
|
14
14
|
|
|
15
|
-
COPY entrypoint.sh /entrypoint.sh
|
|
16
|
-
|
|
17
15
|
EXPOSE 8002
|
|
18
16
|
|
|
19
17
|
USER structables
|
|
20
18
|
|
|
21
|
-
ENTRYPOINT ["/entrypoint.sh"]
|
|
19
|
+
ENTRYPOINT ["/app/entrypoint.sh"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structables
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A simple frontend for Instructables
|
|
5
5
|
Project-URL: Homepage, https://git.private.coffee/privatecoffee/structables
|
|
6
6
|
Project-URL: Bug Tracker, https://git.private.coffee/privatecoffee/structables/issues
|
|
@@ -672,7 +672,7 @@ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
|
672
672
|
Classifier: Operating System :: OS Independent
|
|
673
673
|
Classifier: Programming Language :: Python :: 3
|
|
674
674
|
Requires-Python: >=3.10
|
|
675
|
-
Requires-Dist:
|
|
675
|
+
Requires-Dist: beautifulsoup4
|
|
676
676
|
Requires-Dist: flask
|
|
677
677
|
Requires-Dist: markdown2[all]
|
|
678
678
|
Description-Content-Type: text/markdown
|
|
@@ -692,18 +692,26 @@ An open source alternative front-end to Instructables. This is a fork of <a href
|
|
|
692
692
|
|
|
693
693
|
## Instances
|
|
694
694
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
|
698
|
-
|
|
|
699
|
-
| [structables.
|
|
695
|
+
<!-- START_INSTANCE_LIST type:eq=clearnet -->
|
|
696
|
+
|
|
697
|
+
| URL | Provided by | Country | Notes |
|
|
698
|
+
| ---------------------------------------------------------------------- | ---------------------------------------------- | ---------------- | ------------- |
|
|
699
|
+
| [structables.private.coffee](https://structables.private.coffee) | [Private.coffee](https://private.coffee) | Austria 🇦🇹 🇪🇺 | Main instance |
|
|
700
|
+
| [structables.bloat.cat](https://structables.bloat.cat) | [Bloat.cat](https://bloat.cat) | Germany 🇩🇪 🇪🇺 | |
|
|
701
|
+
| [structables.darkness.services](https://structables.darkness.services) | [Darkness.services](https://darkness.services) | United States 🇺🇸 | |
|
|
702
|
+
|
|
703
|
+
<!-- END_INSTANCE_LIST -->
|
|
700
704
|
|
|
701
705
|
### Tor Hidden Services
|
|
702
706
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
|
706
|
-
|
|
|
707
|
+
<!-- START_INSTANCE_LIST type:eq=onion -->
|
|
708
|
+
|
|
709
|
+
| URL | Provided by | Country | Notes |
|
|
710
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ---------------- | ------------- |
|
|
711
|
+
| [structables.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion](http://structables.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion) | [Private.coffee](https://private.coffee) | Austria 🇦🇹 🇪🇺 | Main instance |
|
|
712
|
+
| [structables.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion](http://structables.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion) | [Darkness.services](https://darkness.services) | United States 🇺🇸 | |
|
|
713
|
+
|
|
714
|
+
<!-- END_INSTANCE_LIST -->
|
|
707
715
|
|
|
708
716
|
### Adding Your Instance
|
|
709
717
|
|
|
@@ -729,6 +737,8 @@ Of course, you can also join our [Matrix room](https://matrix.pcof.fi/#/#structa
|
|
|
729
737
|
|
|
730
738
|
### Production: Docker
|
|
731
739
|
|
|
740
|
+
**Note:** The Docker image is now hosted on [git.private.coffee](https://git.private.coffee/privatecoffee/structables/packages) instead of Docker Hub. Please use the new image name `git.private.coffee/privatecoffee/structables` instead of `privatecoffee/structables` to continue receiving updates.
|
|
741
|
+
|
|
732
742
|
1. Copy `.env.example` to `.env` and adjust the settings as necessary
|
|
733
743
|
2. Copy `docker-compose-example.yml` to `docker-compose.yml` and adjust it as necessary, for example modifying resource limits or changing the port/host configuration
|
|
734
744
|
3. Build and run the Docker container:
|
|
@@ -760,6 +770,12 @@ Structables supports the use of the following environment variables for configur
|
|
|
760
770
|
- `STRUCTABLES_UNSAFE`: If set, allow embedding untrusted iframes (if unset, display a warning and allow loading the content manually)
|
|
761
771
|
- `STRUCTABLES_PRIVACY_FILE`: The path to a text file or Markdown file (with .md suffix) to use for the Privacy Policy page (if unset, try `privacy.txt` or `privacy.md` in the working directory, or fall back to a generic message)
|
|
762
772
|
- `STRUCTABLES_DEBUG`: If set, log additional debug information to stdout
|
|
773
|
+
- `STRUCTABLES_THEME`: Allows selecting a theme for the frontend. Currently, only `dark` and `light` are supported. If not set, it will be automatically detected based on the user's system settings, and a toggle will be provided in the header.
|
|
774
|
+
- `STRUCTABLES_CACHE_ENABLED`: Whether to enable caching of proxied content (default: true). Set to "false" or "0" to disable caching.
|
|
775
|
+
- `STRUCTABLES_CACHE_DIR`: The directory to use for caching proxied content (default: `structables_cache` within the temporary directory as returned by `tempfile.gettempdir()`)
|
|
776
|
+
- `STRUCTABLES_CACHE_MAX_AGE`: The maximum age of cached content in seconds before it's considered stale (default: 604800 seconds, or 1 week)
|
|
777
|
+
- `STRUCTABLES_CACHE_MAX_SIZE`: The maximum size of the cache directory in bytes (default: 1073741824 bytes, or 1GB)
|
|
778
|
+
- `STRUCTABLES_CACHE_CLEANUP_INTERVAL`: How often to run the cache cleanup process in seconds (default: 3600 seconds, or 1 hour)
|
|
763
779
|
|
|
764
780
|
## License
|
|
765
781
|
|
|
@@ -13,18 +13,26 @@ An open source alternative front-end to Instructables. This is a fork of <a href
|
|
|
13
13
|
|
|
14
14
|
## Instances
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
| [structables.
|
|
16
|
+
<!-- START_INSTANCE_LIST type:eq=clearnet -->
|
|
17
|
+
|
|
18
|
+
| URL | Provided by | Country | Notes |
|
|
19
|
+
| ---------------------------------------------------------------------- | ---------------------------------------------- | ---------------- | ------------- |
|
|
20
|
+
| [structables.private.coffee](https://structables.private.coffee) | [Private.coffee](https://private.coffee) | Austria 🇦🇹 🇪🇺 | Main instance |
|
|
21
|
+
| [structables.bloat.cat](https://structables.bloat.cat) | [Bloat.cat](https://bloat.cat) | Germany 🇩🇪 🇪🇺 | |
|
|
22
|
+
| [structables.darkness.services](https://structables.darkness.services) | [Darkness.services](https://darkness.services) | United States 🇺🇸 | |
|
|
23
|
+
|
|
24
|
+
<!-- END_INSTANCE_LIST -->
|
|
21
25
|
|
|
22
26
|
### Tor Hidden Services
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
+
<!-- START_INSTANCE_LIST type:eq=onion -->
|
|
29
|
+
|
|
30
|
+
| URL | Provided by | Country | Notes |
|
|
31
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ---------------- | ------------- |
|
|
32
|
+
| [structables.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion](http://structables.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion) | [Private.coffee](https://private.coffee) | Austria 🇦🇹 🇪🇺 | Main instance |
|
|
33
|
+
| [structables.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion](http://structables.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion) | [Darkness.services](https://darkness.services) | United States 🇺🇸 | |
|
|
34
|
+
|
|
35
|
+
<!-- END_INSTANCE_LIST -->
|
|
28
36
|
|
|
29
37
|
### Adding Your Instance
|
|
30
38
|
|
|
@@ -50,6 +58,8 @@ Of course, you can also join our [Matrix room](https://matrix.pcof.fi/#/#structa
|
|
|
50
58
|
|
|
51
59
|
### Production: Docker
|
|
52
60
|
|
|
61
|
+
**Note:** The Docker image is now hosted on [git.private.coffee](https://git.private.coffee/privatecoffee/structables/packages) instead of Docker Hub. Please use the new image name `git.private.coffee/privatecoffee/structables` instead of `privatecoffee/structables` to continue receiving updates.
|
|
62
|
+
|
|
53
63
|
1. Copy `.env.example` to `.env` and adjust the settings as necessary
|
|
54
64
|
2. Copy `docker-compose-example.yml` to `docker-compose.yml` and adjust it as necessary, for example modifying resource limits or changing the port/host configuration
|
|
55
65
|
3. Build and run the Docker container:
|
|
@@ -81,6 +91,12 @@ Structables supports the use of the following environment variables for configur
|
|
|
81
91
|
- `STRUCTABLES_UNSAFE`: If set, allow embedding untrusted iframes (if unset, display a warning and allow loading the content manually)
|
|
82
92
|
- `STRUCTABLES_PRIVACY_FILE`: The path to a text file or Markdown file (with .md suffix) to use for the Privacy Policy page (if unset, try `privacy.txt` or `privacy.md` in the working directory, or fall back to a generic message)
|
|
83
93
|
- `STRUCTABLES_DEBUG`: If set, log additional debug information to stdout
|
|
94
|
+
- `STRUCTABLES_THEME`: Allows selecting a theme for the frontend. Currently, only `dark` and `light` are supported. If not set, it will be automatically detected based on the user's system settings, and a toggle will be provided in the header.
|
|
95
|
+
- `STRUCTABLES_CACHE_ENABLED`: Whether to enable caching of proxied content (default: true). Set to "false" or "0" to disable caching.
|
|
96
|
+
- `STRUCTABLES_CACHE_DIR`: The directory to use for caching proxied content (default: `structables_cache` within the temporary directory as returned by `tempfile.gettempdir()`)
|
|
97
|
+
- `STRUCTABLES_CACHE_MAX_AGE`: The maximum age of cached content in seconds before it's considered stale (default: 604800 seconds, or 1 week)
|
|
98
|
+
- `STRUCTABLES_CACHE_MAX_SIZE`: The maximum size of the cache directory in bytes (default: 1073741824 bytes, or 1GB)
|
|
99
|
+
- `STRUCTABLES_CACHE_CLEANUP_INTERVAL`: How often to run the cache cleanup process in seconds (default: 3600 seconds, or 1 hour)
|
|
84
100
|
|
|
85
101
|
## License
|
|
86
102
|
|
|
@@ -2,15 +2,16 @@ services:
|
|
|
2
2
|
structables:
|
|
3
3
|
container_name: structables
|
|
4
4
|
restart: unless-stopped
|
|
5
|
-
image: privatecoffee/structables:latest
|
|
5
|
+
image: git.private.coffee/privatecoffee/structables:latest
|
|
6
6
|
ports:
|
|
7
7
|
- "127.0.0.1:8002:8002"
|
|
8
|
+
volumes:
|
|
9
|
+
- ./cache:/app/structables_cache
|
|
8
10
|
env_file: .env
|
|
9
11
|
security_opt:
|
|
10
12
|
- no-new-privileges:true
|
|
11
13
|
cap_drop:
|
|
12
14
|
- ALL
|
|
13
|
-
read_only: true
|
|
14
15
|
deploy:
|
|
15
16
|
resources:
|
|
16
17
|
limits:
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "structables.private.coffee",
|
|
4
|
+
"url": "https://structables.private.coffee",
|
|
5
|
+
"type": "clearnet",
|
|
6
|
+
"provider": {
|
|
7
|
+
"name": "Private.coffee",
|
|
8
|
+
"url": "https://private.coffee"
|
|
9
|
+
},
|
|
10
|
+
"location": "AT",
|
|
11
|
+
"notes": "Main instance"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "structables.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion",
|
|
15
|
+
"url": "http://structables.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion",
|
|
16
|
+
"type": "onion",
|
|
17
|
+
"provider": {
|
|
18
|
+
"name": "Private.coffee",
|
|
19
|
+
"url": "https://private.coffee"
|
|
20
|
+
},
|
|
21
|
+
"location": "AT",
|
|
22
|
+
"notes": "Main instance"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "structables.bloat.cat",
|
|
26
|
+
"url": "https://structables.bloat.cat",
|
|
27
|
+
"type": "clearnet",
|
|
28
|
+
"provider": {
|
|
29
|
+
"name": "Bloat.cat",
|
|
30
|
+
"url": "https://bloat.cat"
|
|
31
|
+
},
|
|
32
|
+
"location": "DE"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "structables.darkness.services",
|
|
36
|
+
"url": "https://structables.darkness.services",
|
|
37
|
+
"type": "clearnet",
|
|
38
|
+
"provider": {
|
|
39
|
+
"name": "Darkness.services",
|
|
40
|
+
"url": "https://darkness.services"
|
|
41
|
+
},
|
|
42
|
+
"location": "US"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "structables.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion",
|
|
46
|
+
"url": "http://structables.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion",
|
|
47
|
+
"type": "onion",
|
|
48
|
+
"provider": {
|
|
49
|
+
"name": "Darkness.services",
|
|
50
|
+
"url": "https://darkness.services"
|
|
51
|
+
},
|
|
52
|
+
"location": "US"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "structables"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Private.coffee Team", email="support@private.coffee" },
|
|
10
10
|
]
|
|
@@ -19,7 +19,7 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
21
|
"flask",
|
|
22
|
-
"
|
|
22
|
+
"beautifulsoup4",
|
|
23
23
|
"markdown2[all]",
|
|
24
24
|
]
|
|
25
25
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .utils.helpers import get_typesense_api_key
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Config:
|
|
13
|
+
DEBUG = os.environ.get("FLASK_DEBUG", os.environ.get("STRUCTABLES_DEBUG", False))
|
|
14
|
+
PORT = int(os.environ.get("STRUCTABLES_PORT", 8002))
|
|
15
|
+
LISTEN_HOST = os.environ.get("STRUCTABLES_LISTEN_HOST", "127.0.0.1")
|
|
16
|
+
INVIDIOUS = os.environ.get("STRUCTABLES_INVIDIOUS")
|
|
17
|
+
UNSAFE = os.environ.get("STRUCTABLES_UNSAFE", False)
|
|
18
|
+
PRIVACY_FILE = os.environ.get("STRUCTABLES_PRIVACY_FILE")
|
|
19
|
+
THEME = os.environ.get("STRUCTABLES_THEME", "auto")
|
|
20
|
+
TYPESENSE_API_KEY = get_typesense_api_key()
|
|
21
|
+
|
|
22
|
+
# Cache settings
|
|
23
|
+
CACHE_ENABLED = os.environ.get("STRUCTABLES_CACHE_ENABLED", "true").lower() not in (
|
|
24
|
+
"false",
|
|
25
|
+
"0",
|
|
26
|
+
"no",
|
|
27
|
+
"off",
|
|
28
|
+
"n",
|
|
29
|
+
)
|
|
30
|
+
CACHE_DIR = os.environ.get("STRUCTABLES_CACHE_DIR")
|
|
31
|
+
|
|
32
|
+
if CACHE_ENABLED and CACHE_DIR is None:
|
|
33
|
+
try:
|
|
34
|
+
CACHE_DIR = Path(tempfile.gettempdir()) / "structables_cache"
|
|
35
|
+
except FileNotFoundError:
|
|
36
|
+
CACHE_DIR = Path(os.getcwd()) / "structables_cache"
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
(CACHE_DIR / "test").write_text("test")
|
|
41
|
+
(CACHE_DIR / "test").unlink()
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.error(f"Could not create or write to cache directory: {e} - disabling cache")
|
|
44
|
+
CACHE_ENABLED = False
|
|
45
|
+
CACHE_DIR = None
|
|
46
|
+
|
|
47
|
+
CACHE_MAX_AGE = int(
|
|
48
|
+
os.environ.get("STRUCTABLES_CACHE_MAX_AGE", 60 * 60 * 24 * 7)
|
|
49
|
+
) # 1 week default
|
|
50
|
+
CACHE_MAX_SIZE = int(
|
|
51
|
+
os.environ.get("STRUCTABLES_CACHE_MAX_SIZE", 1024 * 1024 * 1024)
|
|
52
|
+
) # 1GB default
|
|
53
|
+
CACHE_CLEANUP_INTERVAL = int(
|
|
54
|
+
os.environ.get("STRUCTABLES_CACHE_CLEANUP_INTERVAL", 60 * 60)
|
|
55
|
+
) # 1 hour default
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def init_app(app):
|
|
59
|
+
pass
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
from flask import Flask
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from .config import Config
|
|
9
|
+
from .routes import init_routes
|
|
10
|
+
from .utils.data import update_data
|
|
11
|
+
from .routes.proxy import start_cache_cleanup_thread
|
|
12
|
+
|
|
13
|
+
# Configure logging
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
app = Flask(__name__, template_folder="templates", static_folder="static")
|
|
17
|
+
app.config.from_object(Config)
|
|
18
|
+
|
|
19
|
+
logger.debug("Initializing routes")
|
|
20
|
+
init_routes(app)
|
|
21
|
+
logger.debug("Performing initial data update")
|
|
22
|
+
update_data(app)
|
|
23
|
+
|
|
24
|
+
def background_update_data(app):
|
|
25
|
+
"""Runs the update_data function every 5 minutes.
|
|
26
|
+
|
|
27
|
+
This replaces the need for a cron job to update the data.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
app (Flask): The Flask app instance.
|
|
31
|
+
"""
|
|
32
|
+
logger.debug("Starting background update thread")
|
|
33
|
+
while True:
|
|
34
|
+
logger.debug("Running scheduled data update")
|
|
35
|
+
update_data(app)
|
|
36
|
+
logger.debug("Data update complete, sleeping for 5 minutes")
|
|
37
|
+
time.sleep(300)
|
|
38
|
+
|
|
39
|
+
def main():
|
|
40
|
+
if app.config["DEBUG"]:
|
|
41
|
+
logging.basicConfig(
|
|
42
|
+
level=logging.DEBUG,
|
|
43
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
logging.basicConfig(
|
|
47
|
+
level=logging.INFO,
|
|
48
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
logger.debug("Starting background update thread")
|
|
52
|
+
threading.Thread(target=background_update_data, args=(app,), daemon=True).start()
|
|
53
|
+
|
|
54
|
+
# Start the cache cleanup thread
|
|
55
|
+
start_cache_cleanup_thread(app)
|
|
56
|
+
|
|
57
|
+
logger.info(f"Starting Structables on {app.config['LISTEN_HOST']}:{app.config['PORT']}")
|
|
58
|
+
app.run(
|
|
59
|
+
port=app.config["PORT"],
|
|
60
|
+
host=app.config["LISTEN_HOST"],
|
|
61
|
+
debug=app.config["DEBUG"],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
from flask import redirect
|
|
2
2
|
from werkzeug.exceptions import NotFound
|
|
3
3
|
from ..utils.helpers import project_list, category_page
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
def init_category_routes(app):
|
|
7
10
|
@app.route("/<category>/<channel>/projects/")
|
|
8
11
|
def route_channel_projects(category, channel):
|
|
12
|
+
logger.debug(f"Rendering channel projects for {category}/{channel}")
|
|
9
13
|
return project_list(app, channel.title())
|
|
10
14
|
|
|
11
15
|
@app.route("/<category>/<channel>/projects/<sort>/")
|
|
12
16
|
def route_channel_projects_sort(category, channel, sort):
|
|
17
|
+
logger.debug(
|
|
18
|
+
f"Rendering channel projects for {category}/{channel} sorted by {sort}"
|
|
19
|
+
)
|
|
13
20
|
return project_list(
|
|
14
21
|
app,
|
|
15
22
|
channel.title(),
|
|
@@ -18,50 +25,62 @@ def init_category_routes(app):
|
|
|
18
25
|
|
|
19
26
|
@app.route("/<category>/projects/")
|
|
20
27
|
def route_category_projects(category):
|
|
28
|
+
logger.debug(f"Rendering category projects for {category}")
|
|
21
29
|
return project_list(app, category.title())
|
|
22
30
|
|
|
23
31
|
@app.route("/<category>/projects/<sort>/")
|
|
24
32
|
def route_category_projects_sort(category, sort):
|
|
33
|
+
logger.debug(f"Rendering category projects for {category} sorted by {sort}")
|
|
25
34
|
return project_list(app, category.title(), " Sorted by " + sort.title())
|
|
26
35
|
|
|
27
36
|
@app.route("/projects/")
|
|
28
37
|
def route_projects():
|
|
38
|
+
logger.debug("Rendering all projects")
|
|
29
39
|
return project_list(app, "")
|
|
30
40
|
|
|
31
41
|
@app.route("/projects/<sort>/")
|
|
32
42
|
def route_projects_sort(sort):
|
|
43
|
+
logger.debug(f"Rendering all projects sorted by {sort}")
|
|
33
44
|
return project_list(app, "", " Sorted by " + sort.title())
|
|
34
45
|
|
|
35
46
|
@app.route("/circuits/")
|
|
36
47
|
def route_circuits():
|
|
48
|
+
logger.debug("Rendering circuits category page")
|
|
37
49
|
return category_page(app, "Circuits")
|
|
38
50
|
|
|
39
51
|
@app.route("/workshop/")
|
|
40
52
|
def route_workshop():
|
|
53
|
+
logger.debug("Rendering workshop category page")
|
|
41
54
|
return category_page(app, "Workshop")
|
|
42
55
|
|
|
43
56
|
@app.route("/craft/")
|
|
44
57
|
def route_craft():
|
|
58
|
+
logger.debug("Rendering craft category page")
|
|
45
59
|
return category_page(app, "Craft")
|
|
46
60
|
|
|
47
61
|
@app.route("/cooking/")
|
|
48
62
|
def route_cooking():
|
|
63
|
+
logger.debug("Rendering cooking category page")
|
|
49
64
|
return category_page(app, "Cooking")
|
|
50
65
|
|
|
51
66
|
@app.route("/living/")
|
|
52
67
|
def route_living():
|
|
68
|
+
logger.debug("Rendering living category page")
|
|
53
69
|
return category_page(app, "Living")
|
|
54
70
|
|
|
55
71
|
@app.route("/outside/")
|
|
56
72
|
def route_outside():
|
|
73
|
+
logger.debug("Rendering outside category page")
|
|
57
74
|
return category_page(app, "Outside")
|
|
58
75
|
|
|
59
76
|
@app.route("/teachers/")
|
|
60
77
|
def route_teachers():
|
|
78
|
+
logger.debug("Rendering teachers category page")
|
|
61
79
|
return category_page(app, "Teachers", True)
|
|
62
80
|
|
|
63
81
|
@app.route("/<category>/<channel>/")
|
|
64
82
|
def route_channel_redirect(category, channel):
|
|
83
|
+
logger.debug(f"Channel redirect for {category}/{channel}")
|
|
65
84
|
if (
|
|
66
85
|
category == "circuits"
|
|
67
86
|
or category == "workshop"
|
|
@@ -71,6 +90,8 @@ def init_category_routes(app):
|
|
|
71
90
|
or category == "outside"
|
|
72
91
|
or category == "teachers"
|
|
73
92
|
):
|
|
93
|
+
logger.debug(f"Redirecting to /{category}/{channel}/projects/")
|
|
74
94
|
return redirect(f"/{category}/{channel}/projects/", 307)
|
|
75
95
|
else:
|
|
96
|
+
logger.warning(f"Invalid category: {category}")
|
|
76
97
|
raise NotFound()
|