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.
Files changed (70) hide show
  1. {structables-0.3.16 → structables-0.4.1}/.forgejo/workflows/docker.yml +4 -7
  2. {structables-0.3.16 → structables-0.4.1}/.forgejo/workflows/release.yml +1 -1
  3. {structables-0.3.16 → structables-0.4.1}/.gitignore +2 -0
  4. {structables-0.3.16 → structables-0.4.1}/Dockerfile +2 -4
  5. {structables-0.3.16 → structables-0.4.1}/PKG-INFO +27 -11
  6. {structables-0.3.16 → structables-0.4.1}/README.md +25 -9
  7. {structables-0.3.16 → structables-0.4.1}/docker-compose-example-dev.yml +2 -1
  8. {structables-0.3.16 → structables-0.4.1}/docker-compose-example.yml +3 -2
  9. structables-0.4.1/instances.json +54 -0
  10. {structables-0.3.16 → structables-0.4.1}/pyproject.toml +2 -2
  11. structables-0.4.1/renovate.json +6 -0
  12. structables-0.4.1/requirements-dev.txt +6 -0
  13. structables-0.4.1/src/structables/config.py +59 -0
  14. structables-0.4.1/src/structables/main.py +65 -0
  15. {structables-0.3.16 → structables-0.4.1}/src/structables/routes/category.py +21 -0
  16. {structables-0.3.16 → structables-0.4.1}/src/structables/routes/contest.py +39 -4
  17. {structables-0.3.16 → structables-0.4.1}/src/structables/routes/main.py +125 -49
  18. {structables-0.3.16 → structables-0.4.1}/src/structables/routes/member.py +22 -3
  19. structables-0.4.1/src/structables/routes/proxy.py +358 -0
  20. structables-0.4.1/src/structables/static/css/iframe.css +114 -0
  21. structables-0.4.1/src/structables/static/css/style.css +1190 -0
  22. structables-0.4.1/src/structables/static/img/magnifying-glass-solid.svg +2 -0
  23. structables-0.4.1/src/structables/templates/base.html +58 -0
  24. structables-0.4.1/src/structables/templates/category.html +35 -0
  25. structables-0.4.1/src/structables/templates/footer.html +17 -0
  26. structables-0.4.1/src/structables/templates/header.html +49 -0
  27. structables-0.4.1/src/structables/templates/iframe.html +23 -0
  28. structables-0.4.1/src/structables/templates/index.html +37 -0
  29. structables-0.4.1/src/structables/templates/projects.html +49 -0
  30. structables-0.4.1/src/structables/utils/data.py +76 -0
  31. structables-0.4.1/src/structables/utils/helpers.py +551 -0
  32. structables-0.3.16/src/structables/config.py +0 -13
  33. structables-0.3.16/src/structables/main.py +0 -43
  34. structables-0.3.16/src/structables/routes/proxy.py +0 -60
  35. structables-0.3.16/src/structables/static/css/style.css +0 -282
  36. structables-0.3.16/src/structables/static/dist/css/bootstrap.min.css +0 -6
  37. structables-0.3.16/src/structables/static/dist/css/bootstrap.min.css.map +0 -1
  38. structables-0.3.16/src/structables/static/img/magnifying-glass-solid.svg +0 -1
  39. structables-0.3.16/src/structables/templates/base.html +0 -27
  40. structables-0.3.16/src/structables/templates/category.html +0 -68
  41. structables-0.3.16/src/structables/templates/footer.html +0 -21
  42. structables-0.3.16/src/structables/templates/header.html +0 -39
  43. structables-0.3.16/src/structables/templates/iframe.html +0 -12
  44. structables-0.3.16/src/structables/templates/index.html +0 -45
  45. structables-0.3.16/src/structables/templates/projects.html +0 -56
  46. structables-0.3.16/src/structables/utils/data.py +0 -56
  47. structables-0.3.16/src/structables/utils/helpers.py +0 -463
  48. {structables-0.3.16 → structables-0.4.1}/.env.example +0 -0
  49. {structables-0.3.16 → structables-0.4.1}/LICENSE +0 -0
  50. {structables-0.3.16 → structables-0.4.1}/cronjob.txt +0 -0
  51. {structables-0.3.16 → structables-0.4.1}/entrypoint.sh +0 -0
  52. {structables-0.3.16 → structables-0.4.1}/src/structables/__init__.py +0 -0
  53. {structables-0.3.16 → structables-0.4.1}/src/structables/routes/__init__.py +0 -0
  54. {structables-0.3.16 → structables-0.4.1}/src/structables/static/img/logo.png +0 -0
  55. {structables-0.3.16 → structables-0.4.1}/src/structables/static/img/logo_lg.png +0 -0
  56. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/400.html +0 -0
  57. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/404.html +0 -0
  58. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/429.html +0 -0
  59. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/500.html +0 -0
  60. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/archives.html +0 -0
  61. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/article-review.html +0 -0
  62. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/article.html +0 -0
  63. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/collection.html +0 -0
  64. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/contest.html +0 -0
  65. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/contests.html +0 -0
  66. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/member-instructables.html +0 -0
  67. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/member.html +0 -0
  68. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/privacypolicy.html +0 -0
  69. {structables-0.3.16 → structables-0.4.1}/src/structables/templates/sitemap.html +0 -0
  70. {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: node:20-bookworm
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@v5
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:${{ env.GITHUB_REF_NAME }}
30
+ privatecoffee/structables:${{ github.ref_name }}
@@ -10,7 +10,7 @@ jobs:
10
10
  publish:
11
11
  name: Publish to PyPI
12
12
  container:
13
- image: node:20-bookworm
13
+ image: node:22-bookworm
14
14
 
15
15
  steps:
16
16
  - name: Check out code
@@ -6,6 +6,8 @@ privacy.md
6
6
  privacy.txt
7
7
  *.pyc
8
8
  venv/
9
+ /output
10
+ /cache/
9
11
 
10
12
  # Special rules needed for building the Docker image
11
13
  /dist/*
@@ -1,4 +1,4 @@
1
- FROM alpine:3.20
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.16
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: bs4
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
- | URL | Provided by | Country | Comments |
696
- | ----------------------------------------------------------------------- | ----------------------------------------------- | ------- | -------- |
697
- | [structables.private.coffee](https://structables.private.coffee/) | [Private.coffee](https://private.coffee/) | Austria | |
698
- | [structables.bloat.cat](https://structables.bloat.cat/) | [Bloat.cat](https://bloat.cat) | Germany | |
699
- | [structables.darkness.services](https://structables.darkness.services/) | [Darkness.services](https://darkness.services/) | USA | |
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
- | URL | Provided by | Country | Comments |
704
- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------- | -------- |
705
- | [structables.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion](http://structables.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion/) | [Private.coffee](https://private.coffee/) | Austria | |
706
- | [structables.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion](http://structables.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion/) | [Darkness.services](https://darkness.services/) | USA | |
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
- | URL | Provided by | Country | Comments |
17
- | ----------------------------------------------------------------------- | ----------------------------------------------- | ------- | -------- |
18
- | [structables.private.coffee](https://structables.private.coffee/) | [Private.coffee](https://private.coffee/) | Austria | |
19
- | [structables.bloat.cat](https://structables.bloat.cat/) | [Bloat.cat](https://bloat.cat) | Germany | |
20
- | [structables.darkness.services](https://structables.darkness.services/) | [Darkness.services](https://darkness.services/) | USA | |
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
- | URL | Provided by | Country | Comments |
25
- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------- | -------- |
26
- | [structables.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion](http://structables.coffee2m3bjsrrqqycx6ghkxrnejl2q6nl7pjw2j4clchjj6uk5zozad.onion/) | [Private.coffee](https://private.coffee/) | Austria | |
27
- | [structables.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion](http://structables.darknessrdor43qkl2ngwitj72zdavfz2cead4t5ed72bybgauww5lyd.onion/) | [Darkness.services](https://darkness.services/) | USA | |
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
 
@@ -5,12 +5,13 @@ services:
5
5
  build: .
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:
@@ -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.3.16"
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
- "bs4",
22
+ "beautifulsoup4",
23
23
  "markdown2[all]",
24
24
  ]
25
25
 
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended"
5
+ ]
6
+ }
@@ -0,0 +1,6 @@
1
+ ruff
2
+ black
3
+ isort
4
+ mypy
5
+ types-beautifulsoup4
6
+ types-colorama
@@ -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()