Puzzle Massive Overview
Massively Multiplayer Online Jigsaw Puzzles web application.
Currently in active development. A static version has been published at https://puzzle.massive.xyz/ and is playable. Note that the static version is single player and persists puzzle piece positions on the browser side only. Snapshots of puzzle piece positions can be made and downloaded which allows some collaboration between players for a puzzle.
Goals
- Handle thousands of players moving thousands of pieces.
- Local-first; support offline capabilities and let users own their data.
- A fun project to hack on.
- A sustainable and cost-effective project.
- Learn something new and apply it.
Development and Source Code
Puzzle Massive is a hobby project that Jake Hickenlooper is doing in his spare time. This is version 3, which is a rewrite of Version 2. Version 1 and Version 2 of Puzzle Massive were done as open source projects with a AGPLv3 license. These have now been archived and no further development or maintenance will be done on them.
Project source code for version 3 is hosted on sourcehut: puzzle-massive. However, access to the project source code is not publicly available. Humans can ask Jake for a downloadable archive file of the source code. Please include your reason why you'd like to access it. Not all requests will be fulfilled. I may be open to interested individuals that want to collaborate with me on this project.
Links to stay up to date:
- Puzzle Massive News and News RSS Feed
- Mailing Lists
- Issue Tracking for reported bugs.
- Any security issues should be directly reported to jake@massive.xyz
- Active Projects that have been well defined and can be worked on.
Overview
The technology stack is:
- Python for most server side applications.
- Web frameworks LiteStar and Chill.
- Database is SQLite for persistent storage.
- redict for active data.
- Client side makes use of Web Components, htmx, IndexedDB, and lit-html.
- HTML canvas, and SVG for puzzle pieces interaction and rendering; based on the example from piecemaker.
- NGINX as the web proxy server.
- Chillbox for keeping things cool.
Files and Directories
A tree of the files are shown below to help get a sense of the current Puzzle Massive code base. I'm not ready to allow anyone access to this for a number of reasons. I also don't approve of web scrapers that are frequently used to train LLMs without the consent of the authors. For those curious about my choices of software or how I implemented something; please use the below file names as a guide when asking to see something.
Note that files that were generated from the cookiecutters project may be excluded from the list.
119 directories, 643 files
.
├── LICENSE
├── pm-files.txt
├── README.md
├── README.md-1.svg
├── vendor-manifest.toml
│ { Used by vendor-grab tool to download dependencies
├── docs
│ └── development.md
├── piecemaker
│ ⎧ cookiecutters chillbox-site
│ ⎨ Mostly wrapping the piecemaker CLI tool as an API that puzzle-massive can call
│ ⎩ internally.
│ ├── LICENSE
│ ├── local.site.toml
│ ├── Makefile
│ ├── README.md
│ ├── VERSION
│ ├── apiv0
│ │ { cookiecutters python-service
│ │ ├── Containerfile
│ │ ├── LICENSE
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── requirements.txt
│ │ └── src
│ │ └── piecemaker_apiv0
│ │ ├── app.py
│ │ ├── env.py
│ │ ├── __init__.py
│ │ └── sql
│ │ ├── schema.sql
│ │ └── select_schema_version.sql
│ ├── nginx
│ │ ├── Containerfile
│ │ ├── nginx.conf
│ │ ├── piecemaker.ssl_cert.include
│ │ ├── root
│ │ │ ├── error.html
│ │ │ ├── favicon.ico
│ │ │ ├── forbidden.html
│ │ │ ├── humans.txt
│ │ │ ├── maintenance.html
│ │ │ ├── notfound.html
│ │ │ ├── not-supported.html
│ │ │ └── robots.txt
│ │ └── templates
│ │ └── piecemaker.nginx.conf.template
│ └── runner
│ { cookiecutters python-worker
│ ├── Containerfile
│ ├── LICENSE
│ ├── Makefile
│ ├── pyproject.toml
│ ├── README.md
│ ├── requirements.txt
│ └── src
│ └── piecemaker_runner
│ ├── env.py
│ ├── __init__.py
│ ├── main.py
│ ├── script.py
│ └── wrapper.py
├── puzzlefreeze
│ ⎧ cookiecutters chillbox-site
│ ⎨ A static copy of the puzzle-massive site while it is under active development.
│ ⎩ No running services, just NGINX serving static files.
│ ├── LICENSE
│ ├── local.site.toml
│ ├── Makefile
│ ├── pz-view.importmap.json -> ../puzzle-massive/polar-bear/documents/pz-view.importmap.json
│ ├── README.md
│ ├── user-settings-localstorage.js -> ../puzzle-massive/polar-bear/documents/user-settings-localstorage.js
│ ├── VERSION
│ ├── freeze
│ │ { A custom script to build the static site files by using wget.
│ │ ├── Containerfile
│ │ ├── httpd.conf
│ │ ├── Makefile
│ │ ├── README.md
│ │ └── src
│ │ └── page_urls.txt
│ └── nginx
│ ├── Containerfile
│ ├── nginx.conf
│ ├── puzzlefreeze.ssl_cert.include
│ ├── root
│ │ ├── error.html
│ │ ├── favicon.ico
│ │ ├── forbidden.html
│ │ ├── humans.txt
│ │ ├── maintenance.html
│ │ ├── notfound.html
│ │ ├── partial-dingbat.html
│ │ ├── puzzlers.html
│ │ ├── robot-archive.html
│ │ ├── robots.txt
│ │ └── seal.html
│ └── templates
│ └── puzzlefreeze.nginx.conf.template
├── puzzle-massive
│ ⎧ cookiecutters chillbox-site
│ ⎩ The dynamic site.
│ ├── add-crontab-entries.sh
│ ├── Containerfile
│ ├── LICENSE
│ ├── local.site.toml
│ ├── Makefile
│ ├── nginx-log-prepare-up
│ ├── nginx-log-run
│ ├── nginx-run
│ ├── README.md
│ ├── redict-run.template.sh
│ ├── s6-overlay-exp.mk
│ ├── s6-rc-setup-service.sh
│ ├── scrub-solid-color-image.sh
│ ├── starting-data.toml
│ ├── store-packages-notes.md
│ ├── todo.txt
│ ├── VERSION
│ ├── account
│ │ { cookiecutters python-service
│ │ ├── Containerfile
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── requirements.txt
│ │ └── src
│ │ └── puzzle_massive_account
│ │ ├── app.py
│ │ ├── authz.py
│ │ ├── brown_paper_packages_tied_up_with_strings.py
│ │ │ { Some of my favorite things.
│ │ ├── env.py
│ │ ├── errors.py
│ │ ├── __init__.py
│ │ ├── logging.py
│ │ ├── package_jobs.py
│ │ ├── partials.py
│ │ ├── public_claim_codes.py
│ │ ├── puzzle.py
│ │ ├── token_permissions.py
│ │ ├── user.py
│ │ ├── sql
│ │ │ ├── action.py
│ │ │ ├── check.py
│ │ │ ├── __init__.py
│ │ │ ├── insert_brown_paper_packages_tied_up_with_strings_for_owner.sql
│ │ │ ├── insert_brown_paper_packages_tied_up_with_strings_for_user_and_owner.sql
│ │ │ ├── insert_brown_paper_packages_tied_up_with_strings_for_user.sql
│ │ │ ├── insert_brown_paper_packages_tied_up_with_strings.sql
│ │ │ ├── insert_claim_codes.sql
│ │ │ ├── insert_link_claim_codes_tokens.sql
│ │ │ ├── insert_link_user_claim_codes.sql
│ │ │ ├── insert_tokens.sql
│ │ │ ├── insert_tokens_with_owner_and_permission.sql
│ │ │ ├── insert_tokens_with_owner.sql
│ │ │ ├── schema.sql
│ │ │ ├── select_active_claim_codes_for_user.sql
│ │ │ ├── select_all_from_token_types.sql
│ │ │ ├── select_all_user_ids_with_limit.sql
│ │ │ ├── select_brown_paper_packages_tied_up_with_strings.sql
│ │ │ ├── select_claim_code_for_user.sql
│ │ │ ├── select_claim_codes_for_user.sql
│ │ │ ├── select_claim_code.sql
│ │ │ ├── select_count_tokens_for_user_that_are_available_and_not_expired.sql
│ │ │ ├── select_count_tokens_that_are_available_and_not_expired.sql
│ │ │ ├── select_packages_for_user.sql
│ │ │ ├── select_schema_version.sql
│ │ │ ├── select_tokens_active_permissions_for_bearer.sql
│ │ │ ├── select_tokens_active_permissions_for_owner.sql
│ │ │ ├── select_tokens_for_claim_code.sql
│ │ │ ├── select_tokens_that_user_is_bearer_of.sql
│ │ │ ├── select_tokens_with_status_and_type_for_user.sql
│ │ │ ├── select_tokens_with_status_and_type_for_user_that_are_not_claimed.sql
│ │ │ ├── select_tokens_with_status_and_type.sql
│ │ │ ├── select_tokens_with_status_and_type_that_are_not_claimed.sql
│ │ │ ├── select_user_settings_privacy.sql
│ │ │ ├── select_user.sql
│ │ │ ├── update_access_token_set_bearer_to_user.sql
│ │ │ ├── update_brown_paper_package_redeemed.sql
│ │ │ ├── update_brown_paper_packages_status.sql
│ │ │ ├── update_brown_paper_packages_status_when_redeemable_limit.sql
│ │ │ ├── update_claim_codes_status.sql
│ │ │ ├── update_guest_tokens_set_permission_and_bearer.sql
│ │ │ ├── update_invite_token_set_bearer_to_user.sql
│ │ │ ├── update_tokens_from_claim_code_set_bearer_to_user.sql
│ │ │ ├── update_user_guest_token_count_and_interval.sql
│ │ │ ├── upsert_package_jobs.sql
│ │ │ ├── upsert_token_permissions.sql
│ │ │ ├── upsert_user_id.sql
│ │ │ ├── upsert_user_settings_privacy.sql
│ │ │ └── upsert_users.sql
│ │ └── templates
│ │ ├── 403-error.html.jinja2
│ │ ├── app-dependency-error.html.jinja2
│ │ ├── base.html.jinja2
│ │ ├── brown-paper-packages-tied-up-with-strings-claim.html
│ │ ├── brown-paper-packages-tied-up-with-strings-landing.html
│ │ ├── brown-paper-packages-tied-up-with-strings-listing.html.jinja2
│ │ ├── brown-paper-packages-tied-up-with-strings-package-contents-partial.html.jinja2
│ │ ├── public-claim-code.html.jinja2
│ │ ├── public-claim-code-post-message.html.jinja2
│ │ ├── settings-privacy.html.jinja2
│ │ ├── user-claim-code-error.html.jinja2
│ │ ├── user-claim-code.html.jinja2
│ │ ├── user-claim-codes.html.jinja2
│ │ ├── user-header-partial.html.jinja2
│ │ ├── user-invite-package-response.html.jinja2
│ │ ├── user-invites.html.jinja2
│ │ ├── user-landing.html.jinja2
│ │ └── user-settings-localstorage.js
│ ├── cedar-block
│ │ { cookiecutters python-service
│ │ ├── Containerfile
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── requirements.txt
│ │ └── src
│ │ └── puzzle_massive_cedar_block
│ │ ├── app.py
│ │ ├── cedar_policy.py
│ │ ├── env.py
│ │ ├── __init__.py
│ │ └── logging.py
│ ├── cedar-tree
│ │ { cookiecutters immutable-make
│ │ ├── _01-process-src-files.mk
│ │ ├── Containerfile
│ │ ├── Makefile
│ │ ├── README.md
│ │ └── src
│ │ ├── account.entities.cedar.toml
│ │ ├── account.policies.cedar
│ │ ├── account_test.py
│ │ ├── admin.policies.cedar
│ │ ├── admin_test.py
│ │ ├── convert-cedar-toml-to-json.py
│ │ ├── entities_and_policies_test.py
│ │ ├── format-cedar-policy.py
│ │ ├── __init__.py
│ │ ├── puzzle.entities.cedar.toml
│ │ ├── puzzle-porte.policies.cedar
│ │ ├── puzzles.entities.cedar.toml
│ │ ├── puzzles.policies.cedar
│ │ ├── puzzles_test.py
│ │ ├── puzzle_test.py
│ │ ├── studio.entities.cedar.toml
│ │ ├── studio.policies.cedar
│ │ ├── studio_test.py
│ │ └── utils.py
│ ├── doer
│ │ { cookiecutters python-worker
│ │ ├── Containerfile
│ │ ├── LICENSE
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── requirements.txt
│ │ └── src
│ │ └── puzzle_massive_doer
│ │ ├── env.py
│ │ ├── __init__.py
│ │ ├── logging.py
│ │ ├── main.py
│ │ ├── script.py
│ │ └── jobs
│ │ ├── http_request.py
│ │ └── __init__.py
│ ├── media
│ │ ⎧ cookiecutters immutable-make
│ │ ⎨ The graphics are not included in the git history and so they also don't show
│ │ ⎪ in the tree output here. All image files are included by running the
│ │ ⎩ vendor-grab command.
│ │ ├── _01-process-src-files.mk
│ │ ├── _02-convert-scanned-typed-doc.mk
│ │ ├── _03-snowstorm-media.mk
│ │ ├── _04-handwritten-media.mk
│ │ ├── Containerfile
│ │ ├── Makefile
│ │ ├── README.md
│ │ └── src
│ │ ├── file1.txt
│ │ └── file2.txt
│ ├── mini-map
│ │ { cookiecutters client-side-public
│ │ ├── Containerfile
│ │ ├── LICENSE
│ │ ├── Makefile
│ │ ├── README.md
│ │ ├── vendor-manifest.toml
│ │ │ { Used by vendor-grab tool to download dependencies
│ │ ├── src
│ │ │ ├── index.html
│ │ │ ├── index.js
│ │ │ ├── mini-map.css
│ │ │ └── mini-map.js
│ │ └── vendor
│ │ └── normalize.css
│ │ └── LICENSE.md
│ ├── nginx
│ │ ├── Containerfile
│ │ ├── nginx.conf
│ │ ├── puzzle-massive.ssl_cert.include
│ │ ├── root
│ │ │ ├── error.html
│ │ │ ├── favicon.ico
│ │ │ ├── forbidden.html
│ │ │ ├── humans.txt
│ │ │ ├── maintenance.html
│ │ │ ├── notfound.html
│ │ │ ├── puzzlers.html
│ │ │ ├── robot-archive.html
│ │ │ ├── robots.txt
│ │ │ └── legal
│ │ │ ├── privacy-policy.html
│ │ │ └── terms-of-service.html
│ │ └── templates
│ │ └── puzzle-massive.nginx.conf.template
│ ├── polar-bear
│ │ { cookiecutters chill-service
│ │ ├── chill-data-puzzle-authors.yaml
│ │ ├── chill-data-weekly.yaml
│ │ ├── chill-data.yaml
│ │ ├── chill-dump.sql
│ │ ├── Containerfile
│ │ ├── LICENSE
│ │ ├── Makefile
│ │ ├── README.md
│ │ ├── requirements.in
│ │ ├── requirements.txt
│ │ ├── site.cfg
│ │ ├── documents
│ │ │ ├── archive.md
│ │ │ ├── cookie-consent.md
│ │ │ ├── cookie-customize.md
│ │ │ ├── pz-view.importmap.json
│ │ │ └── user-settings-localstorage.js
│ │ ├── queries
│ │ │ ├── select-article.sql
│ │ │ ├── select-datetime-now-for-rss-date.sql
│ │ │ ├── select-error.sql
│ │ │ ├── select-images-for-moderation.sql
│ │ │ ├── select-latest-weekly-post-pub-date.sql
│ │ │ ├── select-latest-weekly-post.sql
│ │ │ ├── select-moderation-image-detail.sql
│ │ │ ├── select-puzzles-for-player.sql
│ │ │ ├── select-puzzle_share_maneuvers_add_players_as_guests_response.sql
│ │ │ ├── select-pz_info.sql
│ │ │ ├── select-pz_name.sql
│ │ │ ├── select-recent-weekly-posts.sql
│ │ │ ├── select-seal.sql
│ │ │ ├── select-studio-puzzle-admin-landing.sql
│ │ │ ├── select-studio-puzzle-landing.sql
│ │ │ ├── select-template-partial-name.sql
│ │ │ ├── select-template-snip-name.sql
│ │ │ ├── select-weekly-post-by-date.sql
│ │ │ ├── select-weekly-post-by-latest.sql
│ │ │ └── weekly.sql
│ │ └── templates
│ │ ├── aboutpage.html.jinja2
│ │ ├── archive.html.jinja2
│ │ ├── color-scheme-settings.html.jinja2
│ │ ├── cookie-consent-dialog.html.jinja2
│ │ ├── copyright.html.jinja2
│ │ ├── head_style.html.jinja2
│ │ ├── homepage.html
│ │ ├── htmx-head-script.html.jinja2
│ │ ├── importmap.html.jinja2
│ │ ├── intro-dialog.html.jinja2
│ │ ├── menu.html.jinja2
│ │ ├── moderation.html.jinja2
│ │ ├── moderation-images-detail.html.jinja2
│ │ ├── moderation-images-landing.html.jinja2
│ │ ├── page-footer.html.jinja2
│ │ ├── page-header.html.jinja2
│ │ ├── page.html.jinja2
│ │ ├── page-links.html.jinja2
│ │ ├── partial-dingbat.html.jinja2
│ │ ├── partial.html.jinja2
│ │ ├── piecemovementspage.html.jinja2
│ │ ├── puzzle-author.html.jinja2
│ │ ├── puzzle-info.html.jinja2
│ │ ├── puzzle-share.html.jinja2
│ │ ├── puzzle-share-maneuvers-add-players-as-guests-response.html.jinja2
│ │ ├── puzzles.html.jinja2
│ │ ├── puzzles-landing.html.jinja2
│ │ ├── puzzle-snapped.html.jinja2
│ │ ├── pz-error.html.jinja2
│ │ ├── pz-status.html.jinja2
│ │ ├── pz-view.html.jinja2
│ │ ├── rss.jinja2
│ │ ├── seal.html.jinja2
│ │ ├── site.html.jinja2
│ │ ├── snippet-content-space-close.html.jinja2
│ │ ├── sniptext-about.html.jinja2
│ │ ├── sniptext-loading.html.jinja2
│ │ ├── sniptext-login.html.jinja2
│ │ ├── sniptext-privacy-policy.html.jinja2
│ │ ├── sniptext-puzzle-massive.html.jinja2
│ │ ├── sniptext-terms-of-service.html.jinja2
│ │ ├── sniptext-weekly.html.jinja2
│ │ ├── snowstorm_js_script.html.jinja2
│ │ ├── studio-puzzle-admin-landing-view.html.jinja2
│ │ ├── studio-puzzle-landing-view.html.jinja2
│ │ ├── template-snip.html.jinja2
│ │ ├── typedpage.html.jinja2
│ │ ├── user_data_settings.html.jinja2
│ │ ├── user_settings_localstorage_js_script.html.jinja2
│ │ ├── web-component-puzzle-snapped-message-template.html.jinja2
│ │ ├── web-component-snap-template.html.jinja2
│ │ ├── weekly.html.jinja2
│ │ └── weekly-latest.html.jinja2
│ ├── porte
│ │ { cookiecutters python-service
│ │ ├── Containerfile
│ │ ├── LICENSE
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── requirements.txt
│ │ └── src
│ │ └── puzzle_massive_porte
│ │ ├── account.py
│ │ ├── app_logging.py
│ │ ├── app.py
│ │ ├── archive.py
│ │ ├── authz.py
│ │ ├── env.py
│ │ ├── feed.py
│ │ ├── __init__.py
│ │ ├── moderation.py
│ │ ├── oauth2.py
│ │ ├── page.py
│ │ ├── partial.py
│ │ ├── puzzles.py
│ │ ├── seal.py
│ │ ├── snap.py
│ │ ├── snapshots.py
│ │ ├── studio.py
│ │ ├── tools.py
│ │ ├── users.py
│ │ ├── sql
│ │ │ ├── action.py
│ │ │ ├── check.py
│ │ │ ├── count_total_pz_names.sql
│ │ │ ├── __init__.py
│ │ │ ├── insert_name_to_pz_names.sql
│ │ │ ├── insert_name_to_pz_names_with_owner.sql
│ │ │ ├── insert_puzzle_snapshot.sql
│ │ │ ├── insert_pznames_puzzletypes_link.sql
│ │ │ ├── insert_snap_offset.sql
│ │ │ ├── schema.sql
│ │ │ ├── select_all_puzzle_snapshots_for_owner.sql
│ │ │ ├── select_puzzle_by_pzname.sql
│ │ │ ├── select_puzzle_offset_by_snap.sql
│ │ │ ├── select_puzzle_offset_list_by_pzname.sql
│ │ │ ├── select_puzzles_for_anyone.sql
│ │ │ ├── select_puzzles_for_guest.sql
│ │ │ ├── select_puzzles_for_members.sql
│ │ │ ├── select_puzzles_for_owner.sql
│ │ │ ├── select_puzzle_snap_by_pzname.sql
│ │ │ ├── select_puzzle_snap_key_by_pzname.sql
│ │ │ ├── select_puzzle_snapshots_for_name.sql
│ │ │ ├── select_puzzletypes_with_name.sql
│ │ │ ├── select_puzzle_with_puzzle_type_for_anyone.sql
│ │ │ ├── select_puzzle_with_pzname.sql
│ │ │ ├── select_schema_version.sql
│ │ │ ├── select_user_by_emailhash.sql
│ │ │ ├── select_user_by_name.sql
│ │ │ ├── select_user_by_user.sql
│ │ │ ├── upsert_puzzletypes_name.sql
│ │ │ ├── upsert_users.sql
│ │ │ └── upsert_users_with_name_and_email_hash.sql
│ │ └── templates
│ │ └── puzzle-not-ripe-yet.html.jinja2
│ │ ⎧ It's not ripe yet... This template partial is shown when a puzzle is still
│ │ ⎨ being created. HTMX is used to do some simple polling that updates the
│ │ ⎩ content with the latest status.
│ ├── puzzle-canvas
│ │ { cookiecutters client-side-public
│ │ ├── Containerfile
│ │ ├── LICENSE
│ │ ├── Makefile
│ │ ├── README.md
│ │ ├── serve.py
│ │ ├── vendor-manifest.toml
│ │ │ { Used by vendor-grab tool to download dependencies
│ │ ├── src
│ │ │ ├── deno.json
│ │ │ ├── flat-view.js
│ │ │ ├── index.html
│ │ │ ├── index.js
│ │ │ ├── puzzle-canvas.css
│ │ │ ├── puzzle-canvas.js
│ │ │ ├── test.html
│ │ │ ├── TinyGesture.js
│ │ │ └── utils.js
│ │ └── vendor
│ │ ├── msgpack
│ │ │ ├── CachedKeyDecoder.mjs
│ │ │ ├── context.mjs
│ │ │ ├── decodeAsync.mjs
│ │ │ ├── DecodeError.mjs
│ │ │ ├── decode.mjs
│ │ │ ├── Decoder.mjs
│ │ │ ├── encode.mjs
│ │ │ ├── Encoder.mjs
│ │ │ ├── ExtData.mjs
│ │ │ ├── ExtensionCodec.mjs
│ │ │ ├── index.mjs
│ │ │ ├── LICENSE
│ │ │ ├── msgpack.js
│ │ │ ├── timestamp.mjs
│ │ │ └── utils
│ │ │ ├── int.mjs
│ │ │ ├── prettyByte.mjs
│ │ │ ├── stream.mjs
│ │ │ ├── typedArrays.mjs
│ │ │ └── utf8.mjs
│ │ └── tinygesture
│ │ ├── LICENSE
│ │ └── TinyGesture.js
│ ├── ressources-de-puzzle
│ │ { cookiecutters python-service
│ │ ├── Containerfile
│ │ ├── LICENSE
│ │ ├── Makefile
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ ├── requirements.txt
│ │ └── src
│ │ └── puzzle_massive_ressources_de_puzzle
│ │ ├── app.py
│ │ ├── env.py
│ │ ├── imageanalysis.py
│ │ ├── image.py
│ │ ├── __init__.py
│ │ ├── loader.py
│ │ ├── logging.py
│ │ ├── pz_name.py
│ │ ├── sql
│ │ │ ├── check.py
│ │ │ ├── delete_puzzle_job_where_expired.sql
│ │ │ ├── __init__.py
│ │ │ ├── insert_attribution.sql
│ │ │ ├── insert_attribution_without_license.sql
│ │ │ ├── insert_image_analysis.sql
│ │ │ ├── insert_image.sql
│ │ │ ├── insert_license.sql
│ │ │ ├── insert_link_piecemaker_artifact_image.sql
│ │ │ ├── insert_piecemaker_artifact.sql
│ │ │ ├── insert_puzzle_job.sql
│ │ │ ├── insert_puzzle.sql
│ │ │ ├── schema.sql
│ │ │ ├── select_adjacent_by_pz_name.sql
│ │ │ ├── select_cache_key_by_name.sql
│ │ │ ├── select_image_by_id.sql
│ │ │ ├── select_image_cache_id_by_id.sql
│ │ │ ├── select_image_detail_by_id.sql
│ │ │ ├── select_image_ids_for_moderation.sql
│ │ │ ├── select_image_moderation_by_id.sql
│ │ │ ├── select_license_by_name.sql
│ │ │ ├── select_phash_for_all_image.sql
│ │ │ ├── select_public_puzzle_piece_props_by_uuid.sql
│ │ │ ├── select_puzzle_by_name.sql
│ │ │ ├── select_puzzle_data_by_name.sql
│ │ │ ├── select_puzzle_job_by_name.sql
│ │ │ ├── select_puzzle_job_where_expired.sql
│ │ │ ├── select_puzzle_piece_origins_by_pz_name.sql
│ │ │ ├── select_schema_version.sql
│ │ │ ├── select_submitters.sql
│ │ │ ├── update_puzzle_job_by_name.sql
│ │ │ ├── update_puzzle_job_status_by_name.sql
│ │ │ └── upsert_submitters.sql
│ │ └── templates
│ │ ├── mini-map.html.jinja2
│ │ ├── puzzle-canvas.html.jinja2
│ │ ├── puzzle-image-detail.html.jinja2
│ │ ├── puzzle-image-moderation.html.jinja2
│ │ ├── puzzle-preload.html.jinja2
│ │ ├── puzzle-variants.html.jinja2
│ │ └── snapshots.html.jinja2
│ ├── snap
│ │ { cookiecutters client-side-public
│ │ ├── Containerfile
│ │ ├── LICENSE
│ │ ├── Makefile
│ │ ├── README.md
│ │ ├── vendor-manifest.toml
│ │ │ { Used by vendor-grab tool to download dependencies
│ │ ├── src
│ │ │ ├── database.js
│ │ │ ├── deno.json
│ │ │ ├── flatbush.js
│ │ │ ├── immer.js
│ │ │ ├── index.html
│ │ │ ├── index.js
│ │ │ ├── joined-piece-groups.js
│ │ │ ├── joined-piece-groups.test.js
│ │ │ ├── piece-proximity.js
│ │ │ ├── pieces.js
│ │ │ ├── service.js
│ │ │ ├── shared-utils.js
│ │ │ ├── snap.component.js
│ │ │ ├── snap.css
│ │ │ ├── snap.js
│ │ │ ├── snapshots.component.js
│ │ │ ├── snapshots.css
│ │ │ ├── utils.js
│ │ │ └── service-workers
│ │ │ ├── build-snap.js
│ │ │ ├── example-puzzle.js
│ │ │ ├── index.sw.js
│ │ │ ├── publish.intercept.js
│ │ │ ├── snapshots.intercept.js
│ │ │ └── snap.sw.js
│ │ └── vendor
│ │ ├── flatbush
│ │ │ ├── flatbush.js
│ │ │ └── LICENSE
│ │ ├── idb
│ │ │ ├── idb.js
│ │ │ └── LICENSE
│ │ ├── immer
│ │ │ ├── immer.js
│ │ │ └── LICENSE
│ │ ├── lit-html
│ │ │ ├── LICENSE
│ │ │ └── lit-html.min.js
│ │ └── msgpack
│ │ ├── CachedKeyDecoder.mjs
│ │ ├── context.mjs
│ │ ├── decodeAsync.mjs
│ │ ├── DecodeError.mjs
│ │ ├── decode.mjs
│ │ ├── Decoder.mjs
│ │ ├── encode.mjs
│ │ ├── Encoder.mjs
│ │ ├── ExtData.mjs
│ │ ├── ExtensionCodec.mjs
│ │ ├── index.mjs
│ │ ├── LICENSE
│ │ ├── msgpack.js
│ │ ├── timestamp.mjs
│ │ └── utils
│ │ ├── int.mjs
│ │ ├── prettyByte.mjs
│ │ ├── stream.mjs
│ │ ├── typedArrays.mjs
│ │ └── utf8.mjs
│ └── snowstorm
│ { cookiecutters client-side-public
│ ├── Containerfile
│ ├── LICENSE
│ ├── Makefile
│ ├── README.md
│ ├── vendor-manifest.toml
│ │ { Used by vendor-grab tool to download dependencies
│ ├── src
│ │ ├── 0-generic-reset.css
│ │ ├── 2-media.css
│ │ ├── 2-plain.css
│ │ ├── 2-pure.css
│ │ ├── 4-arctic.css
│ │ ├── 5-utils.css
│ │ ├── 6-settings.css
│ │ ├── account.html
│ │ ├── deno.json
│ │ ├── details-open.js
│ │ ├── draggable.js
│ │ ├── index.html
│ │ ├── index.js
│ │ ├── intro-dialog.html
│ │ ├── page.html
│ │ ├── page-legal.html
│ │ ├── polarcat.js
│ │ ├── puzzle.html
│ │ ├── puzzle-info-dialog.html
│ │ ├── puzzle-share-dialog.html
│ │ ├── qr-code.js
│ │ ├── README.md
│ │ ├── seal.html
│ │ ├── snowstorm.css
│ │ ├── snowstorm.js
│ │ ├── 1-elements
│ │ │ ├── elements.css
│ │ │ ├── pm-mini-map.css
│ │ │ └── qr-code.css
│ │ ├── 2-check-mark
│ │ │ └── check-mark.css
│ │ ├── 2-tabs
│ │ │ ├── tabs.css
│ │ │ └── tabs.js
│ │ ├── 3-account
│ │ │ ├── account.css
│ │ │ └── account.js
│ │ ├── 3-background
│ │ │ └── background.css
│ │ ├── 3-base
│ │ │ └── base.css
│ │ ├── 3-color-scheme-control
│ │ │ └── color-scheme-control.css
│ │ ├── 3-content-space
│ │ │ └── content-space.css
│ │ ├── 3-cookie-consent-customize-dialog
│ │ │ └── cookie-consent-customize-dialog.js
│ │ ├── 3-cookie-consent-dialog
│ │ │ ├── cookie-consent-dialog.css
│ │ │ └── cookie-consent-dialog.js
│ │ ├── 3-dialog
│ │ │ └── dialog.js
│ │ ├── 3-htmx
│ │ │ ├── htmx.js
│ │ │ └── response-handling.js
│ │ ├── 3-icon
│ │ │ └── icon.css
│ │ ├── 3-mark
│ │ │ └── mark.css
│ │ ├── 3-menu
│ │ │ ├── menu.css
│ │ │ └── menu.js
│ │ ├── 3-package-listing
│ │ │ └── package-listing.css
│ │ ├── 3-page
│ │ │ └── page.css
│ │ ├── 3-puzzle
│ │ │ ├── puzzle.css
│ │ │ └── puzzle.js
│ │ ├── 3-puzzle-image-detail
│ │ │ └── puzzle-image-detail.css
│ │ ├── 3-puzzle-info
│ │ │ └── puzzle-info.css
│ │ ├── 3-puzzle-massive-intro
│ │ │ ├── puzzle-massive-intro.css
│ │ │ └── puzzle-massive-intro.js
│ │ ├── 3-puzzles
│ │ │ └── puzzles.css
│ │ ├── 3-puzzle-snapped-message
│ │ │ ├── puzzle-snapped-message.component.js
│ │ │ ├── puzzle-snapped-message.css
│ │ │ └── puzzle-snapped-message.js
│ │ ├── 3-seal
│ │ │ └── seal.css
│ │ ├── 3-share
│ │ │ └── share.css
│ │ ├── 3-snapshots
│ │ │ ├── snapshots.css
│ │ │ └── snapshots.js
│ │ ├── 3-squid-beach
│ │ │ └── squid-beach.css
│ │ ├── 3-studio
│ │ │ └── studio.css
│ │ ├── 3-user-invites
│ │ │ └── user-invites.css
│ │ └── 3-user-landing
│ │ └── user-landing.css
│ └── vendor
│ ├── htmx
│ │ ├── htmx.min.js
│ │ └── LICENSE
│ ├── idiomorph
│ │ ├── idiomorph-ext.min.js
│ │ └── LICENSE
│ ├── lit-html
│ │ └── lit-html.min.js -> ../../../snap/vendor/lit-html/lit-html.min.js
│ ├── main.css
│ │ ├── _helpers.css
│ │ └── LICENSE.txt
│ ├── normalize.css
│ │ ├── LICENSE.md
│ │ └── normalize.css
│ ├── noto-emoji
│ │ ├── LICENSE
│ │ └── noto-emoji-emoji-700-normal.woff2
│ ├── old-timey-mono-font
│ │ ├── LICENSE.md
│ │ └── OldTimeyMono.ttf
│ ├── purecss
│ │ ├── LICENSE
│ │ └── tables.css
│ ├── qr-code
│ │ ├── LICENSE
│ │ └── qr-code.js
│ ├── qrjs
│ │ └── qr.js
│ └── suitcss-utils-text
│ ├── LICENSE.md
│ └── text.css
└── scripts
├── doit.sh
├── shareable-archive.sh
├── start.sh
├── stop.sh
└── weekly.sh
119 directories, 643 files
Software and Services
Listing of various software and services that Puzzle Massive is currently using. This is to allow a glimpse of the internals of a web application like this and crediting other projects.
Jigsaw Puzzle Related
- BeautifulSoup An XML parser
- CustomShapeJigsawJs A custom border jigsaw puzzle generator for laser cutting based on Voronoi tesselation
- EXIF.py Easy to use Python module to extract Exif metadata from digital image files.
- ImageHash A Python Perceptual Image Hashing Module
- ImageMagick is a free, open-source software suite, used for editing and manipulating digital images.
- piecemaker Create jigsaw puzzle pieces
- svgwrite Python Package to write SVG files
Backend (Server Side)
- Chill a database driven web application framework in Flask and it can be used to create static or dynamic web sites.
- Litestar is a powerful, flexible, highly performant, and opinionated ASGI framework.
- PyJWT JSON Web Token implementation in Python
- Python QR Code image generator
- Python is a programming language that lets you work quickly and integrate systems more effectively.
- SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine.
- arq Fast job queuing and RPC in python with asyncio and redis.
- boto3 AWS SDK for Python
- cedar is a language for defining permissions as policies, which describe who should have access to what.
- httpx A next-generation HTTP client for Python.
- msgspec is a fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML.
- redict is a distributed key/value store (a remote dictionary, if you will).
Frontend (Browser Side)
- @msgpack/msgpack implementation of MessagePack for TypeScript and JavaScript, providing a compact and efficient binary serialization format.
- Old Timey Mono A clean monospace typeface based on Reproducing Typewriter which was available as early as 1906.
- Phosphor is a flexible icon family for interfaces, diagrams, presentations — whatever, really.
- Plain Javascript Vanilla JavaScript for building powerful web applications
- TinyGesture.js Very small gesture recognizer for JavaScript. Swipe, pan, tap, doubletap, longpress, pinch, and rotate.
- flatbush A really fast static spatial index for 2D points and rectangles in JavaScript.
- htmx high power tools for HTML
- idb IndexedDB with usability.
- idiomorph is a DOM morphing algorithm created by the htmx creator.
- lit-html The rendering library used by LitElement.
- main.css A repository for the development of the HTML5 Boilerplate CSS file, main.css
- normalize.css A cross-browser CSS foundation
- pure.css A set of small, responsive CSS modules that you can use in every web project.
- qr.js QR code generator in pure Javascript (2011)
- utils-text SUIT CSS text utilities.
Infrastructure
- Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.
- Chillbox Deployment scripts for websites running on Alpine Linux or Debian servers.
- Debian the universal operating system
- Litestream Fully-replicated database with no pain and little cost.
- NGINX is an HTTP web server, reverse proxy, content cache, load balancer, TCP/UDP proxy server, and mail proxy server.
- OpenTofu is a fork of Terraform that is open-source, community-driven, and managed by the Linux Foundation.
- rauthy Rauthy is an OpenID Connect (OIDC) Provider and Single Sign-On solution written in Rust.
- thumbor open-source smart on-demand image cropping, resizing and filters
Development
- GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files.
- podman Containers
- esbuild An extremely fast bundler for the web
- ruff An extremely fast Python linter and code formatter, written in Rust.
- sqlfluff The SQL Linter for Humans
- vendor-grab Download gzipped tar files from URLs and extract only selected files to destination paths in current working directory.
Services
- DigitalOcean Hosting
- Gravatar Your Free Avatar, Profile, and Link In Bio.
- Let's Encrypt A nonprofit Certificate Authority providing TLS certificates to 500 million websites.
- Postmark The email delivery service that people actually like
- Sightengine Powerful APIs to assess, filter and moderate photos, videos and texts.
- TinEye Reverse Image Search
- Unsplash Over 6 million free high-resolution photos and illustrations brought to you by the world’s most generous community of contributors.
- Wordpress Photo Directory Choose from a growing collection of free, CC0-licensed photos to customize and enhance your WordPress website.