{"id":18362494,"url":"https://github.com/crrobinson14/sits","last_synced_at":"2025-06-22T10:37:09.874Z","repository":{"id":146152192,"uuid":"83583798","full_name":"crrobinson14/sits","owner":"crrobinson14","description":"Simple Image Thumbnail Service","archived":false,"fork":false,"pushed_at":"2017-03-13T05:40:57.000Z","size":358,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-05-08T00:35:35.493Z","etag":null,"topics":["actionhero","image-transformations","microservice","thumbnail"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/crrobinson14.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-01T17:50:01.000Z","updated_at":"2017-03-07T18:20:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"2c76ae25-2fdf-4d10-80dd-2111b75f01b4","html_url":"https://github.com/crrobinson14/sits","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/crrobinson14/sits","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crrobinson14%2Fsits","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crrobinson14%2Fsits/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crrobinson14%2Fsits/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crrobinson14%2Fsits/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/crrobinson14","download_url":"https://codeload.github.com/crrobinson14/sits/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/crrobinson14%2Fsits/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261280139,"owners_count":23134904,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actionhero","image-transformations","microservice","thumbnail"],"created_at":"2024-11-05T22:41:00.019Z","updated_at":"2025-06-22T10:37:04.862Z","avatar_url":"https://github.com/crrobinson14.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Simple Image Thumbnail Service (SITS)\n\nSITS is an example of using the ActionHero framework to build a thumbnail\ngeneration service. However, it is not a sample project - it is a fully\nfunctional application that addresses the unique needs of one of this\nauthor's projects. In particular, the requirements were for a micro-\nservice that was:\n\n* Variant-based,\n* Self-hosted, and\n* Easily \"primed\" to avoid slow accesses for the first users retrieving\na new asset.\n\nPrior to developing SITS, this author was using Thumbor, and that\nproject remains very useful today. However, it depends on the PIL, which\nhas issues with some low-quality sources, particularly those with dodgy\nSSL certificates and/or out-of-spec images. One might argue that those\nassets should not be handled in the first place... but users only care\nthat the images work and do not look broken.\n\n\u003e Want to see how this project was built? Watch \"Code With Me: Image\nThumbnail Service in NodeJS and ActionHero\":\n\n![Pirates!](https://img.youtube.com/vi/a5V5C8mEVzY/0.jpg)(https://www.youtube.com/watch?v=a5V5C8mEVzY \"Pirates!\")\n\n## Overview and Requirements\n\nAssume an original image, available via an HTTP URL:\n\n![Original Image](https://raw.githubusercontent.com/crrobinson14/sits/master/docs/original.png)\n\nThe classic thumbnailing approach uses a formatted URL to access a\nthumbnailing service and generate a variant of the original image. For\nexample, we might ask our thumbnail service to create a 90x60\nscale-and-crop version of the original 100x100 source file:\n\n![Classic Approach](https://raw.githubusercontent.com/crrobinson14/sits/master/docs/classic.png)\n\nHowever, this naive approach has some problems. The most important is that\nit is easy to DDoS such a service - an attacker can simply ask for every\npossible size from 1..Infinity to overload the server. Thumbnailing is\n\"expensive\" in terms of CPU resources.\n\nBut even if we solve the DDoS issue (which we will shortly), there are\nother issues as well. If the Design team asks us to bump up the quality\nof JPEG assets in our app, we need to add this parameter to every client\nhitting the server (or hope for a messy, hard-to-maintain rewrite rule).\nAnd because thumbs are generated only upon request, the first users\nhitting our servers will have slower experiences, as they are the ones\n\"paying the bill\" so to speak to get the images themselves. We could\ntry to script this, but then we have to know every possible size ahead\nof time. What if we miss one?\n\n![Signed URLs](https://raw.githubusercontent.com/crrobinson14/sits/master/docs/signed.png)\n\nThe typical \"next step,\" supported in Thumbor and most other options, is\nto \"sign\" our URLs. This addresses the DDoS risk by only allowing pre-\napproved operations... but falls short of addressing the entire issue.\nIt's also clumsy to implement because we need code changes in both our\nservers and clients. What to do?\n\n![Variant Approach](https://raw.githubusercontent.com/crrobinson14/sits/master/docs/variant.png)\n\nFinally, we arrive at the variant-based approach, which is also used in\nDrupal's \"Image Styles\" module but we're delivering here as a packaged\nmicro-service. \"Variants\" is a five-dollar word for a five-cent concept:\nit just means we will pre-defined our transforms, and access them by\nID instead of supplying all of the parameters in every request.\nThis is not a panacaea: we must know about and pre-define those variants\nin the first place! But once we accept that burden, this option does fix\nthe other issues, and this is the reason this author chose this approach\nfor a recent project.\n\nOne word of caution about DDoS: if you're paying attention, you should\nhave noticed there is still one user-supplied parameter that is hard to\nvalidate, and thus becomes an easy source of workload-injection: the\nsource URI of the image. This author was able to add an application-\nspecific database check to address that risk. You could also strip query\nstrings if your application doesn't need them, and/or add a domain\nwhitelist if you wanted to address this.\n\n## Installation and Usage\n\nThis author chose NodeJS + ActionHero as the primary tech stack for the\nreasons outlined here:\n\n[How to Choose a NodeJS\nFramework](https://medium.com/@CodeAndBiscuits/how-to-choose-a-nodejs-framework-a8a44bf73ad4#.i9ooww31u)\n\nInstalling the service is easy. SITS uses GraphicsMagick for image\ntransformation, so start by installing the\n[prerequisites](https://github.com/aheckmann/gm#getting-started).\n\nAfter that a simple `npm install` followed by an `npm start` is enough\nto start the basic server. By default, the server will run on port 8080,\nwhich you can change either by editing config/servers/web.js or setting\nPORT when starting the server:\n\n    PORT=3000 npm start\n\nYou could then make an API call to create a simple scale-and-crop\nvariant with a JPEG export:\n\n    curl -H \"Content-Type: application/json\" \\\n         -X POST http://localhost:8080/api/variants \\\n         -d '{\"apiKey\":\"CHANGEME\", \"id\":\"mediumthumb\", \"transforms\":\"-geometry 120x70^ -gravity center -extent 120x70\"}'\n\nTo keep things simple for now, SITS assumes variant CRUD operations are\nTRUSTED. This means all requests must include a secret API key as set in\n`config/api.js`. Because this is ActionHero, an administrator can easily\nchange this secret key for different environments, overriding the developer's\ndefaults for QA, Production, etc. See [ActionHero\nConfig](https://www.actionherojs.com/docs/core/#config)\nfor an in-depth guide to configuring ActionHero-based projects.\n\n\u003e *IMPORTANT NOTE:* This version of SITS uses a simple SQLite local\ndatabase file and the FakeRedis module for demonstration purposes.\nThese settings are also easily changed via config parameters... but\nuntil you do, data may be lost between test/run passes, and only a single\nnode should be run at a time!\n\nAs shown in the example above, image transformations are just a list of\nGraphicsMagick options. All operations are technically available here\nbut only a subset actually make sense. Please refer to the [GraphicsMagick\nDocumentation](http://www.graphicsmagick.org/GraphicsMagick.html) for\nmore information on the available options. This example would change\nthe above operation to a top-center crop (ideal for head shots), and a\n16:9 final output:\n\n    curl -H \"Content-Type: application/json\" \\\n         -X POST http://localhost:8080/api/variants \\\n         -d '{\"apiKey\":\"CHANGEME\", \"id\":\"widethumb\", \"transforms\":\"-geometry 120x67^ -gravity north -extent 120x67\"}'\n\nVariants may be listed with a GET request:\n\n    curl http://localhost:8080/api/variants?apiKey=CHANGEME\n\nwould now output:\n\n    {\n      \"variants\": [\n        {\n          \"id\": \"mediumthumb\",\n          \"transforms\": \"-geometry 120x70^ -gravity center -extent 120x70\",\n          \"createdAt\": \"2017-03-07T05:47:25.395Z\",\n          \"updatedAt\": \"2017-03-07T05:47:25.395Z\"\n        },\n        {\n          \"id\": \"widethumb\",\n          \"transforms\": \"-geometry 120x67^ -gravity north -extent 120x67\",\n          \"createdAt\": \"2017-03-07T05:49:32.691Z\",\n          \"updatedAt\": \"2017-03-07T05:49:32.691Z\"\n        }\n      ]\n    }\n\nWe can also retrieve a single variant by its ID:\n\n    curl http://localhost:8080/api/variants/widethumb?apiKey=CHANGEME\n\nproduces:\n\n    {\n      \"variants\": [\n        {\n          \"id\": \"mediumthumb\",\n          \"transforms\": \"-geometry 120x70^ -gravity center -extent 120x70\",\n          \"createdAt\": \"2017-03-07T05:47:25.395Z\",\n          \"updatedAt\": \"2017-03-07T05:47:25.395Z\"\n        }\n      ]\n    }\n\n`PUT` and `DELETE` requests may similarly be used to update (change\nthe transforms) and remove existing variants.\n\nOnce variants are made, images may be accessed as follows (assuming you\nhave `imgcat` installed):\n\n    curl http://localhost:8080/api/image/mediumthumb/http%3A%2F%2Fplacehold.it%2F100x100.png%3Ftext%3DTEST | imgcat\n\nOr you can choose to pre-generate images for users to retrieve later:\n\n    curl http://localhost:8080/api/image/mediumthumb/http%3A%2F%2Fplacehold.it%2F100x100.png%3Ftext%3DTEST | imgcat\n\nSimple usage statistics are also available:\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrrobinson14%2Fsits","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrrobinson14%2Fsits","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrrobinson14%2Fsits/lists"}