Compare commits

...

649 Commits

Author SHA1 Message Date
Pitu 9367eb5eb8 Merge branch 'master' of github.com:WeebDev/lolisafe 2022-01-14 01:05:46 +09:00
Pitu 6cf87b95ec chore: change to sass 2022-01-14 01:05:43 +09:00
Andy Chan fcd9b7797f
Remove safe.fiery.moe from sites.md (#317)
safe.fiery.moe is no longer active. see: https://blog.fiery.me/safe-fiery-me-shutting-down-soon-tm
2022-01-13 23:19:04 +09:00
Pitu f8d763dca3 chore: change to dart-sass 2022-01-13 23:18:31 +09:00
Brandon Dusseau 556dfbd5f5
Update sharp package to fix crash (#312) 2021-11-25 04:19:32 +09:00
Jason 25897ba6d3
Allow API Access to some endpoints (#304) 2021-11-03 22:17:34 +09:00
Kana 58864852d1
fix: update upload directories 2021-09-02 02:04:57 +09:00
Kana f262fa8069
Update db location 2021-09-02 01:54:17 +09:00
Pitu ff046169bf fix: wrong case on env variable 2021-08-22 22:50:30 +09:00
Pitu 63016a5b74 fix: chunked upload post 2021-08-22 22:40:32 +09:00
Zephyrrus a8b985240d
fix: anon upload and domain fix for album links (#292) 2021-08-22 22:33:28 +09:00
Kana 0d36d03db6
Merge pull request #283 from Devices/patch-1
Remove duplicate proxy_redirect directive
2021-07-01 02:35:21 +09:00
Devices 45ae83d60c
Remove duplicate proxy_redirect directive
The config check run by nginx will fail because this directive is stated twice
2021-06-30 17:40:24 +02:00
Pitu c9de92cc7f fix: error message not being shown 2021-06-20 23:39:31 +09:00
Pitu a4c7dc5cf3 fix: typo 2021-06-20 22:03:10 +09:00
Pitu 2efd58f1a4 chore: remove leftovers 2021-06-20 21:53:16 +09:00
Pitu c416e2a0bf feat: show field notes if any 2021-06-20 21:53:09 +09:00
Pitu d9e0537a1d fix: be able to delete tags 2021-06-20 21:20:27 +09:00
Pitu 1c463ea81e fix: pulling config from the db during build process 2021-06-20 21:16:56 +09:00
Pitu 270b7acd4c chore: hardcode api location 2021-06-20 18:53:59 +09:00
Pitu a4c447bb8b chore: add defaults for stats and other features 2021-06-20 18:46:21 +09:00
Pitu 43ce7c7c32 chore: remove update script 2021-06-20 18:46:04 +09:00
Pitu 0917f6f91f fix: typos 2021-06-20 18:45:56 +09:00
Pitu aff22be1b3 fix: return all used values 2021-06-20 18:45:32 +09:00
Pitu 740666012c fix: docker script 2021-06-20 03:54:45 +09:00
Kana 065c5221a0
Merge pull request #278 from Zephyrrus/Zephyrrus-feature/database_based_settings
Zephyrrus feature/database based settings
2021-06-19 02:03:57 +09:00
Pitu 0ec31e2371 fix: potentially fix the blocked extensions array splitting 2021-06-19 02:03:24 +09:00
Zephyrrus 2b5857182f fix: migrations
fix: order of parameters in the startup script
2021-06-18 15:11:47 +03:00
Pitu acc7da5310 test: seeding during migration 2021-06-18 02:53:52 +09:00
Pitu 9a0e6f9640 fix: seeding 2021-06-18 02:53:45 +09:00
Pitu 142028a058 feat: make nuxt use the new domain .env 2021-06-18 01:46:25 +09:00
Pitu 940ab07d35 feat: prevent running without a domain 2021-06-18 01:45:19 +09:00
Pitu f42c75c7f4 feat: add domain to setup process 2021-06-18 01:45:10 +09:00
Pitu d4390cce40 chore: change save msg 2021-06-17 23:35:44 +09:00
Pitu c131c3a1fc feat: save correct db info 2021-06-17 23:31:48 +09:00
Pitu ce893ebc15 chore: prevent restarting for the time being 2021-06-17 23:31:38 +09:00
Pitu ed9fa0fa72 chore: remove console.log 2021-06-17 23:31:28 +09:00
Zephyrrus 0cae7e9eda feat: show validation errors from joi on the frontend 2021-06-17 16:06:53 +03:00
Zephyrrus 6fe5055e9d feat: show setting values on the settings page and implement sending to backend (no saving yet) 2021-06-17 01:13:15 +03:00
Pitu 30808a3574 feat: fetch all settings if admin 2021-06-17 03:39:58 +09:00
Pitu ff343727d2 fix: upload size 2021-06-17 01:17:32 +09:00
Pitu 50d13e2ae7 feat: fetch settings from api 2021-06-17 01:10:24 +09:00
Pitu 334c3d1c34 feat: make frontend use database settings 2021-06-17 00:51:09 +09:00
Pitu b2253c7f60 chore: update db migration, seed and docker env 2021-06-15 00:13:37 +09:00
Pitu f45cb197e4 update package.json, pin volta, update deps 2021-06-15 00:12:56 +09:00
Pitu d3c80127ec chore: update process.env usage 2021-06-15 00:12:26 +09:00
Pitu 9b28e56e09 chore: get host from req instead of config 2021-06-08 00:33:01 +09:00
Pitu 3358cf6939 chore: simplify dev commands 2021-06-08 00:12:38 +09:00
Pitu 44974f937a chore: docs update 2021-06-07 17:40:28 +09:00
Kana 987078b9af
Merge pull request #276 from jeremyfritzen/master
Made Chibisafe.sh executable + Updated Docker docs
2021-06-07 16:26:22 +09:00
Pitu 5f5716963d wip 2021-06-07 16:25:02 +09:00
Jeremy 067140cf65 Made chibisafe.sh executable and updated docker docs to fix the chibisafe.sh name 2021-05-28 12:00:43 -04:00
iCrawl 44cdb2a51e
fix: docker setup once and for all 2021-05-23 14:24:39 +02:00
Noel cb76049e02
fix: docker image for chibisafe 2021-05-22 15:42:13 +02:00
Kana 2bd32f927b
Merge pull request #272 from Swaggaaa/patch-1
Removes duplicated entries in nginx.conf
2021-04-24 12:32:19 +09:00
Sergi Ibànyez Peña 2267e3ea55
Removes duplicated entries in nginx.conf 2021-04-24 04:14:28 +02:00
Kana fd0a106cea
Create sites.md 2021-04-12 22:59:09 +09:00
Pitu 3f223a9dbf feat: implement initial values and saving to db logic 2021-03-25 02:03:57 +09:00
Pitu abd7a1ca2e chore: move database migration script 2021-03-25 00:09:33 +09:00
Pitu ab211c8a9e wip 2021-03-23 22:35:39 +09:00
Pitu c8456954b2 fix: proper source maps 2021-03-23 22:35:36 +09:00
Pitu 541bfedb92 wip 2021-03-23 22:35:29 +09:00
Pitu b24c0175f5 Merge branch 'feature/database_based_settings' of https://github.com/Zephyrrus/huskysafe into Zephyrrus-feature/database_based_settings 2021-03-23 22:24:11 +09:00
Kana 771d8494ef
chore: add screenshots to the readme 2021-03-21 17:47:01 +09:00
Kana 96ffed5cca
Merge pull request #262 from iShibi/fix-readme
docs(Readme): fix numbering for installation steps
2021-03-16 01:37:03 +09:00
Shubham Parihar 602f1572f4 docs(Readme): fix numbering for installation steps 2021-03-15 22:01:07 +05:30
Pitu 52a91f796e chore: update size limit in nginx 2021-03-14 17:49:15 +09:00
Pitu 6199cc0cb6 Merge branch 'master' of github.com:WeebDev/lolisafe 2021-02-21 23:38:54 +09:00
Pitu ea0d8aafcf fix: disabled users on migration 2021-02-21 23:38:00 +09:00
Pitu e09fcf70d8 fix: default values 2021-02-21 23:37:47 +09:00
Kana 9200604adb
Update docker.md 2021-02-21 01:10:58 +09:00
iCrawl f7bcd6718d
chore: update docker guide 2021-02-20 00:42:23 +01:00
Pitu bbce774d6c chore: add docker docs 2021-02-06 00:25:43 +09:00
Pitu 58e900b5ac chore: update logo to be square for embeds 2021-01-21 01:40:39 +09:00
Pitu 950a874acf chore: change twitter embed size 2021-01-21 01:36:45 +09:00
Pitu ba409ef2fc chore: update metadata 2021-01-21 01:35:04 +09:00
Pitu f2ac2da642 fix: potentially fix meta embeds 2021-01-21 01:14:43 +09:00
Kana e779706ab7
Merge pull request #254 from WeebDev/feature/ssr
Feature/ssr
2021-01-21 00:51:43 +09:00
Pitu 528425bcba feat: prevent embeds being nsfw 2021-01-20 16:58:13 +09:00
Pitu 56f38f4494 feat: make public albums ssr friendly 2021-01-20 14:09:22 +09:00
Pitu a3bf693d30 chore: switch to asyncData where needed 2021-01-20 14:09:06 +09:00
Pitu 91a15f417e feat: enable ssr and serve it with the api 2021-01-20 14:01:36 +09:00
Pitu 68fcbcb545 chore: update nuxt 2021-01-20 14:01:01 +09:00
Pitu eb82ecb077 chore: remove console logs 2021-01-20 01:36:32 +09:00
Pitu 68fd5bd133 feat: add pagination to public album links 2021-01-20 01:33:35 +09:00
Pitu bf0b0f64bf feat: add pagination to user files in admin view 2021-01-20 00:43:03 +09:00
Kana e1e1a8ba0c
Merge pull request #253 from Zephyrrus/fix/fetching_restricted_data_on_public_album
fix: trying to fetch tags and albums on a public album link
2021-01-19 02:36:28 +09:00
Zephyrrus cf513f2ebe fix: trying to fetch tags and albums on a public album link 2021-01-17 18:26:01 +02:00
Kana 38021d9599
Merge pull request #252 from WeebDev/fix/docker
fix: docker setup
2021-01-12 10:23:37 +09:00
iCrawl 63509cd2d3
fix: docker setup 2021-01-11 18:42:59 +01:00
Zephyrrus dc4f3a6557 fix: sections not rendering 2021-01-11 18:57:19 +02:00
Zephyrrus d69fcd856a feat: add sections to settings object meta 2021-01-11 12:51:45 +02:00
Zephyrrus 46ef63fb9f feat: add dynamic settings page rendering based on the Joi object 2021-01-10 02:04:35 +02:00
Zephyrrus 3c303241e1 feat: create settings schema (used for rendering and validating) 2021-01-09 01:03:03 +02:00
Kana 01e17ed856
Merge pull request #251 from Zephyrrus/fix/paginate_search
fix: pagination not working when searching & fix: search not working on albums
2021-01-09 00:53:16 +09:00
Zephyrrus e962efd014 fix: pagination not working when searching
fix: search not working on albums
2021-01-08 17:44:48 +02:00
Pitu 81b722452c feat: use LIKE on queries for case sensitivity issues 2021-01-09 00:42:25 +09:00
Pitu 0d18e7d2a2 chore: remove query from API response 2021-01-09 00:01:34 +09:00
Pitu 0484ea74a2 fix: prevent db from committing suicide 2021-01-08 23:47:18 +09:00
Pitu 523359ec32 chore: be able to refresh stats on demand 2021-01-08 20:55:57 +09:00
Pitu 09c834e6ce chore: force migration on service start 2021-01-08 20:19:06 +09:00
Kana 3cfb2721ce
Merge pull request #250 from Zephyrrus/feature/system_status_page
Feature/system status page
2021-01-08 19:48:25 +09:00
Kana 0205300cb9
Merge pull request #248 from WeebDev/feature/stats-dashboard
feature: Add basic statistics UI
2021-01-08 13:46:17 +09:00
Zephyrrus b58e12cad8 fix: pg driver doesn't return anything without .returning()
feat: disable .returning() is not supported warning message, it's useless.
2021-01-08 00:41:43 +02:00
Zephyrrus bf10242180 fix: don't generate a stat every second on minute 0 of every hour 2021-01-08 00:01:11 +02:00
Zephyrrus 925080f6a0 chore: refactor stats generator to use an enum for types
fix: saved data not being converted to JSON automatically when using SQLite (and possibly MSSQL)
feat: display when a stat was generated in the section's header
fix: not being able to click through the footer (sorry IE11 users, you still won't be able to click through it)
fix: add is-mobile to columns so they don't stack on top of each other
feat: add text next to the NSFW toggle and make it look like the elements around it (begone round iOS edges)
2021-01-07 23:55:37 +02:00
Zephyrrus 34bd71948e chore: revert nuxt update, it breaks SSR in development mode, not worth it. 2021-01-07 21:56:35 +02:00
Zephyrrus a838d024a7 Merge branch 'feature/stats-dashboard' into feature/system_status_page 2021-01-07 21:39:47 +02:00
Zephyrrus b3df1dd7a6 feat: change mutation timestamp on every database mutation 2021-01-07 21:36:56 +02:00
Zephyrrus d4b1550439 chore: update knex, mdi/font, nuxtjs/axios, nuxt, systeminformation
chore: remove deprecated mode from nuxt.config.js
2021-01-07 21:36:26 +02:00
Pitu be6ce9ac5a fix: indentation 2021-01-07 23:05:15 +09:00
Pitu 5823fa95cd feature: Add basic statistics UI 2021-01-07 23:02:35 +09:00
Pitu f3dc0ffe75 chore: update sharex snippet to use apiKey instead of jwt 2021-01-07 19:03:47 +09:00
Zephyrrus e0801f0c19 fix: use PassThrough from FileType to get the real mimetype of a file while it's being saved to the disk 2021-01-07 10:47:16 +02:00
Zephyrrus f151a8ac3a chore: Move statistics related functions to it's own file
fix: Extract database constructor to a separate file to ensure we only create one knex instance/db
feat: Add cron for saving the stats to the database every hour
feat: Get cached stats from database (if a stat is not found in the db, generate it)
2021-01-07 10:47:16 +02:00
Zephyrrus 9370c32182 feat: Add experimental stats endpoint (no cache system yet) 2021-01-07 10:47:16 +02:00
Zephyrrus 53f5015c99 feat: check for real mimetype using file-type
For now, if file-type returns undefined, we take the value from the browser. In the future this should be removed to ensure people can't bypass the real mime checking using a special file that can't be recognized by file-type.
2021-01-07 10:47:16 +02:00
Pitu 46ec3f7168 fix: normalize url and thumbnail response 2021-01-07 17:11:01 +09:00
Pitu 07a873c88b fix: terminate rehashing script when done 2021-01-07 17:09:56 +09:00
Pitu 9da45d6160 fix: incorrect case on colum name 2021-01-07 13:22:58 +09:00
Kana f944469162
Merge pull request #246 from WeebDev/feature/stream-writer
feature: new chunk and write strategy
2021-01-07 03:47:42 +09:00
Pitu a0824b5a97 feat: add rehashing script 2021-01-07 03:08:08 +09:00
Pitu df4e4272f4 fix: early return if duplicated file 2021-01-07 03:08:00 +09:00
Pitu ac5b3f29fa chore: bump version 2021-01-07 03:07:32 +09:00
Pitu 1eccbb3d30 chore: cleanup 2021-01-07 02:25:43 +09:00
Pitu 38ff50eb80 chore: remove unused methods 2021-01-07 02:25:07 +09:00
Pitu 28efb0d9ed feature: new chunk and write strategy 2021-01-07 02:11:53 +09:00
Pitu d2bf8ce8c8 fix: dont show admin navbar if not an admin 2021-01-05 17:41:31 +09:00
Pitu 153d764c8f feat: enable user creation from admin panel 2021-01-05 17:03:25 +09:00
Pitu 4bda02813f Merge branch 'master' of github.com:WeebDev/lolisafe 2021-01-05 16:39:13 +09:00
Pitu 92f38085b0 fix: dont save the file to album if no album 2021-01-05 16:38:44 +09:00
Pitu f125011413 chore: docs 2021-01-05 16:12:33 +09:00
Pitu cdeca073ba fix: migration didn't properly grab the env variables 2021-01-05 16:12:25 +09:00
Kana 25723290b9
Merge pull request #233 from Biyoni/patch-2
Update systemd.md
2021-01-05 16:04:03 +09:00
Liam 05137aad9c
Update systemd.md 2021-01-05 04:56:47 +00:00
Kana 5e274c58ce
Merge pull request #232 from Biyoni/patch-1
Add systemd unit and guide
2021-01-05 11:44:32 +09:00
Liam c2afd2c2dc
Update systemd.md 2021-01-05 02:41:39 +00:00
Liam a8e37fa0c8
Create systemd guide 2021-01-05 02:40:52 +00:00
Liam 1516ec6281
Create sample service file
Create sample service file for launching/restarting via systemd.
Configured by modifying User, WorkingDirectory and EnvironmentFile to reflect username and path.
File is to be moved to ```/etc/systemd/system/chibisafe.service```
2021-01-05 02:33:41 +00:00
Pitu f2e270d284 fix: try to remove thumbnail only if the file has one 2021-01-05 11:19:56 +09:00
Pitu 5e219868b0 Add meta name to pages 2021-01-05 01:27:39 +09:00
Pitu 3934621d25 chore: begone TODO list, hello github projects 2021-01-05 00:51:02 +09:00
Pitu d8af517adc chore: change yarn for npm 2021-01-04 01:06:46 +09:00
Pitu fcd39dc550 Merge branch 'dev' 2021-01-04 01:04:20 +09:00
Pitu f20963b4a1 chore: update todo 2021-01-04 01:02:27 +09:00
Pitu 4ff204115c chore: update docs 2021-01-04 00:51:58 +09:00
Pitu b77c0a57cc feat: add testing capabilities 2021-01-04 00:48:34 +09:00
Pitu 7d5b3c4ac7 Update migration 2021-01-03 22:48:07 +09:00
Pitu 72d3fad525 chore: add defaults to setup script 2020-12-28 05:06:08 +09:00
Kana 4334dda3f6
Merge pull request #231 from Zephyrrus/dev
fix: nsfw album toggle doesn't propagate the state change properly
2020-12-28 02:42:18 +09:00
Zephyrrus 13058d99d6 fix: nsfw album toggle doesn't propagate the changes properly
fix: add nsfw flag to the booleanFields in knex postProcessResponse
2020-12-27 18:58:58 +02:00
Pitu edb3bed988 feat: Add warning to nsfw albums 2020-12-28 00:10:59 +09:00
Pitu 24edf8f4fd fix: mobile styling 2020-12-27 20:17:28 +09:00
Pitu b2ddfbc8a6 Update admin password to be random 2020-12-27 05:16:21 +09:00
Kana 2737eb5c2c
Merge pull request #229 from WeebDev/feat/docker
add docker support
2020-12-27 05:03:13 +09:00
iCrawl 5d2d46d8dc
add docker support 2020-12-26 21:00:53 +01:00
Kana 35c14c2242
Merge pull request #230 from WeebDev/fix/chunk-uploads
Fix/chunk uploads
2020-12-27 04:49:38 +09:00
Pitu aa7d245317 feat: Add hash checking for chunked uploads 2020-12-27 04:48:03 +09:00
Pitu e97fee4844 Fixes chunked uploads not being saved to albums or thumbnails 2020-12-27 04:27:56 +09:00
Pitu 68634418e1 Squashed commit of the following:
commit df4b0378571708086a276e49ac8630095e08b0b7
Author: Pitu <heyitspitu@gmail.com>
Date:   Sun Dec 27 03:00:17 2020 +0900

    feat: move database modification to a new migration file
2020-12-27 03:02:14 +09:00
Pitu 726f47f301 chore: use instance name for album download 2020-12-27 01:59:38 +09:00
Pitu ec2f9e0d98 Rebrand 2020-12-25 20:45:22 +09:00
Pitu 7190e035b4 chore: style changes 2020-12-25 20:17:47 +09:00
Pitu 5c2f6782dd Chore: prevent server from starting if .env config missing 2020-12-25 20:09:17 +09:00
Pitu 24647beb97 Update ESLint 2020-12-25 20:08:38 +09:00
Pitu 4dfb087729 fix: database location 2020-12-25 04:10:52 +09:00
Pitu f73cde6bb5 Chore: Move database to a subfolder for docker purposes 2020-12-25 03:58:19 +09:00
Pitu 943a00827d fix: remove log 2020-12-25 03:24:02 +09:00
Pitu 493e05df27 Fix: thumbnail creation 2020-12-25 03:08:53 +09:00
Pitu 047a6afce6 Fix: use webp for thumbnails 2020-12-25 02:54:22 +09:00
Pitu 3051fbe948 Feat: add rotating logs when running in production env 2020-12-25 02:54:05 +09:00
Pitu 09d8d02e6c Cleanup 2020-12-25 02:08:54 +09:00
Pitu 279cde7dd3 Remove flexsearch 2020-12-24 23:52:26 +09:00
Pitu fb2c27086f Fix ESLint rules once and for all 2020-12-24 23:45:16 +09:00
Kana 2412a60bd4
Merge pull request #228 from Zephyrrus/begone_trailing_commas
Merge own dev branch into main dev branch
2020-12-24 21:41:24 +09:00
Zephyrrus 587f7d69e8 bug: fix showlist resetting itself every time the page is changed
bug: fix store not commiting search results
2020-12-24 13:57:09 +02:00
Zephyrrus d2efb2707c bug: Thumbs are stored as webp and not as png anymore 2020-12-24 13:20:11 +02:00
Zephyrrus e6c3327b9c chore: apply linter rules to files outside of src 2020-12-24 12:25:19 +02:00
Zephyrrus 90001c2df5 chore: remove trailing commas 2020-12-24 10:40:50 +02:00
Zephyrrus 13825ddae6 chore: update lock files 2020-12-24 10:21:19 +02:00
Zephyrrus d6813fa912
Merge pull request #4 from Zephyrrus/chore/update_buefy
Update buefy
2020-11-12 23:07:16 +02:00
Zephyrrus 20ee770fd6 chore: update package.json 2020-11-12 23:05:51 +02:00
Zephyrrus 4a3fef2b99 chore: update buefy to 0.9 2020-11-12 22:58:34 +02:00
Zephyrrus 443e63d05a
Merge pull request #3 from Zephyrrus/feature/custom_album_urls
feat: allow administrators to create custom links for albums
2020-10-02 22:18:16 +03:00
Zephyrrus 151c360740 feat: allow administrators to create custom links for albums 2020-10-02 22:16:34 +03:00
Zephyrrus c88d08330f feat: add experimental query to sql generator for searching 2020-07-23 04:09:01 +03:00
Zephyrrus 39e9941ded feat: Add hiding to recommendations as a prop
fix: change some of the regexes to remove , and ; as separator support because the query parser doesn't understad them and I don't feel like dealing with all the edge cases introduces by it
2020-07-23 04:08:31 +03:00
Zephyrrus 279234a081 feat: add new search input
For now, it clones the autocomplete from buefy, untill a PR or resolution to the opened issue about not being able to manipulate how the suggestions are applied is found
2020-07-22 02:14:06 +03:00
Zephyrrus 01c04298b0 fix: add no-active class 2020-07-22 02:12:40 +03:00
Zephyrrus 1a4744de3c feat: show selected albums first when modal is first opened
fix: change the color of the dropdown for tags to match the base theme
chore: pass props into some of the components instead of fetching from store inside of components
2020-07-22 02:12:24 +03:00
Zephyrrus 78c6fa14e6 feat: add experimental search parser 2020-07-22 02:11:05 +03:00
Zephyrrus 93478a334b fix: make modal use full height on mobile 2020-07-21 15:45:28 +03:00
Zephyrrus 18bb451f79 feat: implement all-in-one file detail viewer, tag editor and album selection modal 2020-07-20 23:01:45 +03:00
Zephyrrus fe314a742f fix: return the edited/changed/delete entity from API 2020-07-20 22:40:31 +03:00
Zephyrrus 9de50b26f1 feat: add tag deletion from images 2020-07-20 22:40:00 +03:00
Zephyrrus c5b165b495 fix: join tags by the proper key 2020-07-20 21:43:23 +03:00
Zephyrrus 6fee07d9e1 fix: don't crash the server if a route fails to load 2020-07-20 21:29:06 +03:00
Zephyrrus 04660dbce1 feat: add single tag adding to file
fix: fix the batch tag adding to properly await for a response, and use the proper column while adding the tags
2020-07-20 21:28:46 +03:00
Pitu a891cbc1fc Enable deleting files with the API key 2020-07-20 09:17:13 +09:00
Zephyrrus 645b62b81d Merge branch 'dev' into dev-zephy 2020-07-19 22:35:59 +03:00
Zephyrrus c93ddb0900 feat: Start working on a new album/tags/image info modal 2020-07-19 22:27:11 +03:00
Zephyrrus b49017aafd chore: add custom class to inputs until fix is released on buefy's master 2020-07-19 22:26:55 +03:00
Zephyrrus 165731fae8 chore: add updated yarn.lock 2020-07-19 22:25:29 +03:00
Zephyrrus ef255587b1 feat: Add bulma divider
fix: Change extractCSS to only extract on production so we don't break HMR
2020-07-19 22:25:11 +03:00
Pitu bf63bc5e2e Update setup script 2020-07-18 16:58:29 +09:00
Pitu 37a5a8cce2 Upgrade migration doc 2020-07-18 16:50:39 +09:00
Pitu 0b6867f1b1 update guide 2020-07-18 16:45:51 +09:00
Pitu aea442a956 Update migration instructions 2020-07-18 16:45:01 +09:00
Pitu d3b7321bf6 whoops 2020-07-18 16:41:08 +09:00
Pitu 91507249c0 Update readme 2020-07-18 16:40:21 +09:00
Pitu 8e3c3841a4 Update database migration 2020-07-18 16:39:24 +09:00
Pitu 898a2dde78 Re-process thumbnails on migration 2020-07-18 05:50:59 +09:00
Pitu d644b21d43 Make thumbnails webp (bye bye safari) 2020-07-18 05:50:47 +09:00
Pitu 609ff7ceb4 Update displaying thumbnails of unsupported file types 2020-07-18 04:47:24 +09:00
Pitu 5e07436f0d Add logout button 2020-07-18 04:45:58 +09:00
Pitu 8ffa0ba075 Add endpoint with version of the API 2020-07-18 04:31:12 +09:00
Pitu ec51cc803e Update TODO 2020-07-18 03:40:25 +09:00
Pitu b70a75da1a Fix for real 2020-07-18 03:37:52 +09:00
Pitu 6dd7500084 Fix deleting files without thumb 2020-07-18 03:35:08 +09:00
Pitu a057f26896 Fix url retrieval of uploaded file 2020-07-18 03:23:59 +09:00
Pitu 407fb8bcc3 Fix authorization 2020-07-18 03:05:12 +09:00
Pitu 5f58431409 Fix authorization 2020-07-18 02:57:24 +09:00
Pitu 4dafc79cb7 fix authorization 2020-07-18 02:55:05 +09:00
Pitu 2d06d918a1 Timeout, package and docs cleanup 2020-07-18 02:21:31 +09:00
Zephyrrus a721681944
Merge pull request #1 from Zephyrrus/feature/store_refactor
Feature/store refactor
2020-07-10 01:17:00 +03:00
Zephyrrus 0f66d80703 refactor: finish refactoring all the components to use vuex 2020-07-10 01:13:51 +03:00
Zephyrrus 7e78a03931 fix: stop leaking user passwords to admins AGAIN 2020-07-10 01:13:23 +03:00
Zephyrrus fd3f6de51a refactor: refactor most of the admin pages to use the store instead of internal states 2020-07-09 02:24:40 +03:00
Zephyrrus da703de1d0 fix: register handler as a plugin 2020-07-09 02:23:32 +03:00
Zephyrrus 5ded974ef9 feat: add lolisafe as a color to bulma instead of doing weird hacks 2020-07-09 02:23:19 +03:00
Zephyrrus 704578e964 feat: add handler for actions
Takes care of dispatching a 🍞 on success/error
2020-07-09 02:22:48 +03:00
Zephyrrus 746a454612 fix: stop leaking user's password and their apikey to admins 2020-07-09 02:22:08 +03:00
Zephyrrus 495a23c3a5 feat: add notifier plugin for 🐍 and 🍞 2020-07-09 02:21:35 +03:00
Zephyrrus 6713eca9d4 chore: add unique integrity checks to the database for many-to-many tables 2020-07-08 19:22:25 +03:00
Zephyrrus ad852de51a chore: linter the entire project using the new rules 2020-07-08 04:00:12 +03:00
Zephyrrus b519b6ccb4 refactor: refactor grid to use vuex for every action 2020-07-08 03:37:50 +03:00
Zephyrrus 49d3e3b203 feat: add morgan for logging requests if env is not production 2020-07-08 03:15:27 +03:00
Zephyrrus 1526637881 feat: add new sidebar with mdi icons and active reactivity 2020-07-08 03:15:07 +03:00
Zephyrrus 1a8b6602e0 refactor: change uploader component to use vuex 2020-07-08 03:13:51 +03:00
Zephyrrus eccbb1ca93 fix: errors in Util caused by separating into different classes improperly 2020-07-08 02:32:12 +03:00
Zephyrrus 5d61b4d000 feat: refactor waterfall to be more efficient 2020-07-07 02:50:03 +03:00
Zephyrrus fb0bc57542 feat: try fixing THE SHITTY WATERFALL 2020-07-07 02:02:59 +03:00
Zephyrrus 15f296a780 chore: eslint stores
feat: merge album and images
2020-07-07 02:02:37 +03:00
Zephyrrus 766e74cc51 feat: add video preview on hover to dashboard and apply new linter rules to some of the files 2020-07-05 04:18:08 +03:00
Zephyrrus 04fdd63cee feat: refactor single album page to use vuex 2020-07-05 04:17:09 +03:00
Zephyrrus ba5a740885 chore: change to vue recommended eslint rules + airbnb-base for js 2020-07-05 04:16:02 +03:00
Zephyrrus 1e1f3fbb27 feat: experimental videos in grid 2020-07-04 23:18:51 +03:00
Zephyrrus b4603fd64e fix: fix axios plugin throwing if no message from backend 2020-07-04 03:53:06 +03:00
Zephyrrus 39ed9d336e feat: make the delete link button more reactive (loading state if deletion pending) 2020-07-04 03:52:40 +03:00
Zephyrrus ac1d6eec5b fix: change watcher from a component watcher to a store watcher 2020-07-04 03:28:05 +03:00
Zephyrrus 92be4504cc feat: refactor most of the album components to use store for presentation and actions 2020-07-04 03:26:35 +03:00
Zephyrrus 836a01327d chore: add nsfw flag to migration 2020-07-04 03:25:21 +03:00
Zephyrrus b620aa9815 feat: refactor some of the queries to returned added/updated data 2020-07-04 03:25:04 +03:00
Zephyrrus 7581d13d1c feat: separate album view into multiple components and use vuex 2020-07-03 00:35:09 +03:00
Zephyrrus 22f9eb4dff feat: refactor preview to support random fragment extraction 2020-07-02 23:42:44 +03:00
Zephyrrus c2dbbe6396 feat: refactor account to use vuex, fix small presentational components 2020-07-02 23:42:02 +03:00
Zephyrrus dd46f79550 feat: return APIKey when fetching user 2020-07-02 23:40:35 +03:00
Zephyrrus 24157dd1b9 feat: experimental video preview generator in webm form 2020-07-02 20:13:05 +03:00
Zephyrrus a790d7749e feat: add experimental meaningful preview extraction from videos
For now, it sitll requires gifski. It could be rewritten to use webp instead of gifs, because that is a lot faster, uses less space and we could use ffmpeg for it.
2020-07-02 03:42:20 +03:00
Zephyrrus 42f1a1003a feat: externalize thumb generation function for easier testing 2020-07-02 03:12:12 +03:00
Zephyrrus 720ffaf008 feat: start refactoring the code to actually use vuex
This includes creating multiple stores as needed for components and removing all complex states from components (since all those states should be stored in vuex)
2020-07-02 02:50:55 +03:00
Zephyrrus f0753e1551 chore: add compound launcher and basic prettier 2020-07-02 02:49:30 +03:00
Zephyrrus 1576a67bc6 chore: change nuxt.config to generate proper source-maps for vscode debugging 2020-07-02 02:48:48 +03:00
Zephyrrus 6688587eb6 feat: move toolbar into a level and add slot for pagination on grid 2020-07-02 02:48:21 +03:00
Zephyrrus e9ef148d74 feat: backend pagination for albums 2020-07-01 20:40:10 +03:00
Zephyrrus 261d0f4781 chore: add thumb generator for migration 2020-07-01 20:39:55 +03:00
Zephyrrus 44e8175e96 feat: use SSR when rendering the dashboard (if possible) 2020-07-01 20:39:42 +03:00
Zephyrrus 8416dc81a3 fix: use file id instead of index and remove load more button 2020-07-01 20:15:08 +03:00
Zephyrrus 3e1677c18a fix: remove .bmp from the imageExtensions because sharp doesn't support it
Trying to generate a thumb would throw a not supported exception, which would crash newer node.js versions because the process (on older version, it would only throw a UnhandledPromiseRejectionWarning)
2020-07-01 20:13:34 +03:00
Zephyrrus 520062508c feat: backend pagination
serverLoad++;
userRamUsage--;
2020-06-29 22:18:42 +03:00
Zephyrrus c8ad345d12 fix: heigh issues where the parent is smaller than the child 2020-06-29 22:18:18 +03:00
Zephyrrus 20a782fefe feat: add darker image for album section 2020-06-29 22:17:42 +03:00
Zephyrrus 7e4dbdbd2b misc: Change the color of the warning on the homepage to red and bigger font-size 2020-06-29 20:12:14 +03:00
Zephyrrus ac037c773e fix: Remove hero and hero body and use sections and containers instead
Hero is meant to be used as a full-width banner to showcare or present somethign, not to contain the entire content of the webpage
2020-06-29 20:02:47 +03:00
Zephyrrus 27b0fdafb2 misc: remove patreon. sorry. 2020-06-29 20:00:49 +03:00
Zephyrrus 50d233cd66 fix: fix minor visual issues on the album page 2020-06-29 17:02:06 +03:00
Zephyrrus 0a50ef771e fix: change navbar to buefy navbar for mobile support 2020-06-29 17:01:38 +03:00
Zephyrrus 048e5d9325 feat: add logout link if logged in 2020-06-29 16:03:08 +03:00
Zephyrrus 0e1ae73855 chore: update buefy to 0.8.20 2020-06-29 16:02:36 +03:00
Zephyrrus 4df3976ded fix: dispatch logout from axios if token expires 2020-06-29 16:02:23 +03:00
Pitu 695d9a74ef Updated TODO 2020-06-25 02:21:05 +09:00
Pitu 04319acf20 Only display `Show more` button when there's more to show 2020-06-25 02:14:11 +09:00
Pitu 207fc916d9 Handle nuxt routes on page load 2020-06-25 02:06:11 +09:00
Pitu d1340c26b5 Optimize album view 2020-06-25 02:06:00 +09:00
Pitu f189ddf9e6 Cleanup 2020-06-25 02:05:48 +09:00
Pitu a9fe08f9e5 Update error message 2020-06-25 01:36:04 +09:00
Pitu b526d88036 Optimize the queries fetching albums/files 2020-06-25 01:35:52 +09:00
Pitu b8fc0cc858 Fix frontend registration display 2020-05-11 01:15:46 +09:00
Pitu e0c35a7d74 fix: nginx config 2020-05-11 01:07:44 +09:00
Pitu 496477ebda Feature: enable apiKey access to uploads and album fetching for the uploader/sharex/3rd party 2020-05-11 00:57:56 +09:00
Pitu b886fda079 chore: cleanup and todo 2020-05-11 00:19:10 +09:00
Pitu ec67bb8087 fix: remove uuid from user registration 2020-05-10 22:44:21 +09:00
Pitu cd6170b939 fix .env values for the frontend 2020-05-10 22:16:16 +09:00
Pitu 5df8485c5e updated nginx sample config 2020-05-10 21:51:49 +09:00
Pitu 124ff68f06 Feat: implement lazy loading for waterfall view 2020-05-10 21:22:48 +09:00
Pitu b27b4c47f7 feat: Proper deleting of albums 2020-05-10 21:22:25 +09:00
Pitu 1836c8c93a Small fix for migration script, dont import deleted albums 2020-05-10 21:12:30 +09:00
Pitu 4c52932426 Features:
* Serve files during development
* Own endpoint for fetching the albums of a file
2020-05-10 20:02:48 +09:00
Pitu d4ac722f58 Feature: Migration script from v3 to v4 2020-05-10 20:01:37 +09:00
Pitu aadb01bcff Style changes to albums 2020-05-10 20:01:05 +09:00
Pitu 6da29eb7c1 Sort files by newest 2020-05-10 20:00:52 +09:00
Pitu 432d86022c chore: forgot to remove this uuid 2020-05-10 00:39:53 +09:00
Pitu 97243c7087 chore: Ignore old db data 2020-05-10 00:39:43 +09:00
Pitu de54e19d3a chore: remove the use of uuid 2020-05-10 00:03:45 +09:00
Pitu a639b85734 Fix: consistent hash of uploads 2020-05-09 23:56:35 +09:00
Pitu c114e59be3 Feature:
* Frontend is now served by the API process
* Only 1 process spawns for lolisafe to work
* Switched frontend from server-side render to static site, now saved in `/dist`
2020-05-09 19:21:20 +09:00
Pitu d63f1f57e9 wip:
* Dark theme by default
* Re-arranged vue files structure
* Re-arranged the layout file to make it easier to extend
2020-04-28 10:47:22 +09:00
Pitu bf37e04cb6 feature: footer 2020-04-13 01:17:03 +09:00
Pitu 759832cd78 chore: revamp colors 2020-04-13 01:16:54 +09:00
Pitu 05c129ec32 chore: remove filepond 2020-04-11 00:52:06 +09:00
Pitu 3fb303380e chore: make use of sass 2020-04-11 00:36:14 +09:00
Pitu a957713e81 chore: update deps 2020-04-10 23:40:22 +09:00
Kana 78c5b73b70
Create FUNDING.yml 2020-02-28 09:28:12 +09:00
Kana be3f3c7d79
remove absolute urls 2019-12-22 17:08:25 +09:00
Pitu cba7bf8586 This commit adds a bunch of features for admins:
* banning IP
* see files from other users if you are admin
* be able to see details of an uploaded file and it's user
* improved display of thumbnails for non-image files
2019-10-13 02:53:45 +09:00
Pitu 4e4b4ea468 don't log out on API error 2019-10-12 22:59:56 +09:00
Pitu bca8fbcd83 refactor: removed useless code, cleaned up, fixed permissions 2019-10-12 21:14:19 +09:00
Pitu 5ca3c35381 Added new links to the navbar 2019-10-12 20:16:38 +09:00
Pitu 1177de9b04 fix: update sharex snippet 2019-10-12 20:16:23 +09:00
Pitu 43eb86c77f fix: we don't want non-admins to access the settings section 2019-10-12 20:16:10 +09:00
Pitu 4683848108 chore: update nuxt 2019-10-12 20:04:13 +09:00
Pitu 2695d192ba feature: prevent duplicated files
- If the user uploads a file that already exists under their account, it will return the previous url
- If an anonymous user uploads a file and already exists previously from another anon, return that url
- Files are unique per account since they can be deleted, anonymous files can't
2019-10-12 18:18:32 +09:00
Pitu e6eb13e5cd feature: save uploaded files to album if specified 2019-10-12 17:52:49 +09:00
Pitu c385f8b30a add nodemon for API restart during development 2019-10-12 17:52:25 +09:00
Pitu 391ee68e4a chore: Upgrade buefy to newest version 2019-10-12 15:47:25 +09:00
Pitu 459ab5433b chore: remove exif strip support.
After some thought, modifying uploaded files is not something I want to support.
2019-10-12 14:58:58 +09:00
Pitu dd9c9ac11f chore: wizard was a stupid name, it's setup 2019-10-12 14:57:31 +09:00
Pitu c121bd42f3 chore: upload fixes 2019-10-12 14:37:09 +09:00
Pitu 3680432bdf chore: prepare for filepond 2019-10-01 14:20:37 -03:00
Pitu 579e1e754a feature: uploader with chunks support 2019-10-01 14:11:16 -03:00
Pitu a552aca8ab chore: Remove unnecesary stuff 2019-10-01 14:08:43 -03:00
Pitu b1f56ef9b9 chore: hide search until it's ready 2019-09-30 07:26:15 +00:00
Pitu 4db167ec43 Fix deletion of albums and links 2019-09-30 07:24:37 +00:00
Pitu 0d36f0d69a feature: tags logic 2019-09-30 07:06:35 +00:00
Pitu 8e4f1b7838 feature: album links 2019-09-30 07:06:22 +00:00
Kana 84f1feff43
Bump lodash from 4.17.5 to 4.17.15 (#200)
Bump lodash from 4.17.5 to 4.17.15

Co-authored-by: null <49699333+dependabot[bot]@users.noreply.github.com>
2019-09-15 02:32:36 +09:00
dependabot[bot] 0ba2a828a0
Bump lodash from 4.17.5 to 4.17.15
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.5 to 4.17.15.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.5...4.17.15)

Signed-off-by: dependabot[bot] <support@github.com>
2019-09-14 17:26:49 +00:00
Kana 4d3b35072a
Merge pull request #197 from WeebDev/dependabot/npm_and_yarn/js-yaml-3.13.1
Bump js-yaml from 3.10.0 to 3.13.1
2019-06-21 10:55:16 +09:00
Kana 9f037eb6d1
Merge pull request #196 from WeebDev/dependabot/npm_and_yarn/handlebars-4.1.2
Bump handlebars from 4.0.10 to 4.1.2
2019-06-21 10:55:09 +09:00
Kana 84a44c7743
Merge pull request #195 from WeebDev/dependabot/npm_and_yarn/eslint-4.18.2
Bump eslint from 4.18.1 to 4.18.2
2019-06-21 10:55:01 +09:00
dependabot[bot] 9c617c82b3
Bump js-yaml from 3.10.0 to 3.13.1
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.10.0 to 3.13.1.
- [Release notes](https://github.com/nodeca/js-yaml/releases)
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.10.0...3.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-21 01:54:34 +00:00
dependabot[bot] a8a7e06b63
Bump handlebars from 4.0.10 to 4.1.2
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.0.10 to 4.1.2.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.10...v4.1.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-21 01:52:55 +00:00
dependabot[bot] 4617a6a60c
Bump eslint from 4.18.1 to 4.18.2
Bumps [eslint](https://github.com/eslint/eslint) from 4.18.1 to 4.18.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v4.18.1...v4.18.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-21 01:51:30 +00:00
Kana 60c9f36374
Merge pull request #194 from WeebDev/dependabot/npm_and_yarn/sshpk-1.16.1
Bump sshpk from 1.13.1 to 1.16.1
2019-06-06 17:06:31 +09:00
dependabot[bot] 677aa8f68e
Bump sshpk from 1.13.1 to 1.16.1
Bumps [sshpk](https://github.com/joyent/node-sshpk) from 1.13.1 to 1.16.1.
- [Release notes](https://github.com/joyent/node-sshpk/releases)
- [Commits](https://github.com/joyent/node-sshpk/compare/v1.13.1...v1.16.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-06 08:05:52 +00:00
Pitu ca9e1c859d Add parallel chunks and raise timeout 2019-04-30 16:01:38 +00:00
Pitu b5a04f21f9 Fix color 2019-04-24 11:14:43 +00:00
Pitu c074b5e197 Fix database value conversion 2019-04-24 09:28:30 +00:00
Pitu fec273b23b Fix when response is not an object 2019-04-24 08:41:49 +00:00
Pitu ac36cdc143 Standarize database calls to support sqlite as well as mysql/postgres 2019-04-24 08:38:53 +00:00
Pitu a7c1855ce5 Better alert handling 2019-04-24 08:37:12 +00:00
Pitu ab0839f1f5 Globally catch exceptions 2019-04-24 08:36:47 +00:00
Pitu 3bd8d119ba Refactor a bit since we globally catch API exceptions 2019-04-24 08:36:28 +00:00
Pitu 9609279554 Get rid of the icons altogether in a future commit 2019-04-23 22:20:23 +09:00
Pitu d44154d379 Fix toast 2019-04-23 22:11:14 +09:00
Pitu 5dc7eda038 Check if accept header is passed 2019-04-16 02:56:49 +00:00
Pitu 4b0966f857 Ditched sqlite. Use postgres or mysql/mariadb 2019-04-05 06:05:21 +00:00
Pitu a248f69947 Tody up a little bit 2019-03-29 03:28:25 +00:00
Pitu e9ce158e36 Add flexsearch 2019-03-29 03:28:16 +00:00
Pitu c4d803c4f6 Make sure to remove the token to prevent credential leak 2019-03-29 03:27:52 +00:00
Pitu 0f4d196c8c Fix conflicts 2019-03-29 00:44:33 +09:00
Pitu b12cc4c289 WIP apiKey validation 2019-03-29 00:36:50 +09:00
Pitu 9aba5cd221 Fix 2019-03-29 00:36:39 +09:00
Pitu 5df5751736 Removed apikey from user object and added route for requesting a new one 2019-03-29 00:36:28 +09:00
Pitu ea3e503d13 Added middleware for pages and switched to $axios 2019-03-29 00:35:58 +09:00
Pitu 8905f2e7a7 Added axios package 2019-03-29 00:35:22 +09:00
Kana fac19ac31d
Merge pull request #190 from robflop/patch-2
Adjust wording & fix grammar in account settings
2019-03-24 21:11:25 +09:00
Robin B cff0ab7ccb
Update account.vue
As discussed before, an adjustment for the wording alongside a grammatical fix.

PR as suggested by you.
2019-03-21 17:47:14 +01:00
Pitu 107d1f4750 API key WIP 2019-03-19 07:58:36 +00:00
Pitu 1790a84430 add restart at the end of update command 2019-03-15 07:53:05 +00:00
Pitu 19e79365ad proper baseurl 2019-03-15 07:52:33 +00:00
Pitu d7745aff40 Turns out we were using the wrong cookieparser 2019-03-15 07:34:19 +00:00
Pitu a142078f64 No need for this 2019-03-15 07:30:58 +00:00
Pitu dfaeb1051f yeah 2019-03-15 07:25:05 +00:00
Pitu 897e22c936 Added missing dependency 2019-03-15 07:22:20 +00:00
Pitu 85ed48219e This is not needed anymore 2019-03-15 07:17:35 +00:00
Pitu 088ffe175e This should fix credential leaking 😓 2019-03-15 07:17:27 +00:00
Pitu f06c8c9d33 dunno what's wrong here yet 2019-03-14 23:14:45 +09:00
Pitu 497a961a38 Tags 2019-03-14 23:14:37 +09:00
Pitu 79eb00f71c Small fixes 2019-03-14 23:14:24 +09:00
Pitu af9d752eaf Add uuid package 2019-03-14 23:13:51 +09:00
Kana 94bc88191e
Update issue templates 2019-03-12 17:24:36 +09:00
Pitu b1e7511593 What are strings even 2019-03-12 08:17:37 +00:00
Pitu e04833dff1 Whoops 2019-03-12 08:05:46 +00:00
Pitu e7d27844d0 Wonder if password generation is failing because of long passwords 2019-03-12 07:57:15 +00:00
Pitu 455c9ee25c Added meta tags 2019-03-12 07:43:14 +00:00
Pitu f11cb56db8 Clicking on album title takes you to the album now 2019-03-12 07:02:26 +00:00
Pitu dc4e6bf907 Added faq 2019-03-12 06:43:17 +00:00
Pitu 87ee815517 No analytics 2019-03-12 06:35:50 +00:00
Pitu 771a5c2bf7 Not tested, but should delete an album 2019-03-12 06:32:10 +00:00
Pitu 4ab3796fbd Add TODO 2019-03-12 06:19:57 +00:00
Pitu 4277db90f6 Possible fix for files not being purged 2019-03-12 06:18:32 +00:00
Pitu 69dd014f49 Add uuids 2019-03-12 06:18:19 +00:00
Pitu 00058e9915 stuff 2019-03-12 06:03:15 +00:00
Pitu 197e69f2f2 Prevent snowflakes from demoting/disabling themselves 2019-03-12 05:48:01 +00:00
Pitu 4a2ed9f0f4 No more infinite loading if album is empty 2019-03-12 05:41:10 +00:00
Pitu 2f063735d7 Changed store from deprecated mode 2019-03-12 05:38:25 +00:00
Pitu dd98cecfeb Error consistency 2019-03-12 05:38:13 +00:00
Pitu 85ac744837 Typo 2019-03-12 05:31:55 +00:00
Pitu c6b0f62d13 Put this on hold 2019-03-12 05:31:31 +00:00
Pitu 3653884560 No point in asking 2019-03-12 04:45:52 +00:00
Pitu 390eeb9b07 Automated pm2 process spawn 2019-03-12 04:45:47 +00:00
Kana 28bca3b195
Update README.md 2019-03-12 12:57:56 +09:00
Pitu 4d6cc7460d Added support links 2019-03-12 03:43:42 +00:00
Pitu 6aee90e375 Merge branch 'dev' of github.com:WeebDev/lolisafe into dev 2019-03-08 00:47:54 +09:00
Pitu 71f2450431 WIP 2019-03-08 00:47:30 +09:00
Kana 77a5b856fe
Create README.md 2019-03-08 00:45:22 +09:00
Pitu 3ce7657871 wip 2019-03-02 22:36:28 +09:00
Pitu 99bc74875e Various password fixes 2019-03-02 22:36:16 +09:00
Pitu 789f5fc259 Removed google analytics 2019-03-02 22:16:35 +09:00
Pitu 70301ca368 Fix uploader not showing up when logged in 2019-03-02 22:07:37 +09:00
Pitu ce76a8ec7b Be able to add a file to multiple albums 2019-03-02 22:07:24 +09:00
Pitu 0639c4e1bb Fix token 2019-03-02 02:55:11 +09:00
Pitu 9edc0d0508 install new packages on update 2019-03-02 02:50:18 +09:00
Pitu 33dded09a7 back 2019-03-02 02:48:11 +09:00
Pitu d821ebf4ac test 2019-03-02 02:47:59 +09:00
Pitu 0ed4624dd0 Allow downloading shareX config file 2019-03-02 02:45:31 +09:00
Pitu 73d85e8c79 Enviroment variables parsing fix 2019-03-02 02:08:11 +09:00
Kana e8f70f5170
Create pm2.md 2019-03-01 14:12:44 +09:00
Kana 04ee40e673
Update uploadPOST.js 2019-03-01 13:53:53 +09:00
Kana 47ca404b6b
Update uploadPOST.js 2019-03-01 12:50:48 +09:00
Pitu 5a701536cf todo 2019-03-01 01:14:34 +09:00
Kana 36b11f0027
Update nginx.md 2019-03-01 00:40:13 +09:00
Pitu a9e9373c9c clearer instructions 2019-03-01 00:36:06 +09:00
Kana a86e9f8323
Create nginx.md 2019-03-01 00:17:31 +09:00
Pitu 9cba85c47c changes 2019-02-28 23:52:04 +09:00
Pitu 9f5a3d15f5 Purge user's files 2019-02-28 23:51:59 +09:00
Pitu c169ab6dc1 Some stuff 2019-02-28 23:26:44 +09:00
Pitu f37d206943 Change password and api keys 2019-02-28 23:26:28 +09:00
Pitu 7ad463469b When a call regarding the token fails, logout 2019-02-28 23:26:03 +09:00
Pitu 7a74647d3e User management 2019-02-26 23:13:24 +09:00
Pitu 62c0c1db20 Add background to main page 2019-02-26 23:13:18 +09:00
Pitu 6cd31674d5 Not needed anymore 2019-02-26 22:26:54 +09:00
Pitu ab66e095a8 Added adminOnly routes 2019-02-26 22:26:35 +09:00
Pitu 80732ff90a User promotion/demotion 2019-02-26 22:26:18 +09:00
Pitu 8be134c8d8 Settings page and other things 2019-02-26 22:26:03 +09:00
Pitu ee601d3524 We dont need this package 2019-02-26 22:25:19 +09:00
Pitu c8e0ebd8ff add deleteUrl when uploading a file 2019-02-23 02:30:53 +09:00
Pitu fc95cb7b0f Better DB handling and stuff 2019-02-23 00:45:45 +09:00
Kana df90d3157a
Update Util.js 2019-02-22 15:07:37 +09:00
Kana 80a76d868f
Update Route.js 2019-02-22 15:06:29 +09:00
Kana 79bb01e720
Update .gitignore 2019-02-22 15:02:17 +09:00
Kana 9c81720c30
Update wizard.js 2019-02-22 14:47:39 +09:00
Kana 5b7dcc7576
WIP deleteUrl 2019-02-22 10:06:43 +09:00
Kana 165f891686
Update wizard.js 2019-02-22 09:31:55 +09:00
Pitu 500eb4f9a0 updated package.json 2019-02-22 00:52:35 +09:00
Pitu a284a9a064 Leftovers 2019-02-22 00:37:20 +09:00
Pitu 84c4b442cf whoops 2019-02-22 00:12:51 +09:00
Pitu c7a4a39de4 Add support for sqlite 2019-02-22 00:00:07 +09:00
Pitu 352d84ea3a Same 2019-02-21 23:49:48 +09:00
Pitu 48d1859723 Load enviroment variables into config 2019-02-21 23:49:44 +09:00
Pitu 44e6fd31d2 Database migration and seeding 2019-02-21 23:49:29 +09:00
Pitu 25c5a06ec3 derp 2019-02-21 23:05:56 +09:00
Kana 3fe0b274b7
Merge pull request #184 from natnat-mc/up-to-date
updated deps to make it work with node 10.x
2019-02-20 23:55:42 +09:00
Pitu 55e6c1f9cd WIP 2019-02-19 23:54:11 +09:00
Pitu 1b3a3de252 Exclude .env from versioning 2019-02-19 23:52:50 +09:00
Pitu 89a271818e Switch config to .env 2019-02-19 23:52:24 +09:00
Kana 2e0cbd3ea7
Create wizard.js 2019-02-19 11:30:10 +09:00
Kana 064d6a6be9
Update package.json 2019-02-19 11:29:22 +09:00
Nathan DECHER 69e3c6e5f5 updated deps to make it work with node 10.x 2019-02-18 22:46:12 +01:00
Pitu a857b8910b More stuff 2019-02-19 00:06:55 +09:00
Pitu 6fa1dbd99e Update dependencies 2019-02-19 00:06:50 +09:00
Pitu e33cf30449 Changes 2019-02-19 00:06:38 +09:00
Pitu 63f327e49d CRLF to LF 2019-02-18 23:43:15 +09:00
Pitu 4c241dc1d1 Styling 2019-02-18 23:36:35 +09:00
Kana c9fd7acf39
Merge pull request #182 from hyperdefined/master
removed my site (fixed)
2019-02-04 13:34:25 -03:00
hyperdefined cc8948eb7e
removed my site 2019-02-03 21:29:49 -05:00
Kana 5b697fd4e6
Merge pull request #179 from WeebDev/RyoshiKayo-remove-inactive-sites
Removed inactive sites
2019-01-28 21:05:41 -03:00
Kana b5e2f09a1d
Merge branch 'master' into RyoshiKayo-remove-inactive-sites 2019-01-28 21:05:32 -03:00
Kana 436cbe4009
Merge pull request #180 from kawaaii/patch-2
Update README.md
2019-01-28 21:04:01 -03:00
天使アシュリー 11881780d7
Update README.md 2019-01-28 00:42:37 +01:00
天使アシュリー 87b7a2b50a
Update README.md
Added my own lolisafe clone with edited looks. 
Not SSL secured yet due my certificate is not for Wildcards yet!
2019-01-27 23:54:01 +01:00
Kayo fe1f0ed65e
Removed inactive sites
Removed:
- safe.waliant.pw
- vortex.64i.de
2019-01-23 23:09:47 -08:00
Kana 40b2e3b3c1
Update README.md 2019-01-01 02:12:33 -03:00
Kana 3d5d540c0e
Merge pull request #177 from theLMGN/patch-1
bad service
2019-01-01 01:34:21 -03:00
Leo MG Nesfield (LMGN) 06c69559f6
bad service 2018-12-31 23:13:51 +00:00
Kana bb722776c2
Merge pull request #171 from PascalTemel/master
fix missing sharp import
2018-12-11 18:23:10 -03:00
Pascal Temel cb320e4354 yikes 2018-12-11 21:34:16 +01:00
Kana ea575898f3
Merge pull request #169 from PascalTemel/master
Replace graphicsmagick with sharp
2018-12-04 14:57:57 -03:00
Pascal Temel fa8ef06764 remove npm from package.json 2018-11-30 23:01:58 +01:00
Pascal Temel 05c17f2dc9 replace graphicsmagick with sharp 2018-11-30 22:58:42 +01:00
Kana ee6e9875db
Merge pull request #165 from calvinc64/patch-1
Add https://vortex.64i.de/ to the list
2018-11-21 15:38:02 -03:00
Kana 4e2eb60613
Merge pull request #167 from hyperdefined/master
URL Change
2018-11-21 15:37:49 -03:00
Kana cca2ec5ecf
Merge branch 'master' into patch-1 2018-11-21 15:37:25 -03:00
Kana 1ccc45c6ec
Merge pull request #164 from AZATEJ/patch-2
Add "criminals.host" to README
2018-11-21 15:36:19 -03:00
hyperdefined 029736fe72
my url changed 2018-11-21 09:28:01 -05:00
hyperdefined 0dd87f204f
Merge pull request #2 from WeebDev/master
updated
2018-11-21 09:27:22 -05:00
Calvin_c64 b3ec39dbe8
Update README.md 2018-11-04 00:01:14 +01:00
Calvin_c64 40fa3bd5df
Update README.md 2018-11-03 23:58:49 +01:00
TRASHER a1d5e43961
Update README.md 2018-10-24 11:29:12 +02:00
Kana 5fe57f6dd1
Merge pull request #157 from Kosemii/patch-1
Fixed my website name and description
2018-10-01 15:48:11 -03:00
Kana b105ff273c
Merge pull request #156 from dragonfire535/typos
Fix README typos
2018-10-01 15:48:04 -03:00
Kosemii ec630b2c24
Fixed my website name and description 2018-10-01 19:36:36 +01:00
Daniel Odendahl Jr 69dfaa6c27 Fix README typos 2018-10-01 14:08:48 +00:00
Kana 558abcca90
Merge pull request #154 from alphv/patch-3
subdomain change
2018-09-22 09:29:23 -03:00
Alpha 304d8c5775
subdomain change 2018-09-22 12:25:26 +02:00
Kana b18828d814
Merge pull request #151 from WeebDev/exec-blocklist
Added .exec to blacklist
2018-09-20 22:11:57 -03:00
Kayo 3e0be53c62
Added .exec to blacklist
Mac/Unix executable files.
2018-09-20 15:07:31 -07:00
Kana de7e639c75
Merge pull request #150 from alphv/patch-2
Update README.md
2018-09-19 04:57:07 -03:00
Kana dddeff0187
Update README.md 2018-09-19 04:56:58 -03:00
Alpha b353d009db
Update README.md 2018-09-19 09:52:51 +02:00
Pitu 430af8306b Switch to Nuxt.js 2018-09-19 04:45:50 -03:00
Pitu 8e1711ed6c Some adjustements to public album view 2018-09-18 04:21:48 -03:00
Pitu 04fb2218cd Return less info to the user when verifying 2018-09-18 04:21:34 -03:00
Pitu 108e5d5d2f 404エラ 2018-09-18 04:02:21 -03:00
Pitu c3fde6d5a6 Unused lines 2018-09-18 03:53:10 -03:00
Pitu 8ca6784eec Better error handling on invalid links 2018-09-18 03:52:49 -03:00
Pitu b75023114a Enable changing album options 2018-09-18 03:34:29 -03:00
Pitu 4b2b02110b We can now download albums yayyyy 2018-09-18 03:34:00 -03:00
Pitu e8bb2c5a7f Stupid hash was working, the size changes for some reason when uploading 2018-09-18 01:49:13 -03:00
Pitu 1fe6f579f9 Delete thumbs when deleting a file 2018-09-18 01:44:58 -03:00
Pitu 41cb8ff986 Add an add more files button to the uploader 2018-09-18 01:44:46 -03:00
Pitu f2c885b718 Commented all the code 2018-09-17 04:55:42 -03:00
Pitu 9f03bc6d4a Changes 2018-09-17 04:38:52 -03:00
Pitu 455ca39886 Removed unused stuff 2018-09-17 04:38:33 -03:00
Pitu c2c6e99878 Public albums wooo! 2018-09-17 04:38:25 -03:00
Pitu 46ed1c6a82 This route should handle more stuff, so it does now 2018-09-17 04:37:27 -03:00
Pitu 77dd0c70f7 We need some more config stuff for the frontend 2018-09-17 04:37:04 -03:00
Pitu 977d3c6d3a Be able to create links on the frontend 2018-09-16 17:53:48 -03:00
Pitu d777439c7b Flawed logic on the async retry 2018-09-16 17:53:26 -03:00
Pitu 7df56eb91c Switching to postgresql as the default had some implications 2018-09-16 17:53:14 -03:00
Pitu e073fb4317 Links can now be created 2018-09-16 17:52:46 -03:00
Pitu 9001133414 Links are managed elsewhere, so there's no point in this 2018-09-16 05:42:38 -03:00
Pitu 5560f69896 We really dont want to source control the uploads folder 2018-09-16 05:28:19 -03:00
Pitu 22b511cf25 Need to figure out why 404 is breaking stuff 2018-09-16 05:27:27 -03:00
Pitu 072fec199d Added logo 2018-09-16 05:27:10 -03:00
Pitu c7af18e730 Preload of config file 2018-09-16 05:27:02 -03:00
Pitu 65a5967058 Second version of startup script 2018-09-16 05:26:35 -03:00
Pitu b0e5dd4539 Styling login and register 2018-09-16 05:26:20 -03:00
Pitu 0dbc9ca7ba Forgot this file here 2018-09-16 05:25:56 -03:00
Pitu 37c7596ac3 Add imagesloaded for lazy loading 2018-09-16 05:25:46 -03:00
Pitu 44e54187c6 We dont need these 2018-09-16 05:25:34 -03:00
Pitu 04cb6dcce5 We dont need the second one, probably 2018-09-16 01:10:46 -03:00
Pitu fe10a00ba9 Site 2018-09-16 01:09:02 -03:00
Pitu 3243d85b59 First version of start script 2018-09-16 00:56:25 -03:00
Pitu e7767ac709 Routes 2018-09-16 00:56:13 -03:00
Pitu a42cf4400e Utils 2018-09-16 00:55:41 -03:00
Pitu 7268d24143 Base structures 2018-09-16 00:55:30 -03:00
Pitu 3f0bdd7a28 New base 2018-09-16 00:54:54 -03:00
Pitu 868f4a64ec Begone! 2018-09-16 00:39:58 -03:00
Kana 51e4d6182a
Merge pull request #149 from iilukas/patch-1
Add discordimages.com as a domain
2018-09-12 17:00:33 -03:00
Lukas 8427372059
Update README.md 2018-09-12 15:50:19 -04:00
Kana cc2318b22a
Merge pull request #148 from ImUrX/patch-2
Remove my domain
2018-09-12 16:33:09 -03:00
Uriel 4e7a9b9fe6
Update README.md 2018-09-06 22:07:06 -03:00
Kana bb3761c23e
Merge pull request #144 from SanchezSihaya/patch-1
Anutha one
2018-09-06 20:25:28 -03:00
Kana 0d40f21ce8
Merge pull request #143 from Kosemii/patch-1
Remove inactive clones
2018-09-06 20:25:16 -03:00
Sanchez 9a4047afa5
Anutha one 2018-08-26 19:43:11 +10:00
Kosemii 20984be41f
Remove inactive clones 2018-08-26 01:12:28 +01:00
Kana 6847cbb346
Merge pull request #142 from BigBrainAFK/patch-1
Added discordjs.moe
2018-08-10 09:26:03 -03:00
Tony Langhammer a6b0a827cf
Added discordjs.moe 2018-08-10 13:37:59 +02:00
Kana a59396aa6d
Merge pull request #140 from hyperdefined/master
safe is dead
2018-08-02 09:06:22 -03:00
hyperdefined 073f14a7c0
safe is gone 2018-08-01 17:57:33 -04:00
hyperdefined d3b13af53b
Merge pull request #1 from WeebDev/master
updated
2018-08-01 17:57:01 -04:00
Kana 7ca08ceade
Merge pull request #139 from Pyxelx/master
Update README.md
2018-07-30 14:35:42 -03:00
Brayden ea65e4b5ea
fix some spelling/grammar 2018-07-30 05:48:29 -08:00
Brayden 3b75760886
Update README.md 2018-07-28 04:39:25 -08:00
Kana 78c184562a
Merge pull request #134 from Shumatsu/master
Site list revision
2018-07-02 12:54:59 -03:00
Shumatsu aa8266087f
Removed updx 2018-07-02 17:34:52 +02:00
Shumatsu b078041503
Restored waliant.pw 2018-07-02 12:43:27 +02:00
Shumatsu 68032b62b9
Site list revision
- UPDX - uses SSL, so no need to keep http: link.
- Kayo.pics - redirects to kayo.moe
- safe.waliant.pw - entire waliant.pw is down right now.
- a.hyper.lol - added a space because I don't like when words are unnecessarily mashed together.
2018-07-02 09:39:19 +02:00
Kana 7711f0d45a
Merge pull request #132 from hyperdefined/master
Added My Site
2018-07-02 03:03:37 -03:00
hyperdefined 39b3c69bd9
xd 2018-07-02 01:59:50 -04:00
hyperdefined f4d6e4bd64
Update README.md 2018-07-02 01:59:34 -04:00
Kana 82e8e7167b
Merge pull request #130 from VVLNT/original
because why not
2018-06-15 17:22:56 -03:00
VVLNT 5ca83af4a4 because why not 2018-06-15 22:11:54 +02:00
Kana c46f5d9b31
Merge pull request #128 from cmyui/patch-1
anotha one
2018-06-03 23:29:49 -03:00
Josh 67d7bf18b3
anotha one 2018-06-03 21:39:39 -04:00
Kana 210967396e
Merge pull request #125 from ImUrX/patch-1
Add new site
2018-06-03 11:17:31 -03:00
Uriel 1bc9c1ed7f
removed wip 2018-06-01 09:01:23 -03:00
Uriel 953981f48d
Update README.md 2018-05-31 14:48:24 -03:00
Kana cbe73ed1ec
Merge pull request #122 from Shumatsu/extensions
File extension bans
2018-05-21 18:23:58 -03:00
Shumatsu a9ac6b1574
Adjusting to recommendations 2018-05-21 23:22:54 +02:00
Shumatsu df6d5459e0
File extension bans
`.nt` - turns out there's more Windows batch file extensions.
`.psm1` - PowerScript extension.
`.bash`, `.bsh`, `.csh`, `.bash_profile`, `.bashrc`, `.profile` - apparently those are valid bash script extensions.
`.reg` - overwrites Windows registry keys upon execution.
2018-05-21 23:13:39 +02:00
Kana 41fd4fb81d
Merge pull request #118 from NadyaNayme/no-thumb-deletion
Fix: File Deletion for No Thumbnail in Album View
2018-05-21 04:51:47 -03:00
Kana 3bb212cfda
Merge pull request #117 from Kosemii/patch-2
Fixed an issue with dashboard
2018-05-21 04:51:26 -03:00
Nadya 55e64b1f36 Fix: File Deletion for No Thumbnail in Album View
If a file did not have a thumbnail the delete button would not render.
2018-05-20 15:12:33 -07:00
Kosemii f28fcf4388
Fixed an issue with dashboard
Fixed an issue cutting off delete button in dashboard
2018-05-20 23:04:33 +01:00
Kana 0994355c7e
Merge pull request #113 from Kosemii/patch-1
Added site
2018-05-20 18:39:47 -03:00
Kana 7655d87066
Merge pull request #116 from NadyaNayme/fix-typo
Fixes typo of charset_Type
2018-05-20 18:31:46 -03:00
Nadya 18b5d8eed9 Fixes typo of charset_Type
unknown directive charset_type, because the directive is charset_types
2018-05-20 14:25:15 -07:00
Kosemii e0911494e6
Added site 2018-05-19 12:17:37 +01:00
Kana 0f66d74683
Merge pull request #111 from NadyaNayme/better-nginx-settings
Small nginx.config improvements
2018-05-19 05:08:25 -03:00
Nadya 30e460b7f8 Improve nginx defaults
Don't send nginx version in headers and serve all text/* as utf-8
2018-05-19 00:57:19 -07:00
Kana e07e87e061
Merge pull request #107 from NadyaNayme/Album-Delete
Album View File Deletion
2018-05-13 00:01:49 -03:00
Nadya ba6b3764dc
Fix typo 2018-05-12 19:25:19 -07:00
Nadya 8758ccac82
Merge pull request #2 from NadyaNayme/Album-Delete-Styles
Styles for Album View Delete
2018-05-12 19:20:14 -07:00
Nadya ef4e7755ae
Styles for Album View Delete 2018-05-12 19:19:38 -07:00
Nadya 92efc7bf17
Add Delete to Album View
QOL change.
2018-05-12 19:17:21 -07:00
Kana 7f488f0ac9
Merge pull request #101 from RyoshiKayo/urls
Fixed more urls
2018-04-28 06:14:23 -03:00
RyoshiKayo 94e24f3c5d Fixed more urls 2018-04-28 05:12:25 -04:00
Kana e6467ab4f1
Merge pull request #100 from RyoshiKayo/patch-4
fixed url
2018-04-28 05:47:17 -03:00
Kayo b08cc2c6bb
fixed url 2018-04-28 01:46:10 -07:00
Kana 4d883de6ac
Merge pull request #99 from RyoshiKayo/patch-3
Removed Bold style from dmca.gripe
2018-04-26 04:06:42 -03:00
Kayo c4993e5882
Removed Bold style from dmca.gripe 2018-04-25 23:57:04 -07:00
Shumatsu 44ca2dd53d De loli-safe (#77)
* Change all occurences of loli-safe to lolisafe

Saw that repository was renamed, links were changed, but a lot of files of lolisafe were unchanged. So I followed the links and changed what seemed safe to change. Which turned out to be all occurences. 

Also, bumped the copyright.

* Change year in LICENSE

This seems to be the proper format.

* Image fix

It was out of focus.

* Relative image path

Heard on StackOverflow it can be done like this.
2018-04-26 08:54:07 +02:00
Kosinus 717367320e added dmca.gripe (#96) 2018-04-26 08:51:35 +02:00
Bobby Wibowo 08637b9ea9 fixed "add to album" error on uploads (#92)
* Updates

Fixed the way error when adding album is being handled.

* I guess that sounds about right.
2018-04-26 08:51:17 +02:00
Bobby Wibowo 19e965a77a SEMICOLONS, ermahgerd (#93) 2018-04-26 08:50:35 +02:00
Bobby Wibowo 5a4bec6b00 no-useless-return (#94) 2018-04-26 08:48:56 +02:00
Bobby Wibowo fa6c33e2e9 no-mixed-spaces-and-tabs (#95)
I wonder why I can't see the diffs from my vscode 🤔
2018-04-26 08:48:44 +02:00
Kana 9d17bb284a
Whoops 2018-04-04 19:38:15 -03:00
Kana 91331e7947
Whoops 2018-04-04 19:37:53 -03:00
Kana 917cdabcb0
Merge pull request #73 from RyoshiKayo/master
Added CF-Connecting-IP
2018-04-03 10:55:55 -03:00
Kayo 5715a2d1f3
Updated README about CF support 2018-04-01 23:59:03 -07:00
Kayo 35da812a01
Mirrored nginx.sample.conf 2018-04-01 23:54:42 -07:00
Kayo d7c792fa8a
Add NGINX compile warning 2018-04-01 23:53:26 -07:00
Kayo 535f12b70a
Updated real-ip-from-cf 2018-04-01 23:52:09 -07:00
Kayo 6f7ec5d282
Updated IP's 2018-04-01 23:51:25 -07:00
Kana 40bfa143c2
Merge pull request #88 from RyoshiKayo/patch-1
Added kayo.pics
2018-03-27 23:04:02 -03:00
Kayo 46bb4079c6
Added kayo.pics
umu
2018-03-26 02:42:03 -07:00
Kayo a9d0e0a85c
Update github URL 2018-03-25 18:16:53 -07:00
Kana 0157388217
Merge pull request #85 from BobbyWibowo/pr-retry-names
Patch to allow "retries" when generating random name
2018-03-19 07:53:32 -03:00
Bobby Wibowo 5be27c129d
Uses async 2018-03-18 23:32:59 +07:00
Bobby Wibowo dcb72734fe
Patch to allow "retries" when generating random name 2018-03-18 19:21:04 +07:00
Kana 46bf0da5ee
Merge pull request #78 from Shumatsu/no-powershell
Ban of .com and .ps1 uploads
2018-03-15 03:58:40 -03:00
Unknown 56e2f3ff5c Adding .jar and .scr files
JAR - java and friends' executable.
SCR - Windows script executable, commonly used in "screenshot" trick.
2018-03-09 21:27:09 +01:00
Unknown ba8500144b Ban of .com and .ps1 uploads
It seems that COM files can still be run in Windows, and they behave like any other executable.
PS1 files are scripts, so I put them on the list.
2018-03-06 22:29:40 +01:00
Kana 496575dea0
Whoops 2018-03-05 01:40:45 -03:00
iCrawl 8a75ab91a6
update deps and add dockerfile 2018-03-01 22:03:47 +01:00
Kana 1a77649ce3
Merge pull request #75 from pyraxo/master
Add missing column in table 'users'
2018-02-22 13:22:57 -03:00
pyra b9cad8e4d5
Add missing column in table 'users' 2018-02-23 00:16:02 +08:00
Kana 939b5c52f7 In theory this will enable us to disable users and not break already running instances 2018-02-16 23:50:23 -03:00
RyoshiKayo d009c2dcf6 Typing is hard 2018-02-11 23:54:05 -05:00
RyoshiKayo 47821474a5 Added CloudFlare Supprt for WeebDev/lolisafe#70 2018-02-11 23:49:11 -05:00
Kana 48ec9d9559
Merge pull request #69 from RyoshiKayo/master
Added HTTP Version of NGINX sample configuration
2018-02-01 00:28:56 -03:00
Kana f1cc65a55e
Merge pull request #68 from BobbyWibowo/master
Added safe.fiery.me to README.md.
2018-02-01 00:28:17 -03:00
RyoshiKayo a9232b905c Added NGINX SSL Version 2018-01-27 06:24:21 +01:00
RyoshiKayo 01f1c600ed Matched ports from sample config 2018-01-27 06:19:40 +01:00
RyoshiKayo 9465cce88a Renamed original NGINX config (SSL Version) 2018-01-27 06:18:16 +01:00
RyoshiKayo 465607cd5b Added HTTP NGINX sample config 2018-01-27 06:16:21 +01:00
Bobby Wibowo 5052cd2651
Sorry.
At first I was concerned due to a particular ESLint rule called "no-undefined", but then after looking more deeply into it, I realized using typeof was unnecessary since "no-global-assign" and "no-shadow-restricted-names" were enabled and thus the previous method surely would not cause any problems.
2018-01-24 19:53:31 +07:00
Bobby Wibowo 7de25210ce
Proper undefined check 2018-01-24 19:38:32 +07:00
Bobby Wibowo 38d77fdfbb
Fix 2018-01-24 05:29:13 +07:00
Bobby Wibowo f42bd7d011
Added safe.fiery.me to README.md. 2018-01-24 01:14:48 +07:00
Kana 5db6a92334
Merge pull request #65 from vistafan12/master
added updx.xyz
2018-01-03 17:51:04 -03:00
vistafan12 e64417d0cb
added updx.xyz 2018-01-03 20:21:01 +01:00
Kana ef153d0cc1
Update README.md 2017-11-07 16:03:50 -03:00
Kana 1c3ac828ed
Merge pull request #62 from EpikPhailure/patch-2
removed extraneous space
2017-11-03 01:36:21 -03:00
EpikPhailure 4ef19ed027
removed extraneous space 2017-11-02 21:19:57 -07:00
Kana 0a4729d2bd Merge pull request #58 from ScruffyRules/sharex
Make ShareX link download a sharex file if you're logged in
2017-10-11 20:59:25 -03:00
Kana eb01ab06a2 Aligned the album download button 2017-10-10 14:56:20 -03:00
ScruffyRules 9c07dda317 Fix up formatting in the sharex_file variable 2017-10-06 17:10:06 +10:30
ScruffyRules d367bc27fa Make ShareX link download a sharex file if you're logged in 2017-10-06 16:58:39 +10:30
Kana 48883d7728 Added minimum node version 2017-10-06 02:53:12 -03:00
Pitu 3c2ba4868a ugh, little mistake 2017-10-04 15:20:07 -03:00
Pitu 759943f798 whoops 2017-10-04 15:13:23 -03:00
Pitu 76d48602c6 Async who? 2017-10-04 02:51:55 -03:00
Pitu 09a5c6bf1e Added a little bit of logging 2017-10-04 02:46:31 -03:00
Pitu 149742ab61 Bypass file if not found when zipping an album 2017-10-04 02:45:45 -03:00
231 changed files with 32416 additions and 4671 deletions

46
.dockerignore Normal file
View File

@ -0,0 +1,46 @@
# Packages
node_modules
**/node_modules
# Log files
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# IDE
.vscode
# Docker (experimental)
docker/
Dockerfile
.dockerignore
docker-compose.yml
docker-compose.config.yml
docker-compose.config.example.yml
# Tests
coverage/
jest-setup.ts
jest.config.js
# Linting
.eslingignore
.eslintrc.json
tsconfig.eslint.json
# Miscellaneous
.tmp
.vscode
.git
.gitattributes
.gitignore
README.md
chibi.ps1
chibi.sh
dist
.nuxt

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: pitu
patreon: pitu
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

27
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,27 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE REQUEST]"
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.

25
.gitignore vendored
View File

@ -1,13 +1,16 @@
.DS_Store
!.gitkeep
# Packages
node_modules/
uploads/
dist/
.nuxt/
logs/
database/db
config.js
start.json
npm-debug.log
pages/custom/**
migrate.js
yarn.lock
package-lock.json
# Chibisafe specifics
database.sqlite
uploads/
.env
!src/api/routes/uploads
db
database.sqlite-journal
docker/nginx/chibisafe.moe.conf
docker-compose.config.yml
/coverage
local/

4
.npmrc Normal file
View File

@ -0,0 +1,4 @@
audit=false
fund=false
node-version=false
legacy-peer-deps=true

50
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,50 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "[DEV] Launch API",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}\\src\\api\\structures\\Server"
},
{
"type": "node",
"request": "launch",
"name": "[DEV] Launch ThumbGen",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}\\src\\api\\utils\\generateThumbs"
},
{
"type": "chrome",
"request": "launch",
"name": "client: chrome",
"url": "http://localhost:8070",
"webRoot": "${workspaceFolder}"
},
{
"type": "node",
"request": "launch",
"name": "server: nuxt",
"args": ["dev"],
"osx": {
"program": "${workspaceFolder}/node_modules/.bin/nuxt"
},
"linux": {
"program": "${workspaceFolder}/node_modules/.bin/nuxt"
},
"windows": {
"program": "${workspaceFolder}/node_modules/nuxt/bin/nuxt.js"
}
}
],
"compounds": [
{
"name": "fullstack: nuxt",
"configurations": ["server: nuxt", "client: chrome"]
}
]
}

22
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"files.insertFinalNewline": true,
"editor.formatOnPaste": true,
"editor.formatOnSave": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[vue]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"prettier.disableLanguages": ["vue"],
"vetur.format.enable": true,
"files.eol": "\n",
"eslint.alwaysShowStatus": true,
"eslint.format.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Pitu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,65 +1,68 @@
![loli-safe](https://a.safe.moe/jcutlz.png)
<p align="center">
<img width="234" height="376" src="https://lolisafe.moe/xjoghu.png">
</p>
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/kanadeko/Kuro/master/LICENSE)
[![Chat / Support](https://img.shields.io/badge/Chat%20%2F%20Support-discord-7289DA.svg?style=flat-square)](https://discord.gg/5g6vgwn)
[![Support me](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dpitu%26type%3Dpledges&style=flat-square)](https://www.patreon.com/pitu)
[![Support me](https://img.shields.io/badge/Support-Buy%20me%20a%20coffee-yellow.svg?style=flat-square)](https://www.buymeacoffee.com/kana)
# lolisafe, a small safe worth protecting.
### Attention
If you are upgrading from `v3.x` to `v4.0.0` (lolisafe to chibisafe) and you want to keep your files and relations please read the [migration guide](docs/migrating.md). Keep in mind the migration is a best-effort script and it's recommended to start from scratch. That being said the migration does work but it's up to you to make a backup beforehand in case something goes wrong.
## What's new in v3.0.0
- Backend rewrite to make it faster, better and easier to extend
- Album downloads (Thanks to [PascalTemel](https://github.com/PascalTemel))
- See releases for changelog
`v4.0.1` changed the hashing algorithm for a better, faster one. So if you are currently running v4.0.0 and decide to update to v4.0.1+ it's in your best interest to rehash all the files your instance is serving. To do this go to the chibisafe root folder and run `node src/api/utils/rehashDatabase.js`. Depending on how many files you have it can take a few minutes or hours, there's a progress bar that will give you an idea.
If you're upgrading from a version prior to v3.0.0 make sure to run **ONCE** `node database/migration.js` to create the missing columns on the database.
## What is Chibisafe?
Chibisafe is a file uploader service written in node that aims to to be easy to use and easy to set up. It's mainly intended for images and videos, but it accepts anything you throw at it.
- You can run it in public or private mode, making it so only people with user accounts can upload files as well as controlling if user signup is enabled or not.
- Out of the box support for ShareX configuration letting you upload screenshots and screenrecordings directly to your chibisafe instance.
- Browser extension to be able to right click any image/video from any website and upload it directly to your chibisafe instance.
- Chunk uploads enabled by default to be able to handle big boi files.
- API Key support so you can integrate the service with whatever you desire.
- Albums, tags and Discord-like search function
- User list and control panel
## Running
1. Clone
2. Rename `config.sample.js` to `config.js`
4. Modify port, domain and privacy options if desired
3. run `npm install` to install all dependencies
5. run `pm2 start lolisafe.js` or `node lolisafe.js` to start the service
### Docker
If you want to deploy a docker instance instead of manually setting the service up, you can use `docker-composer` with our scripts. [Please refer to the docs here](docs/docker.md)
## Getting started
This service supports running both as public and private. The only difference is that one needs a token to upload and the other one doesn't. If you want it to be public so anyone can upload files either from the website or API, just set the option `private: false` in the `config.js` file. In case you want to run it privately, you should set `private: true`.
### Pre-requisites
This guide asumes a whole lot of things, including that you know your way around linux, nginx and internet in general.
Upon running the service for the first time, it's gonna create a user account with the username `root` and password `root`. This is your admin account and you should change the password immediately. This account will let you manage all uploaded files and remove any if necessary.
- Decently updated version of linux (we recommend Debian)
- `node` version 12.18.2+ (we recommend using [volta.sh](https://volta.sh/) or [n](https://github.com/tj/n))
- `build-essential` package installed in your system to build dependencies
- `ffmpeg` package installed
- `pm2` globally installed (`npm i -g pm2`) to keep the service alive at all times.
- Alternatively you can use tmux, forever, or whatever you are most familiar with
- `nginx` installed and running
The option `serveFilesWithNode` in the `config.js` dictates if you want lolisafe to serve the files or nginx/apache once they are uploaded. The main difference between the two is the ease of use and the chance of analytics in the future.
If you set it to `true`, the uploaded files will be located after the host like:
https://lolisafe.moe/yourFile.jpg
> Note: while Chibisafe does work on Windows, setting it up is not covered in this readme. It's up to you to install the neccessary dependencies
If you set it to `false`, you need to set nginx to directly serve whatever folder it is you are serving your
downloads in. This also gives you the ability to serve them, for example, like this:
https://files.lolisafe.moe/yourFile.jpg
### Installing
Both cases require you to type the domain where the files will be served on the `domain` key below.
Which one you use is ultimately up to you. Either way, I've provided a [sample config file for nginx](https://github.com/WeebDev/lolisafe/blob/master/nginx.sample.conf) that you can use to set it up quickly and painlessly!
1. Clone the repository and `cd` into it
2. Run `npm i`
3. Run `npm run setup`
If you set `enableUserAccounts: true`, people will be able to create accounts on the service to keep track of their uploaded files and create albums to upload stuff to, pretty much like imgur does, but only through the API. Every user account has a token that the user can use to upload stuff through the API. You can find this token on the section called `Change your token` on the administration dashboard, and if it gets leaked or compromised you can renew it by clicking the button titled `Request new token`.
Chibisafe is now installed, configured and ready. Now you need to serve it to the public by using a domain name.
## Using loli-safe
Once the service starts you can start hitting the upload endpoint at `/api/upload` with any file. If you're using the frontend to do so then you are pretty much set, but if using the API to upload make sure the form name is set to `files[]` and the form type to `multipart/form-data`. If the service is running in private mode, dont forget to send a header of type `token: YOUR-CLIENT-TOKEN` to validate the request.
4. Check the [nginx](docs/nginx.md) file for a sample configuration that has every step to run chibisafe securely on production.
A sample of the returning json from the endpoint can be seen below:
```json
{
"name": "EW7C.png",
"size": "71400",
"url": "https://i.kanacchi.moe/EW7C.png"
}
```
After you finish setting up nginx, you need to start chibisafe by using pm2. If you want to use something else like forever, ensure that the process spawned from `npm run start` never dies.
To make it easier and better than any other service, you can download [our Chrome extension](https://chrome.google.com/webstore/detail/loli-safe-uploader/enkkmplljfjppcdaancckgilmgoiofnj) that will let you configure your hostname and tokens, so that you can simply `right click` -> `send to loli-safe` to any image/audio/video file on the web.
5. Run `pm2 start pm2.json`:
6. Profit
Because of how nodejs apps work, if you want it attached to a domain name you will need to make a reverse proxy for it. Here is a tutorial [on how to do this with nginx](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04). Keep in mind that this is only a requirement if you want to access your loli-safe service by using a domain name (ex: https://i.kanacchi.moe), otherwise you can use the service just fine by accessing it from your server's IP.
## Sites using loli-safe
- [lolisafe.moe](https://lolisafe.moe): A small safe worth protecting.
- [safe.moe](https://safe.moe): The world's most ~~un~~safe pomf clone
- Feel free to add yours here.
### Screenshots
<p align="center">
<img src="https://lolisafe.moe/73up1d.png">
<img src="https://lolisafe.moe/q0uctp.png">
<img src="https://lolisafe.moe/8fi2x6.png">
</p>
## Author
**loli-safe** © [Pitu](https://github.com/Pitu), Released under the [MIT](https://github.com/WeebDev/loli-safe/blob/master/LICENSE) License.<br>
**Chibisafe** © [Pitu](https://github.com/Pitu), Released under the [MIT](https://github.com/WeebDev/chibisafe/blob/master/LICENSE) License.<br>
Authored and maintained by Pitu.
> [lolisafe.moe](https://lolisafe.moe) · GitHub [@Pitu](https://github.com/Pitu)
> [chibisafe.moe](https://chibisafe.moe) · GitHub [@Pitu](https://github.com/Pitu)

21
babel.config.js Normal file
View File

@ -0,0 +1,21 @@
function isBabelLoader(caller) {
return caller && caller.name === 'babel-loader';
}
module.exports = api => {
if (api.env('test') && !api.caller(isBabelLoader)) {
return {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current'
}
}
]
]
};
}
return {};
};

View File

@ -1,84 +0,0 @@
module.exports = {
/*
If set to true the user will need to specify the auto-generated token
on each API call, meaning random strangers wont be able to use the service
unless they have the token loli-safe provides you with.
If it's set to false, then upload will be public for anyone to use.
*/
private: true,
// If true, users will be able to create accounts and access their uploaded files
enableUserAccounts: true,
/*
Here you can decide if you want lolisafe to serve the files or if you prefer doing so via nginx.
The main difference between the two is the ease of use and the chance of analytics in the future.
If you set it to `true`, the uploaded files will be located after the host like:
https://lolisafe.moe/yourFile.jpg
If you set it to `false`, you need to set nginx to directly serve whatever folder it is you are serving your
downloads in. This also gives you the ability to serve them, for example, like this:
https://files.lolisafe.moe/yourFile.jpg
Both cases require you to type the domain where the files will be served on the `domain` key below.
Which one you use is ultimately up to you.
*/
serveFilesWithNode: false,
domain: 'https://lolisafe.moe',
// Port on which to run the server
port: 9999,
// Pages to process for the frontend
pages: ['home', 'auth', 'dashboard', 'faq'],
// Add file extensions here which should be blocked
blockedExtensions: [
'.exe',
'.bat',
'.cmd',
'.msi',
'.sh'
],
// Uploads config
uploads: {
// Folder where images should be stored
folder: 'uploads',
/*
Max file size allowed. Needs to be in MB
Note: When maxSize is greater than 1 MiB, you must set the client_max_body_size to the same as maxSize.
*/
maxSize: '512MB',
// The length of the random generated name for the uploaded files
fileLength: 32,
/*
NOTE: Thumbnails are only for the admin panel and they require you
to install a separate binary called graphicsmagick (http://www.graphicsmagick.org)
for images and ffmpeg (https://ffmpeg.org/) for video files
*/
generateThumbnails: false,
/*
Allows users to download a .zip file of all files in an album.
The file is generated when the user clicks the download button in the view
and is re-used if the album has not changed between download requests
*/
generateZips: true
},
// Folder where to store logs
logsFolder: 'logs',
// The following values shouldn't be touched
database: {
client: 'sqlite3',
connection: { filename: './database/db' },
useNullAsDefault: true
}
}

View File

@ -1,173 +0,0 @@
const config = require('../config.js');
const db = require('knex')(config.database);
const randomstring = require('randomstring');
const utils = require('./utilsController.js');
const path = require('path');
const fs = require('fs');
const Zip = require('jszip');
const albumsController = {};
albumsController.list = async (req, res, next) => {
const user = await utils.authorize(req, res);
const fields = ['id', 'name'];
if (req.params.sidebar === undefined) {
fields.push('timestamp');
fields.push('identifier');
}
const albums = await db.table('albums').select(fields).where({ enabled: 1, userid: user.id });
if (req.params.sidebar !== undefined) {
return res.json({ success: true, albums });
}
let ids = [];
for (let album of albums) {
album.date = new Date(album.timestamp * 1000)
album.date = utils.getPrettyDate(album.date)
album.identifier = `${config.domain}/a/${album.identifier}`;
ids.push(album.id);
}
const files = await db.table('files').whereIn('albumid', ids).select('albumid');
const albumsCount = {};
for (let id of ids) albumsCount[id] = 0;
for (let file of files) albumsCount[file.albumid] += 1;
for (let album of albums) album.files = albumsCount[album.id];
return res.json({ success: true, albums });
};
albumsController.create = async (req, res, next) => {
const user = await utils.authorize(req, res);
const name = req.body.name;
if (name === undefined || name === '') {
return res.json({ success: false, description: 'No album name specified' });
}
const album = await db.table('albums').where({
name: name,
enabled: 1,
userid: user.id
}).first();
if (album) {
return res.json({ success: false, description: 'There\'s already an album with that name' })
}
await db.table('albums').insert({
name: name,
enabled: 1,
userid: user.id,
identifier: randomstring.generate(8),
timestamp: Math.floor(Date.now() / 1000)
});
return res.json({ success: true });
};
albumsController.delete = async (req, res, next) => {
const user = await utils.authorize(req, res);
const id = req.body.id;
if (id === undefined || id === '') {
return res.json({ success: false, description: 'No album specified' });
}
await db.table('albums').where({ id: id, userid: user.id }).update({ enabled: 0 });
return res.json({ success: true });
};
albumsController.rename = async (req, res, next) => {
const user = await utils.authorize(req, res);
const id = req.body.id;
if (id === undefined || id === '') {
return res.json({ success: false, description: 'No album specified' });
}
const name = req.body.name;
if (name === undefined || name === '') {
return res.json({ success: false, description: 'No name specified' });
}
const album = await db.table('albums').where({ name: name, userid: user.id }).first();
if (album) {
return res.json({ success: false, description: 'Name already in use' })
}
await db.table('albums').where({ id: id, userid: user.id }).update({ name: name })
return res.json({ success: true });
};
albumsController.get = async (req, res, next) => {
const identifier = req.params.identifier;
if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' });
const album = await db.table('albums').where({ identifier, enabled: 1 }).first();
if (!album) return res.json({ success: false, description: 'Album not found' });
const title = album.name;
const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC');
for (let file of files) {
file.file = `${config.domain}/${file.name}`;
const ext = path.extname(file.name).toLowerCase();
if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) {
file.thumb = `${config.domain}/thumbs/${file.name.slice(0, -ext.length)}.png`;
}
}
return res.json({
success: true,
title: title,
count: files.length,
files
});
};
albumsController.generateZip = async (req, res, next) => {
const identifier = req.params.identifier;
if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' });
if (!config.uploads.generateZips) return res.status(401).json({ success: false, description: 'Zip generation disabled' });
const album = await db.table('albums').where({ identifier, enabled: 1 }).first();
if (!album) return res.json({ success: false, description: 'Album not found' });
if (album.zipGeneratedAt > album.editedAt) {
const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`);
const fileName = `${album.name}.zip`;
return res.download(filePath, fileName);
} else {
console.log(`Generating zip for album identifier: ${identifier}`);
const files = await db.table('files').select('name').where('albumid', album.id);
if (files.length === 0) return res.json({ success: false, description: 'There are no files in the album' });
const zipPath = path.join(__dirname, '..', config.uploads.folder, 'zips', `${album.identifier}.zip`);
let archive = new Zip();
for (let file of files) {
archive.file(file.name, fs.readFileSync(path.join(__dirname, '..', config.uploads.folder, file.name)));
}
archive
.generateNodeStream({ type: 'nodebuffer', streamFiles: true })
.pipe(fs.createWriteStream(zipPath))
.on('finish', async () => {
await db.table('albums')
.where('id', album.id)
.update({ zipGeneratedAt: Math.floor(Date.now() / 1000) });
const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`);
const fileName = `${album.name}.zip`;
return res.download(filePath, fileName);
});
}
};
module.exports = albumsController;

View File

@ -1,86 +0,0 @@
const config = require('../config.js');
const db = require('knex')(config.database);
const bcrypt = require('bcrypt');
const randomstring = require('randomstring');
const utils = require('./utilsController.js');
let authController = {};
authController.verify = async (req, res, next) => {
const username = req.body.username;
const password = req.body.password;
if (username === undefined) return res.json({ success: false, description: 'No username provided' });
if (password === undefined) return res.json({ success: false, description: 'No password provided' });
const user = await db.table('users').where('username', username).first();
if (!user) return res.json({ success: false, description: 'Username doesn\'t exist' });
bcrypt.compare(password, user.password, (err, result) => {
if (err) {
console.log(err);
return res.json({ success: false, description: 'There was an error' });
}
if (result === false) return res.json({ success: false, description: 'Wrong password' });
return res.json({ success: true, token: user.token });
});
};
authController.register = async (req, res, next) => {
if (config.enableUserAccounts === false) {
return res.json({ success: false, description: 'Register is disabled at the moment' });
}
const username = req.body.username;
const password = req.body.password;
if (username === undefined) return res.json({ success: false, description: 'No username provided' });
if (password === undefined) return res.json({ success: false, description: 'No password provided' });
if (username.length < 4 || username.length > 32) {
return res.json({ success: false, description: 'Username must have 4-32 characters' })
}
if (password.length < 6 || password.length > 64) {
return res.json({ success: false, description: 'Password must have 6-64 characters' })
}
const user = await db.table('users').where('username', username).first();
if (user) return res.json({ success: false, description: 'Username already exists' });
bcrypt.hash(password, 10, async (err, hash) => {
if (err) {
console.log(err);
return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' });
}
const token = randomstring.generate(64);
await db.table('users').insert({
username: username,
password: hash,
token: token
});
return res.json({ success: true, token: token })
});
};
authController.changePassword = async (req, res, next) => {
const user = await utils.authorize(req, res);
let password = req.body.password;
if (password === undefined) return res.json({ success: false, description: 'No password provided' });
if (password.length < 6 || password.length > 64) {
return res.json({ success: false, description: 'Password must have 6-64 characters' });
}
bcrypt.hash(password, 10, async (err, hash) => {
if (err) {
console.log(err);
return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' });
}
await db.table('users').where('id', user.id).update({ password: hash });
return res.json({ success: true });
});
};
module.exports = authController;

View File

@ -1,34 +0,0 @@
const config = require('../config.js');
const db = require('knex')(config.database);
const randomstring = require('randomstring');
const utils = require('./utilsController.js');
const tokenController = {};
tokenController.verify = async (req, res, next) => {
const token = req.body.token;
if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' });
const user = await db.table('users').where('token', token).first();
if (!user) return res.status(401).json({ success: false, description: 'Invalid token' });
return res.json({ success: true, username: user.username });
};
tokenController.list = async (req, res, next) => {
const user = await utils.authorize(req, res);
return res.json({ success: true, token: user.token });
};
tokenController.change = async (req, res, next) => {
const user = await utils.authorize(req, res);
const newtoken = randomstring.generate(64);
await db.table('users').where('token', user.token).update({
token: newtoken,
timestamp: Math.floor(Date.now() / 1000)
});
res.json({ success: true, token: newtoken });
};
module.exports = tokenController;

View File

@ -1,285 +0,0 @@
const config = require('../config.js');
const path = require('path');
const multer = require('multer');
const randomstring = require('randomstring');
const db = require('knex')(config.database);
const crypto = require('crypto');
const fs = require('fs');
const utils = require('./utilsController.js');
const uploadsController = {};
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, path.join(__dirname, '..', config.uploads.folder));
},
filename: function(req, file, cb) {
cb(null, randomstring.generate(config.uploads.fileLength) + path.extname(file.originalname));
}
});
const upload = multer({
storage: storage,
limits: { fileSize: config.uploads.maxSize },
fileFilter: function(req, file, cb) {
if (config.blockedExtensions !== undefined) {
if (config.blockedExtensions.some(extension => path.extname(file.originalname).toLowerCase() === extension)) {
return cb('This file extension is not allowed');
}
return cb(null, true);
}
return cb(null, true);
}
}).array('files[]');
uploadsController.upload = async (req, res, next) => {
if (config.private === true) {
await utils.authorize(req, res);
}
const token = req.headers.token || '';
const user = await db.table('users').where('token', token).first();
const albumid = req.headers.albumid || req.params.albumid;
if (albumid && user) {
const album = await db.table('albums').where({ id: albumid, userid: user.id }).first();
if (!album) {
return res.json({
success: false,
description: 'Album doesn\'t exist or it doesn\'t belong to the user'
});
}
return uploadsController.actuallyUpload(req, res, user.id, albumid);
}
return uploadsController.actuallyUpload(req, res, user.id, albumid);
};
uploadsController.actuallyUpload = async (req, res, userid, album) => {
upload(req, res, async err => {
if (err) {
console.error(err);
return res.json({ success: false, description: err });
}
if (req.files.length === 0) return res.json({ success: false, description: 'no-files' });
const files = [];
const existingFiles = [];
let iteration = 1;
req.files.forEach(async file => {
// Check if the file exists by checking hash and size
let hash = crypto.createHash('md5');
let stream = fs.createReadStream(path.join(__dirname, '..', config.uploads.folder, file.filename));
stream.on('data', data => {
hash.update(data, 'utf8');
});
stream.on('end', async () => {
const fileHash = hash.digest('hex');
const dbFile = await db.table('files')
.where(function() {
if (userid === undefined) this.whereNull('userid');
else this.where('userid', userid);
})
.where({
hash: fileHash,
size: file.size
})
.first();
if (!dbFile) {
files.push({
name: file.filename,
original: file.originalname,
type: file.mimetype,
size: file.size,
hash: fileHash,
ip: req.ip,
albumid: album,
userid: userid,
timestamp: Math.floor(Date.now() / 1000)
});
} else {
uploadsController.deleteFile(file.filename).then(() => {}).catch(err => console.error(err));
existingFiles.push(dbFile);
}
if (iteration === req.files.length) {
return uploadsController.processFilesForDisplay(req, res, files, existingFiles);
}
iteration++;
});
});
});
};
uploadsController.processFilesForDisplay = async (req, res, files, existingFiles) => {
let basedomain = config.domain;
if (files.length === 0) {
return res.json({
success: true,
files: existingFiles.map(file => {
return {
name: file.name,
size: file.size,
url: `${basedomain}/${file.name}`
};
})
});
}
await db.table('files').insert(files);
for (let efile of existingFiles) files.push(efile);
res.json({
success: true,
files: files.map(file => {
return {
name: file.name,
size: file.size,
url: `${basedomain}/${file.name}`
};
})
});
for (let file of files) {
let ext = path.extname(file.name).toLowerCase();
if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) {
file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`;
utils.generateThumbs(file);
}
if (file.albumid) {
db.table('albums').where('id', file.albumid).update('editedAt', file.timestamp).then(() => {})
.catch(error => { console.log(error); res.json({ success: false, description: 'Error updating album' }); });
}
}
};
uploadsController.delete = async (req, res) => {
const user = await utils.authorize(req, res);
const id = req.body.id;
if (id === undefined || id === '') {
return res.json({ success: false, description: 'No file specified' });
}
const file = await db.table('files')
.where('id', id)
.where(function() {
if (user.username !== 'root') {
this.where('userid', user.id);
}
})
.first();
try {
await uploadsController.deleteFile(file.name);
await db.table('files').where('id', id).del();
if (file.albumid) {
await db.table('albums').where('id', file.albumid).update('editedAt', Math.floor(Date.now() / 1000));
}
} catch (err) {
console.log(err);
}
return res.json({ success: true });
};
uploadsController.deleteFile = function(file) {
const ext = path.extname(file).toLowerCase();
return new Promise((resolve, reject) => {
fs.stat(path.join(__dirname, '..', config.uploads.folder, file), (err, stats) => {
if (err) { return reject(err); }
fs.unlink(path.join(__dirname, '..', config.uploads.folder, file), err => {
if (err) { return reject(err); }
if (!utils.imageExtensions.includes(ext) && !utils.videoExtensions.includes(ext)) {
return resolve();
}
file = file.substr(0, file.lastIndexOf('.')) + '.png';
fs.stat(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), (err, stats) => {
if (err) {
console.log(err);
return resolve();
}
fs.unlink(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), err => {
if (err) { return reject(err); }
return resolve();
});
});
});
});
});
};
uploadsController.list = async (req, res) => {
const user = await utils.authorize(req, res);
let offset = req.params.page;
if (offset === undefined) offset = 0;
const files = await db.table('files')
.where(function() {
if (req.params.id === undefined) this.where('id', '<>', '');
else this.where('albumid', req.params.id);
})
.where(function() {
if (user.username !== 'root') this.where('userid', user.id);
})
.orderBy('id', 'DESC')
.limit(25)
.offset(25 * offset)
.select('id', 'albumid', 'timestamp', 'name', 'userid');
const albums = await db.table('albums');
let basedomain = config.domain;
let userids = [];
for (let file of files) {
file.file = `${basedomain}/${file.name}`;
file.date = new Date(file.timestamp * 1000);
file.date = utils.getPrettyDate(file.date);
file.album = '';
if (file.albumid !== undefined) {
for (let album of albums) {
if (file.albumid === album.id) {
file.album = album.name;
}
}
}
// Only push usernames if we are root
if (user.username === 'root') {
if (file.userid !== undefined && file.userid !== null && file.userid !== '') {
userids.push(file.userid);
}
}
let ext = path.extname(file.name).toLowerCase();
if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) {
file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`;
}
}
// If we are a normal user, send response
if (user.username !== 'root') return res.json({ success: true, files });
// If we are root but there are no uploads attached to a user, send response
if (userids.length === 0) return res.json({ success: true, files });
const users = await db.table('users').whereIn('id', userids);
for (let dbUser of users) {
for (let file of files) {
if (file.userid === dbUser.id) {
file.username = dbUser.username;
}
}
}
return res.json({ success: true, files });
};
module.exports = uploadsController;

View File

@ -1,67 +0,0 @@
const path = require('path');
const config = require('../config.js');
const fs = require('fs');
const gm = require('gm');
const ffmpeg = require('fluent-ffmpeg');
const db = require('knex')(config.database);
const utilsController = {};
utilsController.imageExtensions = ['.jpg', '.jpeg', '.bmp', '.gif', '.png'];
utilsController.videoExtensions = ['.webm', '.mp4', '.wmv', '.avi', '.mov'];
utilsController.getPrettyDate = function(date) {
return date.getFullYear() + '-'
+ (date.getMonth() + 1) + '-'
+ date.getDate() + ' '
+ (date.getHours() < 10 ? '0' : '')
+ date.getHours() + ':'
+ (date.getMinutes() < 10 ? '0' : '')
+ date.getMinutes() + ':'
+ (date.getSeconds() < 10 ? '0' : '')
+ date.getSeconds();
}
utilsController.authorize = async (req, res) => {
const token = req.headers.token;
if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' });
const user = await db.table('users').where('token', token).first();
if (!user) return res.status(401).json({ success: false, description: 'Invalid token' });
return user;
};
utilsController.generateThumbs = function(file, basedomain) {
if (config.uploads.generateThumbnails !== true) return;
const ext = path.extname(file.name).toLowerCase();
let thumbname = path.join(__dirname, '..', config.uploads.folder, 'thumbs', file.name.slice(0, -ext.length) + '.png');
fs.access(thumbname, err => {
if (err && err.code === 'ENOENT') {
if (utilsController.videoExtensions.includes(ext)) {
ffmpeg(path.join(__dirname, '..', config.uploads.folder, file.name))
.thumbnail({
timestamps: [0],
filename: '%b.png',
folder: path.join(__dirname, '..', config.uploads.folder, 'thumbs'),
size: '200x?'
})
.on('error', error => console.log('Error - ', error.message));
} else {
let size = {
width: 200,
height: 200
};
gm(path.join(__dirname, '..', config.uploads.folder, file.name))
.resize(size.width, size.height + '>')
.gravity('Center')
.extent(size.width, size.height)
.background('transparent')
.write(thumbname, error => {
if (error) console.log('Error - ', error);
});
}
}
});
};
module.exports = utilsController;

View File

@ -1,50 +0,0 @@
let init = function(db){
// Create the tables we need to store galleries and files
db.schema.createTableIfNotExists('albums', function (table) {
table.increments()
table.integer('userid')
table.string('name')
table.string('identifier')
table.integer('enabled')
table.integer('timestamp')
}).then(() => {})
db.schema.createTableIfNotExists('files', function (table) {
table.increments()
table.integer('userid')
table.string('name')
table.string('original')
table.string('type')
table.string('size')
table.string('hash')
table.string('ip')
table.integer('albumid')
table.integer('timestamp')
}).then(() => {})
db.schema.createTableIfNotExists('users', function (table) {
table.increments()
table.string('username')
table.string('password')
table.string('token')
table.integer('timestamp')
}).then(() => {
db.table('users').where({username: 'root'}).then((user) => {
if(user.length > 0) return
require('bcrypt').hash('root', 10, function(err, hash) {
if(err) console.error('Error generating password hash for root')
db.table('users').insert({
username: 'root',
password: hash,
token: require('randomstring').generate(64),
timestamp: Math.floor(Date.now() / 1000)
}).then(() => {})
})
})
})
}
module.exports = init

View File

@ -1,13 +0,0 @@
const config = require('../config.js');
const db = require('knex')(config.database);
const migration = {};
migration.start = async () => {
await db.schema.table('albums', table => {
table.dateTime('editedAt');
table.dateTime('zipGeneratedAt');
});
console.log('Migration finished! Now start lolisafe normally');
};
migration.start();

3
docker/chibisafe.ps1 Normal file
View File

@ -0,0 +1,3 @@
$env = $args[0]
$cmd = $args | Select-Object -Skip 1
docker-compose -f docker-compose.yml -f docker-compose.$env.yml -f docker-compose.config.yml $cmd

2
docker/chibisafe.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
docker-compose -f docker-compose.yml -f docker-compose.$1.yml -f docker-compose.config.yml ${@%$1}

View File

@ -0,0 +1,20 @@
FROM jrottenberg/ffmpeg:4.3-alpine312 as ffmpeg
FROM node:14-alpine
WORKDIR /usr/chibisafe
COPY package.json package-lock.json ./
RUN apk add --update \
&& apk add --no-cache ca-certificates libwebp libwebp-tools expat \
&& apk add --no-cache vidstab-dev --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community \
&& apk add --no-cache --virtual .build-deps git curl build-base python3 g++ make \
&& npm ci \
&& apk del .build-deps
COPY --from=ffmpeg /usr/local /usr/local
COPY . .
RUN mkdir uploads && mkdir database
CMD ["sh", "-c", "npm run migrate && npm run seed && npm start"]

View File

@ -0,0 +1,35 @@
version: "3.7"
services:
chibisafe:
environment:
CHUNK_SIZE: 90
SECRET: "wowfcgMHqZHwOIMLadWrKu3liyqPOOILpDLSDvuxq3YGhJmiZXJCVpnF96l11WfR"
ADMIN_ACCOUNT: "admin"
ADMIN_PASSWORD: "admin"
# OVERWRITE_SETTINGS: 'false'
# ROUTE_PREFIX: /api
# RATE_LIMIT_WINDOW: 2
# RATE_LIMIT_MAX: 5
# BLOCKED_EXTENSIONS: '.jar,.exe,.msi,.com,.bat,.cmd,.scr,.ps1,.sh'
# META_THEME_COLOR: '#20222b'
# META_DESCRIPTION: 'Blazing fast file uploader and bunker written in node! 🚀'
# META_KEYWORDS: 'chibisafe,upload,uploader,file,vue,images,ssr,file uploader,free'
# META_TWITTER_HANDLE: ''
# SERVER_PORT: 5000
# DOMAIN: 'http://chibisafe.moe'
# SERVICE_NAME: chibisafe
# MAX_SIZE: 5000
# GENERATE_THUMBNAILS: 'true'
# GENERATE_ZIPS: 'true'
# STRIP_EXIF: 'true'
# SERVE_WITH_NODE: 'true'
# GENERATED_FILENAME_LENGTH: 6
# GENERATED_ALBUM_LENGTH: 4
# PUBLIC_MODE: 'false'
# USER_ACCOUNTS: 'true'
# DB_CLIENT: 'sqlite3'
# DB_HOST: ''
# DB_USER: ''
# DB_PASSWORD: ''
# DB_DATABASE: ''

View File

@ -0,0 +1,15 @@
version: "3.7"
services:
chibisafe:
volumes:
- chibisafe-data:/usr/chibisafe/uploads
- chibisafe-database:/usr/chibisafe/database
volumes:
nginx-data:
name: "nginx-data"
chibisafe-data:
name: "chibisafe-data"
chibisafe-database:
name: "chibisafe-database"

View File

@ -0,0 +1,7 @@
version: "3.7"
services:
chibisafe:
volumes:
- ./chibisafe-data:/usr/chibisafe/uploads
- ./chibisafe-database:/usr/chibisafe/database

59
docker/docker-compose.yml Normal file
View File

@ -0,0 +1,59 @@
version: "3.7"
services:
nginx:
build:
context: ./nginx
dockerfile: Dockerfile
expose:
- "80"
- "443"
ports:
- "80:80"
- "443:443"
restart: unless-stopped
healthcheck:
test: ["CMD", "service", "nginx", "status"]
interval: 60s
timeout: 5s
chibisafe:
build:
context: ../
dockerfile: ./docker/chibisafe/Dockerfile
expose:
- "5000"
- "5001"
restart: unless-stopped
environment:
OVERWRITE_SETTINGS: "false"
CHUNK_SIZE: 90
ROUTE_PREFIX: /api
RATE_LIMIT_WINDOW: 2
RATE_LIMIT_MAX: 5
BLOCKED_EXTENSIONS: ".jar,.exe,.msi,.com,.bat,.cmd,.scr,.ps1,.sh"
SECRET: ""
MAX_LINKS_PER_ALBUM: 5
META_THEME_COLOR: "#20222b"
META_DESCRIPTION: "Blazing fast file uploader and bunker written in node! 🚀"
META_KEYWORDS: "chibisafe,upload,uploader,file,vue,images,ssr,file uploader,free"
META_TWITTER_HANDLE: ""
SERVER_PORT: 5000
DOMAIN: "http://localhost:5000"
SERVICE_NAME: chibisafe
MAX_SIZE: 5000
GENERATE_THUMBNAILS: "true"
GENERATE_ZIPS: "true"
STRIP_EXIF: "true"
SERVE_WITH_NODE: "true"
GENERATED_FILENAME_LENGTH: 6
GENERATED_ALBUM_LENGTH: 4
PUBLIC_MODE: "false"
USER_ACCOUNTS: "true"
ADMIN_ACCOUNT: ""
ADMIN_PASSWORD: ""
DB_CLIENT: "sqlite3"
DB_HOST: ""
DB_USER: ""
DB_PASSWORD: ""
DB_DATABASE: ""

6
docker/nginx/Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM nginx
COPY nginxconfig.io /etc/nginx/nginxconfig.io
COPY nginx.conf /etc/nginx/nginx.conf
COPY chibisafe.moe.conf /etc/nginx/conf.d/chibisafe.moe.conf
COPY ssl /etc/nginx/ssl

View File

@ -0,0 +1,21 @@
server {
listen 80;
listen [::]:80;
server_name chibisafe.moe;
# security
include nginxconfig.io/security.conf;
# logging
access_log /var/log/nginx/chibisafe.moe.access.log;
error_log /var/log/nginx/chibisafe.moe.error.log warn;
# reverse proxy
location / {
proxy_pass http://chibisafe:5000;
include nginxconfig.io/proxy.conf;
}
# additional config
include nginxconfig.io/general.conf;
}

View File

@ -0,0 +1,32 @@
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name chibisafe.moe;
# SSL
ssl_certificate /etc/nginx/ssl/chibisafe.moe.crt;
ssl_certificate_key /etc/nginx/ssl/chibisafe.moe.key;
# security
include nginxconfig.io/security.conf;
# logging
access_log /var/log/nginx/chibisafe.moe.access.log;
error_log /var/log/nginx/chibisafe.moe.error.log warn;
# reverse proxy
location / {
proxy_pass http://chibisafe:5000;
include nginxconfig.io/proxy.conf;
}
# additional config
include nginxconfig.io/general.conf;
}
# HTTP redirect
server {
listen 80;
listen [::]:80;
return 301 https://$server_name$request_uri;
}

56
docker/nginx/nginx.conf Normal file
View File

@ -0,0 +1,56 @@
# Generated by nginxconfig.io
# https://www.digitalocean.com/community/tools/nginx?domains.0.server.domain=tourneys.naval-base.com&domains.0.server.documentRoot=&domains.0.https.certType=custom&domains.0.php.php=false&domains.0.reverseProxy.reverseProxy=true&domains.0.reverseProxy.proxyPass=http%3A%2F%2F127.0.0.1%3A3001&domains.0.routing.root=false&domains.0.logging.accessLog=true&domains.0.logging.errorLog=true
user www-data;
pid /run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 65535;
events {
multi_accept on;
worker_connections 65535;
}
http {
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
log_not_found off;
types_hash_max_size 2048;
# MIME
include mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# SSL
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# Diffie-Hellman parameter for DHE ciphersuites
# ssl_dhparam /etc/nginx/dhparam.pem;
# Mozilla Intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
# OCSP Stapling
ssl_stapling off;
ssl_stapling_verify off;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
resolver_timeout 2s;
# Upload size limit
client_max_body_size 100M;
client_body_timeout 600s;
# Load configs
include /etc/nginx/conf.d/*.conf;
# include /etc/nginx/sites-enabled/*;
}

View File

@ -0,0 +1,18 @@
# favicon.ico
location = /favicon.ico {
log_not_found off;
access_log off;
}
# robots.txt
location = /robots.txt {
log_not_found off;
access_log off;
}
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;

View File

@ -0,0 +1,18 @@
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
# Proxy headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-NginX-Proxy true;
# Proxy timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

View File

@ -0,0 +1,12 @@
# security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline' 'unsafe-eval'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# . files
location ~ /\.(?!well-known) {
deny all;
}

View File

23
docs/docker.md Normal file
View File

@ -0,0 +1,23 @@
### Using Docker
If you want to avoid all the hassle of installing the dependencies, configuring nginx and so on you can try our docker image which makes things a bit simpler.
First make sure you have docker and docker composer installed, so please follow the install instructions for your OS/Distro:
- https://docs.docker.com/engine/install/debian/
- https://docs.docker.com/compose/install/
After that:
- Copy the config file called `docker-compose.config.example.yml` and name it `docker-compose.config.yml` with the values you want. Those that are left commented will use the default values.
- Copy either `chibisafe.moe.http.example.conf` or `chibisafe.moe.https.example.conf` and name it `chibisafe.moe.conf` for either HTTP or HTTPS
- - If using HTTPS make sure to put your certificates into the `ssl` folder and name them accordingly:
- - - `chibisafe.moe.crt` for the certificate
- - - `chibisafe.moe.key` for the certificate key
Once you are done run the following commands:
- `cd docker`
- `./chibisafe prod pull`
- `./chibisafe prod build`
- `./chibisafe prod up -d`
Congrats, your chibisafe instance is now running.

18
docs/migrating.md Normal file
View File

@ -0,0 +1,18 @@
### Migrate from v3 to v4
This version introduces a few breaking changes and updating requires some manual work.
For starters we recommend cloning the new version somewhere else instead of `git pull` on your v3 version.
- After cloning move your `uploads` folder from the v3 folder to the new v4 folder.
- Then copy your `database/db` file from your v3 folder to the root of your v4 folder.
- Make sure to install the dependencies by running `npm i`
- You then need to run `npm run setup` from the v4 folder and finish the setup process.
- Once that's done you need to manually run `node src/api/scripts/databaseMigration.js` from the root folder of v4.
- This will migrate the v3 database to v4 and regenerate every single thumbnail in webp to save bandwidth.
- After the migration finishes, the last step is to update your nginx config with the [newly provided script](./nginx.md).
- Restart nginx with `sudo nginx -s reload`.
- And lastly start your chibisafe instance with `pm2 start pm2.json`.
### Breaking changes
- If you are using the chibisafe extension from one of the stores, the new version has been submitted already. You can also load the unpacked extension by cloning [this repo](https://github.com/WeebDev/chibisafe-extension).
- The chibisafe browser extension needs your new token. Instead of pasting your jwt token into it like before, you need to log in to chibisafe, go to your user settings and generate an `API KEY`, which you will use to access the service from 3rd party apps like the browser extension, ShareX, etc.
- To upload a file to an album directly users used to use the endpoint `/api/upload/${albumId}`. This is no longer the case. To upload directly to an album now it's necessary to pass a header called `albumid` with an integer as the value of the album to which you want to upload the file to.

66
docs/nginx.md Normal file
View File

@ -0,0 +1,66 @@
### Nginx config for SSL
Make sure that:
- `backend` port matches your wizard config
- `client_max_body_size` matches your wizard config
- You replace `your.domain` where pertinent
```nginx
upstream backend {
server 127.0.0.1:5000;
}
server {
listen 80;
listen [::]:80;
server_name your.domain;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your.domain;
ssl_certificate /path/to/certificate.pem;
ssl_certificate_key /path/to/certificate.key;
ssl_trusted_certificate /path/to/certificate.pem;
access_log /var/log/nginx/your.domain.access.log;
error_log /var/log/nginx/your.domain.error.log;
# Security
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-
GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SH
A:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM
-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;
add_header X-XSS-Protection "1; mode=block";
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
client_max_body_size 100M;
client_body_timeout 600s;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://backend;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```

9
docs/sites.md Normal file
View File

@ -0,0 +1,9 @@
### Sites using chibisafe
As you are aware by now chibisafe is a project meant to be self-hosted and so we've compiled the list below of sites using it. Some of them are open for registration and some might even offer anonymous uploads.
- [chibisafe.moe](https://chibisafe.moe): A small safe worth protecting.
- [dmca.gripe](https://dmca.gripe): a dmca-resistant, permanent file hosting service. *(v3)*
- [safe.succmy.wang](https://safe.succmy.wang): A private clone with a ~~funny~~ bad name
- [discordjs.moe](https://discordjs.moe): A まじ卍 as fuck copy of lolisafe.moe
- Feel free to make a PR to add your site here.

30
docs/systemd.md Normal file
View File

@ -0,0 +1,30 @@
### Service config for systemd
If you want to keep Chibisafe running by using systemd you can copy the example code shown below and create the file `/etc/systemd/system/chibisafe.service` with it.
You will need to edit the parameters:
- `User` to be the username/uid of your chibisafe instance
- `WorkingDirectory` to the **FULL** path to your chibisafe, `/home/chibisafe/chibisafe` for example.
- `EnvironmentFile` the same as the above, with the addition of `/.env`, `/home/chibisafe/chibisafe/.env`
### If you are using n/nvm you will also need to update the path to npm in `ExecStart`
- For n this will likely be `/home/username/n/bin/npm`
- You can also find this by running `whereis npm` in your terminal and copy the path from the output.
Example below.
```
[Unit]
Description=chibisafe, easy to use file uploader
After=network.target
[Service]
Type=simple
User=chibisafe
WorkingDirectory=/home/chibisafe/chibisafe
EnvironmentFile=/home/chibisafe/chibisafe/.env
ExecStart=/usr/bin/npm run start
Restart=always
[Install]
WantedBy=multi-user.target
```

9
jest.config.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
moduleFileExtensions: ['js', 'json', 'vue'],
moduleDirectories: ['node_modules'],
transform: {
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
'.*\\.(vue)$': '<rootDir>/node_modules/vue-jest'
},
transformIgnorePatterns: ['/node_modules/(?!vue)']
};

23
knexfile.js Normal file
View File

@ -0,0 +1,23 @@
require('dotenv').config();
module.exports = {
client: process.env.DB_CLIENT,
connection: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
filename: 'database/database.sqlite'
},
pool: {
min: process.env.DATABASE_POOL_MIN || 2,
max: process.env.DATABASE_POOL_MAX || 10
},
migrations: {
directory: 'src/api/database/migrations'
},
seeds: {
directory: 'src/api/database/seeds'
},
useNullAsDefault: process.env.DB_CLIENT === 'sqlite3'
};

View File

@ -1,58 +0,0 @@
const config = require('./config.js');
const api = require('./routes/api.js');
const album = require('./routes/album.js');
const express = require('express');
const helmet = require('helmet');
const bodyParser = require('body-parser');
const RateLimit = require('express-rate-limit');
const db = require('knex')(config.database);
const fs = require('fs');
const exphbs = require('express-handlebars');
const safe = express();
require('./database/db.js')(db);
fs.existsSync('./pages/custom' ) || fs.mkdirSync('./pages/custom');
fs.existsSync('./' + config.logsFolder) || fs.mkdirSync('./' + config.logsFolder);
fs.existsSync('./' + config.uploads.folder) || fs.mkdirSync('./' + config.uploads.folder);
fs.existsSync('./' + config.uploads.folder + '/thumbs') || fs.mkdirSync('./' + config.uploads.folder + '/thumbs');
fs.existsSync('./' + config.uploads.folder + '/zips') || fs.mkdirSync('./' + config.uploads.folder + '/zips')
safe.use(helmet());
safe.set('trust proxy', 1);
safe.engine('handlebars', exphbs({ defaultLayout: 'main' }));
safe.set('view engine', 'handlebars');
safe.enable('view cache');
let limiter = new RateLimit({ windowMs: 5000, max: 2 });
safe.use('/api/login/', limiter);
safe.use('/api/register/', limiter);
safe.use(bodyParser.urlencoded({ extended: true }));
safe.use(bodyParser.json());
if (config.serveFilesWithNode) {
safe.use('/', express.static(config.uploads.folder));
}
safe.use('/', express.static('./public'));
safe.use('/', album);
safe.use('/api', api);
for (let page of config.pages) {
let root = './pages/';
if (fs.existsSync(`./pages/custom/${page}.html`)) {
root = './pages/custom/';
}
if (page === 'home') {
safe.get('/', (req, res, next) => res.sendFile(`${page}.html`, { root: root }));
} else {
safe.get(`/${page}`, (req, res, next) => res.sendFile(`${page}.html`, { root: root }));
}
}
safe.use((req, res, next) => res.status(404).sendFile('404.html', { root: './pages/error/' }));
safe.use((req, res, next) => res.status(500).sendFile('500.html', { root: './pages/error/' }));
safe.listen(config.port, () => console.log(`lolisafe started on port ${config.port}`));

View File

@ -1,43 +0,0 @@
upstream backend {
server 127.0.0.1:3000; # Change to the port you specified on lolisafe
}
server {
listen 80;
listen [::]:80;
server_name lolisafe.moe;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name lolisafe.moe;
ssl_certificate /path/to/your/fullchain.pem;
ssl_certificate_key /path/to/your/privkey.pem;
ssl_trusted_certificate /path/to/your/fullchain.pem;
client_max_body_size 100M; # Change this to the max file size you want to allow
location / {
add_header Access-Control-Allow-Origin *;
root /path/to/your/uploads/folder;
try_files $uri @proxy;
}
location @proxy {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://backend;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

92
nuxt.config.js Normal file
View File

@ -0,0 +1,92 @@
import dotenv from 'dotenv/config';
import autoprefixer from 'autoprefixer';
const Util = require('./src/api/utils/Util');
export default async () => {
/*
FIXME:
Since Util.config is not populated during production env because it needs to grab the values from the db
we need to use this hack to populate it before we can access the properties without await like we do in the export below.
This will be solved once the TypeScript rewrite is complete as we can can simply pass a config object to express
and build from there, but for now the build needs to be triggered before the API is started.
*/
await Util.config;
return {
ssr: true,
srcDir: 'src/site/',
head: {
title: Util.config.serviceName,
titleTemplate: `%s | ${Util.config.serviceName}`,
// TODO: Add the directory with pictures for favicon and stuff
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'theme-color', name: 'theme-color', content: `${Util.config.metaThemeColor}` },
{ hid: 'description', name: 'description', content: `${Util.config.metaDescription}` },
{ hid: 'keywords', name: 'keywords', content: `${Util.config.metaKeywords}` },
{
hid: 'apple-mobile-web-app-title',
name: 'apple-mobile-web-app-title',
content: `${Util.config.serviceName}`
},
{ hid: 'application-name', name: 'application-name', content: `${Util.config.serviceName}` },
{ hid: 'twitter:card', name: 'twitter:card', content: 'summary' },
{ hid: 'twitter:site', name: 'twitter:site', content: `${Util.config.metaTwitterHandle}` },
{ hid: 'twitter:creator', name: 'twitter:creator', content: `${Util.config.metaTwitterHandle}` },
{ hid: 'twitter:title', name: 'twitter:title', content: `${Util.config.serviceName}` },
{ hid: 'twitter:description', name: 'twitter:description', content: `${Util.config.metaDescription}` },
{ hid: 'twitter:image', name: 'twitter:image', content: `/logo.png` },
{ hid: 'og:url', property: 'og:url', content: `/` },
{ hid: 'og:type', property: 'og:type', content: 'website' },
{ hid: 'og:title', property: 'og:title', content: `${Util.config.serviceName}` },
{ hid: 'og:description', property: 'og:description', content: `${Util.config.metaDescription}` },
{ hid: 'og:image', property: 'og:image', content: `/logo.png` },
{ hid: 'og:image:secure_url', property: 'og:image:secure_url', content: `/logo.png` },
{ hid: 'og:site_name', property: 'og:site_name', content: `${Util.config.serviceName}` }
],
link: [
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Nunito:300,400,600,700' },
// This one is a pain in the ass to make it customizable, so you should edit it manually
{ type: 'application/json+oembed', href: `/oembed.json` }
]
},
plugins: [
'~/plugins/axios',
'~/plugins/buefy',
'~/plugins/v-clipboard',
'~/plugins/vue-isyourpasswordsafe',
'~/plugins/vue-timeago',
'~/plugins/vuebar',
'~/plugins/notifier',
'~/plugins/handler'
],
css: [],
modules: ['@nuxtjs/axios', 'cookie-universal-nuxt'],
router: {
linkActiveClass: 'is-active',
linkExactActiveClass: 'is-active'
},
env: {
development: process.env.NODE_ENV !== 'production'
},
axios: {
baseURL: `${process.env.NODE_ENV === 'production' ? process.env.DOMAIN : 'http://localhost:5000'}/api`
},
build: {
extractCSS: process.env.NODE_ENV === 'production',
postcss: {
preset: {
autoprefixer
}
},
extend(config, { isDev }) {
// Extend only webpack config for client-bundle
if (isDev) {
config.devtool = 'source-map';
}
}
}
};
};

18626
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,154 @@
{
"name": "lolisafe",
"version": "3.0.0",
"description": "Blazing fast file uploader and awesome bunker written in node! 🚀",
"author": "Pitu",
"repository": {
"type": "git",
"url": "https://github.com/WeebDev/lolisafe"
},
"bugs": {
"url": "https://github.com/WeebDev/loli-safe/issues"
},
"engines": {
"node": ">=7.0.0"
},
"license": "MIT",
"dependencies": {
"bcrypt": "^1.0.3",
"body-parser": "^1.16.0",
"express": "^4.14.0",
"express-handlebars": "^3.0.0",
"express-rate-limit": "^2.6.0",
"fluent-ffmpeg": "^2.1.0",
"gm": "^1.23.0",
"helmet": "^3.5.0",
"jszip": "^3.1.4",
"knex": "^0.12.6",
"multer": "^1.2.1",
"randomstring": "^1.1.5",
"sqlite3": "^3.1.11"
},
"devDependencies": {
"eslint": "^4.4.1",
"eslint-config-aqua": "^1.4.1"
},
"eslintConfig": {
"extends": [
"aqua"
],
"env": {
"browser": true,
"node": true
},
"rules": {
"func-names": 0
}
}
"name": "chibisafe",
"version": "4.0.2",
"description": "Blazing fast file uploader and bunker written in node! 🚀",
"license": "MIT",
"author": {
"name": "Pitu",
"email": "heyitspitu@gmail.com",
"url": "https://github.com/Pitu"
},
"scripts": {
"setup": "node src/setup.js && npm run migrate && npm run seed",
"start": "npm run migrate && nuxt build && cross-env NODE_ENV=production node src/api/structures/Server",
"dev": "nodemon src/api/structures/Server",
"migrate": "knex migrate:latest",
"seed": "knex seed:run",
"restart": "pm2 restart chibisafe",
"overwrite-config": "cross-env OVERWRITE_SETTINGS=true",
"test:vue": "jest --testPathPattern=src/site",
"test:api": "jest --testPathPattern=src/tests/api",
"test:e2e": "jest --testPathPattern=src/tests/e2e",
"tests": "npm run test:api && npm run test:vue && npm run test:e2e",
"sqlite": "sqlite_web -p 5001 database/database.sqlite"
},
"repository": {
"type": "git",
"url": "https://github.com/WeebDev/chibisafe"
},
"bugs": {
"url": "https://github.com/WeebDev/chibisafe/issues"
},
"engines": {
"node": ">=12.0.0"
},
"dependencies": {
"@mdi/font": "^5.8.55",
"@nuxtjs/axios": "^5.12.5",
"adm-zip": "^0.4.13",
"bcrypt": "^5.0.1",
"blake3": "^2.1.4",
"body-parser": "^1.18.3",
"buefy": "^0.9.4",
"busboy": "^0.2.14",
"chalk": "^2.4.1",
"chrono-node": "^2.1.4",
"compression": "^1.7.2",
"cookie-universal-nuxt": "^2.0.14",
"cors": "^2.8.5",
"cron": "^1.8.2",
"dotenv": "^6.2.0",
"dumper.js": "^1.3.1",
"express": "^4.17.1",
"express-rate-limit": "^3.4.0",
"ffmpeg-probe": "^1.0.6",
"file-saver": "^2.0.1",
"file-type": "^16.1.0",
"fluent-ffmpeg": "^2.1.2",
"fs-jetpack": "^2.2.2",
"helmet": "^3.15.1",
"imagesloaded": "^4.1.4",
"joi": "^17.3.0",
"jsonwebtoken": "^8.5.0",
"knex": "^0.21.15",
"masonry-layout": "^4.2.2",
"moment": "^2.24.0",
"morgan": "^1.10.0",
"multer": "^1.4.1",
"mysql": "^2.16.0",
"nuxt": "^2.14.12",
"nuxt-dropzone": "^0.2.8",
"pg": "^7.8.1",
"qoa": "^0.2.0",
"randomstring": "^1.1.5",
"rotating-file-stream": "^2.1.3",
"search-query-parser": "^1.5.5",
"serve-static": "^1.13.2",
"sharp": "^0.29.0",
"sqlite3": "^5.0.0",
"systeminformation": "^4.34.5",
"uuid": "^3.3.2",
"v-clipboard": "^2.2.1",
"vue-axios": "^2.1.4",
"vue-isyourpasswordsafe": "^1.0.2",
"vue-plyr": "^5.1.0",
"vue-timeago": "^3.4.4",
"vue2-transitions": "^0.2.3",
"vuebar": "^0.0.20"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@vue/test-utils": "^1.1.2",
"autoprefixer": "^9.4.7",
"axios": "^0.21.1",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^26.6.3",
"cross-env": "^5.2.0",
"eslint": "^7.17.0",
"eslint-config-aqua": "^7.3.0",
"eslint-import-resolver-nuxt": "^1.0.1",
"eslint-plugin-vue": "^5.2.1",
"jest": "^26.6.3",
"jest-serializer-vue": "^2.0.2",
"nodemon": "^1.19.4",
"postcss-css-variables": "^0.11.0",
"postcss-nested": "^3.0.0",
"puppeteer": "^5.5.0",
"sass": "^1.48.0",
"sass-loader": "^10.1.0",
"vue-jest": "^3.0.7"
},
"eslintConfig": {
"extends": [
"aqua/node",
"aqua/vue"
],
"parserOptions": {
"parser": "babel-eslint",
"sourceType": "module"
},
"settings": {
"import/resolver": {
"nuxt": {
"nuxtSrcDir": "./src/site",
"extensions": [
".js",
".vue"
]
}
}
}
},
"nodemonConfig": {
"watch": [
"src/api/*"
],
"delay": 2500
},
"keywords": [
"chibisafe",
"lolisafe",
"upload",
"uploader",
"file",
"vue",
"ssr",
"file uploader",
"images"
],
"volta": {
"node": "14.17.0"
}
}

View File

@ -1,60 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="keywords" content="upload,lolisafe,file,images,hosting">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="apple-touch-icon" sizes="180x180" href="https://lolisafe.moe/images/icons/apple-touch-icon.png?v=XBreOJMe24">
<link rel="icon" type="image/png" href="https://lolisafe.moe/images/icons/favicon-32x32.png?v=XBreOJMe24" sizes="32x32">
<link rel="icon" type="image/png" href="https://lolisafe.moe/images/icons/favicon-16x16.png?v=XBreOJMe24" sizes="16x16">
<link rel="manifest" href="https://lolisafe.moe/images/icons/manifest.json?v=XBreOJMe24">
<link rel="mask-icon" href="https://lolisafe.moe/images/icons/safari-pinned-tab.svg?v=XBreOJMe24" color="#5bbad5">
<link rel="shortcut icon" href="https://lolisafe.moe/images/icons/favicon.ico?v=XBreOJMe24">
<meta name="apple-mobile-web-app-title" content="lolisafe">
<meta name="application-name" content="lolisafe">
<meta name="msapplication-config" content="https://lolisafe.moe/images/icons/browserconfig.xml?v=XBreOJMe24">
<meta name="theme-color" content="#ffffff">
<meta property="og:url" content="https://lolisafe.moe" />
<meta property="og:type" content="website" />
<meta property="og:title" content="lolisafe.moe | A small safe worth protecting." />
<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
<meta property="og:image" content="http://lolisafe.moe/images/logo_square.png" />
<meta property="og:image:secure_url" content="https://lolisafe.moe/images/logo_square.png" />
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="lolisafe.moe | A small safe worth protecting.">
<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="twitter:image" content="https://listen.moe/files/images/logo_square.png">
<meta name="twitter:image:src" content="https://lolisafe.moe/images/logo_square.png">
<title>lolisafe - A small safe worth protecting.</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.0/css/bulma.min.css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
<link rel="stylesheet" type="text/css" href="/css/style.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
<script type="text/javascript" src="/js/album.js"></script>
</head>
<body>
<section class="hero is-fullheight">
<div class="hero-head">
<div class="container">
<h1 class="title" id='title' style='margin-top: 1.5rem;'></h1>
<h1 class="subtitle" id='count'></h1>
<hr>
</div>
</div>
<div class="hero-body">
<div class="container" id='container'>
</div>
</div>
</section>
</body>
</html>

View File

@ -1,87 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="keywords" content="upload,lolisafe,file,images,hosting">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="apple-touch-icon" sizes="180x180" href="https://lolisafe.moe/images/icons/apple-touch-icon.png?v=XBreOJMe24">
<link rel="icon" type="image/png" href="https://lolisafe.moe/images/icons/favicon-32x32.png?v=XBreOJMe24" sizes="32x32">
<link rel="icon" type="image/png" href="https://lolisafe.moe/images/icons/favicon-16x16.png?v=XBreOJMe24" sizes="16x16">
<link rel="manifest" href="https://lolisafe.moe/images/icons/manifest.json?v=XBreOJMe24">
<link rel="mask-icon" href="https://lolisafe.moe/images/icons/safari-pinned-tab.svg?v=XBreOJMe24" color="#5bbad5">
<link rel="shortcut icon" href="https://lolisafe.moe/images/icons/favicon.ico?v=XBreOJMe24">
<meta name="apple-mobile-web-app-title" content="lolisafe">
<meta name="application-name" content="lolisafe">
<meta name="msapplication-config" content="https://lolisafe.moe/images/icons/browserconfig.xml?v=XBreOJMe24">
<meta name="theme-color" content="#ffffff">
<meta property="og:url" content="https://lolisafe.moe" />
<meta property="og:type" content="website" />
<meta property="og:title" content="lolisafe.moe | A small safe worth protecting." />
<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
<meta property="og:image" content="http://lolisafe.moe/images/logo_square.png" />
<meta property="og:image:secure_url" content="https://lolisafe.moe/images/logo_square.png" />
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="lolisafe.moe | A small safe worth protecting.">
<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="twitter:image" content="https://listen.moe/files/images/logo_square.png">
<meta name="twitter:image:src" content="https://lolisafe.moe/images/logo_square.png">
<title>lolisafe - A small safe worth protecting.</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.0/css/bulma.min.css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
<link rel="stylesheet" type="text/css" href="/css/style.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
<script type="text/javascript" src="https://use.fontawesome.com/cd26baa9bd.js"></script>
<script type="text/javascript" src="/js/auth.js"></script>
</head>
<body>
<style type="text/css">
section#login {
background-color: #f5f6f8;
}
</style>
<section id='login' class="hero is-fullheight">
<div class="hero-body">
<div class="container">
<h1 class="title">
Dashboard Access
</h1>
<h2 class="subtitle">
Login or register
</h2>
<div class="columns">
<div class="column">
<p class="control">
<input id='user' class="input" type="text" placeholder="Your username">
</p>
<p class="control">
<input id='pass' class="input" type="password" placeholder="Your password">
</p>
<p class="control has-addons is-pulled-right">
<a class="button" id='registerBtn' onclick="page.do('register')">
<span>Register</span>
</a>
<a class="button" id='loginBtn' onclick="page.do('login')">
<span>Log in</span>
</a>
</p>
</div>
<div class="column is-hidden-mobile"></div>
<div class="column is-hidden-mobile"></div>
</div>
</div>
</div>
</section>
</body>
</html>

View File

@ -1,100 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="keywords" content="upload,lolisafe,file,images,hosting">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="apple-touch-icon" sizes="180x180" href="https://lolisafe.moe/images/icons/apple-touch-icon.png?v=XBreOJMe24">
<link rel="icon" type="image/png" href="https://lolisafe.moe/images/icons/favicon-32x32.png?v=XBreOJMe24" sizes="32x32">
<link rel="icon" type="image/png" href="https://lolisafe.moe/images/icons/favicon-16x16.png?v=XBreOJMe24" sizes="16x16">
<link rel="manifest" href="https://lolisafe.moe/images/icons/manifest.json?v=XBreOJMe24">
<link rel="mask-icon" href="https://lolisafe.moe/images/icons/safari-pinned-tab.svg?v=XBreOJMe24" color="#5bbad5">
<link rel="shortcut icon" href="https://lolisafe.moe/images/icons/favicon.ico?v=XBreOJMe24">
<meta name="apple-mobile-web-app-title" content="lolisafe">
<meta name="application-name" content="lolisafe">
<meta name="msapplication-config" content="https://lolisafe.moe/images/icons/browserconfig.xml?v=XBreOJMe24">
<meta name="theme-color" content="#ffffff">
<meta property="og:url" content="https://lolisafe.moe" />
<meta property="og:type" content="website" />
<meta property="og:title" content="lolisafe.moe | A small safe worth protecting." />
<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
<meta property="og:image" content="http://lolisafe.moe/images/logo_square.png" />
<meta property="og:image:secure_url" content="https://lolisafe.moe/images/logo_square.png" />
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="lolisafe.moe | A small safe worth protecting.">
<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="twitter:image" content="https://listen.moe/files/images/logo_square.png">
<meta name="twitter:image:src" content="https://lolisafe.moe/images/logo_square.png">
<title>lolisafe - A small safe worth protecting.</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.0/css/bulma.min.css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
<link rel="stylesheet" type="text/css" href="/css/style.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
<script type="text/javascript" src="https://use.fontawesome.com/cd26baa9bd.js"></script>
<script type="text/javascript" src="/js/dashboard.js"></script>
</head>
<body>
<section id='auth' class="hero is-light is-fullheight">
<div class="hero-body">
<div class="container">
<h1 class="title">
Admin dashboard
</h1>
<h2 class="subtitle">
<p class="control has-addons">
<input id='token' class="input is-danger" type="text" placeholder="Your admin token">
<a id='tokenSubmit' class="button is-danger is-outlined">Check</a>
</p>
</h2>
</div>
</div>
</section>
<section id='dashboard' class="section">
<div id="panel" class="container">
<h1 class="title">Dashboard</h1>
<h2 class="subtitle">A simple <strong>dashboard</strong>, to sort your uploaded stuff</h2>
<hr>
<div class="columns">
<div class="column is-3">
<aside class="menu" id="menu">
<p class="menu-label">General</p>
<ul class="menu-list">
<li><a href="/">Frontpage</a></li>
<li><a id="itemUploads" onclick="panel.getUploads()">Uploads</a></li>
</ul>
<p class="menu-label">Albums</p>
<ul class="menu-list">
<li><a id="itemManageGallery" onclick="panel.getAlbums()">Manage your albums</a></li>
<li>
<ul id='albumsContainer'></ul>
</li>
</ul>
<p class="menu-label">Administration</p>
<ul class="menu-list">
<li><a id="itemTokens" onclick="panel.changeToken()">Change your token</a></li>
<li><a id="itemPassword" onclick="panel.changePassword()">Change your password</a></li>
<li><a id="itemLogout"onclick="panel.logout()">Logout</a></li>
</ul>
</aside>
</div>
<div class="column has-text-centered" id='page'>
<img src="/images/logo.png">
</div>
</div>
</div>
</section>
</body>
</html>

View File

@ -1,47 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>loli-safe</title>
<link href='//fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
<style>
html, body {
height: 100%;
}
body {
margin: 0;
padding: 0;
width: 100%;
color: #B0BEC5;
display: table;
font-weight: 100;
font-family: 'Lato';
}
.container {
text-align: center;
display: table-cell;
vertical-align: middle;
}
.content {
text-align: center;
display: inline-block;
}
.title {
font-size: 72px;
margin-bottom: 40px;
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<div class="title">Page not found.</div>
</div>
</div>
</body>
</html>

View File

@ -1,47 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>loli-safe</title>
<link href='//fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
<style>
html, body {
height: 100%;
}
body {
margin: 0;
padding: 0;
width: 100%;
color: #B0BEC5;
display: table;
font-weight: 100;
font-family: 'Lato';
}
.container {
text-align: center;
display: table-cell;
vertical-align: middle;
}
.content {
text-align: center;
display: inline-block;
}
.title {
font-size: 72px;
margin-bottom: 40px;
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<div class="title">Internal server error.</div>
</div>
</div>
</body>
</html>

View File

@ -1,83 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="keywords" content="upload,lolisafe,file,images,hosting">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="apple-touch-icon" sizes="180x180" href="https://lolisafe.moe/images/icons/apple-touch-icon.png?v=XBreOJMe24">
<link rel="icon" type="image/png" href="https://lolisafe.moe/images/icons/favicon-32x32.png?v=XBreOJMe24" sizes="32x32">
<link rel="icon" type="image/png" href="https://lolisafe.moe/images/icons/favicon-16x16.png?v=XBreOJMe24" sizes="16x16">
<link rel="manifest" href="https://lolisafe.moe/images/icons/manifest.json?v=XBreOJMe24">
<link rel="mask-icon" href="https://lolisafe.moe/images/icons/safari-pinned-tab.svg?v=XBreOJMe24" color="#5bbad5">
<link rel="shortcut icon" href="https://lolisafe.moe/images/icons/favicon.ico?v=XBreOJMe24">
<meta name="apple-mobile-web-app-title" content="lolisafe">
<meta name="application-name" content="lolisafe">
<meta name="msapplication-config" content="https://lolisafe.moe/images/icons/browserconfig.xml?v=XBreOJMe24">
<meta name="theme-color" content="#ffffff">
<meta property="og:url" content="https://lolisafe.moe" />
<meta property="og:type" content="website" />
<meta property="og:title" content="lolisafe.moe | A small safe worth protecting." />
<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
<meta property="og:image" content="http://lolisafe.moe/images/logo_square.png" />
<meta property="og:image:secure_url" content="https://lolisafe.moe/images/logo_square.png" />
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="lolisafe.moe | A small safe worth protecting.">
<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="twitter:image" content="https://listen.moe/files/images/logo_square.png">
<meta name="twitter:image:src" content="https://lolisafe.moe/images/logo_square.png">
<title>lolisafe - A small safe worth protecting.</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.0/css/bulma.min.css">
<link rel="stylesheet" type="text/css" href="/css/style.css">
</head>
<body>
<section class="hero is-fullheight has-text-centered" id="home">
<div class="hero-body">
<div class="container has-text-left">
<h2 class='subtitle'>What is lolisafe?</h2>
<article class="message">
<div class="message-body">
lolisafe is an easy to use, open source and completely free file upload service. We accept your files, photos, documents, anything, and give you back a shareable link for you to send to others.
</div>
</article>
<h2 class='subtitle'>Will you keep my files forever?</h2>
<article class="message">
<div class="message-body">
Unless we receive a copyright complain or some other bullshit, we will.
</div>
</article>
<h2 class='subtitle'>How can I keep track of my uploads?</h2>
<article class="message">
<div class="message-body">
Simply create a user on the site and every upload will be associated with your account, granting you access to your uploaded files through our dashboard.
</div>
</article>
<h2 class='subtitle'>What are albums?</h2>
<article class="message">
<div class="message-body">
Albums are a simple way of sorting uploads together. Right now you can create albums through the dashboard and use them only with <a target="_blank" href="https://chrome.google.com/webstore/detail/loli-safe-uploader/enkkmplljfjppcdaancckgilmgoiofnj">our chrome extension</a> which will enable you to <strong>right click -> send to lolisafe</strong> or to a desired album if you have any.
</div>
</article>
<h2 class='subtitle'>Why should I use this?</h2>
<article class="message">
<div class="message-body">
There are too many file upload services out there, and a lot of them rely on the foundations of pomf which is ancient. In a desperate and unsuccessful attempt of finding a good file uploader that's easily extendable, lolisafe was born. We give you control over your files, we give you a way to sort your uploads into albums for ease of access and we give you an api to use with ShareX or any other thing that let's you make POST requests. Awesome isn't it? Just like you.
</div>
</article>
</div>
</div>
</section>
</body>
</html>

View File

@ -1,93 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="keywords" content="upload,lolisafe,file,images,hosting">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="apple-touch-icon" sizes="180x180" href="https://lolisafe.moe/images/icons/apple-touch-icon.png?v=XBreOJMe24">
<link rel="icon" type="image/png" href="https://lolisafe.moe/images/icons/favicon-32x32.png?v=XBreOJMe24" sizes="32x32">
<link rel="icon" type="image/png" href="https://lolisafe.moe/images/icons/favicon-16x16.png?v=XBreOJMe24" sizes="16x16">
<link rel="manifest" href="https://lolisafe.moe/images/icons/manifest.json?v=XBreOJMe24">
<link rel="mask-icon" href="https://lolisafe.moe/images/icons/safari-pinned-tab.svg?v=XBreOJMe24" color="#5bbad5">
<link rel="shortcut icon" href="https://lolisafe.moe/images/icons/favicon.ico?v=XBreOJMe24">
<meta name="apple-mobile-web-app-title" content="lolisafe">
<meta name="application-name" content="lolisafe">
<meta name="msapplication-config" content="https://lolisafe.moe/images/icons/browserconfig.xml?v=XBreOJMe24">
<meta name="theme-color" content="#ffffff">
<meta property="og:url" content="https://lolisafe.moe" />
<meta property="og:type" content="website" />
<meta property="og:title" content="lolisafe.moe | A small safe worth protecting." />
<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
<meta property="og:image" content="http://lolisafe.moe/images/logo_square.png" />
<meta property="og:image:secure_url" content="https://lolisafe.moe/images/logo_square.png" />
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="lolisafe.moe | A small safe worth protecting.">
<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
<meta name="twitter:image" content="https://listen.moe/files/images/logo_square.png">
<meta name="twitter:image:src" content="https://lolisafe.moe/images/logo_square.png">
<title>lolisafe - A small safe worth protecting.</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.0/css/bulma.min.css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
<link rel="stylesheet" type="text/css" href="/css/style.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.3.0/min/dropzone.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
<script type="text/javascript" src="/js/home.js"></script>
</head>
<body>
<section class="hero is-fullheight has-text-centered" id="home">
<div class="hero-body">
<div class="container">
<p id="b">
<img class="logo" src="/images/logo_smol.png">
</p>
<h1 class="title">lolisafe</h1>
<h2 class="subtitle">A <strong>modern</strong> self-hosted file upload service</h2>
<h3 class="subtitle" id="maxFileSize"></h3>
<div class="columns">
<div class="column is-hidden-mobile"></div>
<div class="column" id="uploadContainer">
<a id="loginToUpload" href="/auth" class="button is-danger">Running in private mode. Log in to upload.</a>
<div class="field" id="albumDiv" style="display: none">
<p class="control select-wrapper">
<span class="select">
<select id="albumSelect">
<option value="">Upload to album</option>
</select>
</span>
</p>
</div>
</div>
<div class="column is-hidden-mobile"></div>
</div>
<div id="uploads">
<div id="template" class="columns">
<div class="column is-hidden-mobile"></div>
<div class="column">
<progress class="progress is-small is-danger" value="0" max="100" data-dz-uploadprogress></progress>
<p data-dz-errormessage></p>
<p class="link"></p>
</div>
<div class="column is-hidden-mobile"></div>
</div>
</div>
<h3 class="subtitle"><a href="/auth" id="loginLinkText"></a></h3>
<h3 id="links">
<a href="https://github.com/kanadeko/loli-safe" target="_blank" class="is-danger">View on GitHub</a><span>|</span><a href="https://lolisafe.moe/sharex.txt">ShareX</a><span>|</span><a href="https://chrome.google.com/webstore/detail/loli-safe-uploader/enkkmplljfjppcdaancckgilmgoiofnj" target="_blank" class="is-danger">Chrome extension</a><span>|</span><a href="/faq" class="is-danger">FAQ</a><span>|</span><a href="/auth" target="_blank" class="is-danger">Dashboard</a>
</h3>
</div>
</div>
</section>
</body>
</html>

15
pm2.json Normal file
View File

@ -0,0 +1,15 @@
{
"apps": [
{
"name": "chibisafe",
"script": "npm",
"args": "run start",
"env": {
"NODE_ENV": "production"
},
"env_production": {
"NODE_ENV": "production"
}
}
]
}

View File

@ -1,112 +0,0 @@
/* ------------------
HOME
------------------ */
section#home #b {
-webkit-animation-delay: 0.5s;
animation-delay: 0.5s;
-webkit-animation-duration: 1.5s;
animation-duration: 1.5s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation-name: floatUp;
animation-name: floatUp;
-webkit-animation-timing-function: cubic-bezier(0, 0.71, 0.29, 1);
animation-timing-function: cubic-bezier(0, 0.71, 0.29, 1);
border-radius: 24px;
display: inline-block;
height: 240px;
margin-bottom: 40px;
position: relative;
vertical-align: top;
width: 240px;
box-shadow: 0 20px 60px rgba(10, 10, 10, 0.05), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2);
}
section#home div#dropzone {
border: 1px solid #dbdbdb;
background-color: rgba(0, 0, 0, 0);
border-color: #ff3860;
color: #ff3860;
display: none;
width: 100%;
border-radius: 3px;
box-shadow: none;
height: 2.5em;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
user-select: none;
justify-content: center;
padding-left: .75em;
padding-right: .75em;
text-align: center;
cursor: pointer;
}
section#home div#uploads, section#home p#tokenContainer, section#home a#panel { display: none; }
section#home div#dropzone:hover { background-color: #ff3860; border-color: #ff3860; color: #fff; }
section#home h3#maxFileSize { font-size: 14px; }
section#home h3#links span { padding-left: 5px; padding-right: 5px; }
section#home img.logo { height: 200px; margin-top: 20px; }
section#home .dz-preview .dz-details { display: flex; }
section#home .dz-preview .dz-details .dz-size, section#home .dz-preview .dz-details .dz-filename { flex: 1; }
section#home .dz-preview img, section#home .dz-preview .dz-success-mark, section#home .dz-preview .dz-error-mark { display: none; }
section#home div#uploads { margin-bottom: 25px; }
@keyframes floatUp {
0% {
opacity: 0;
box-shadow: 0 0 0 rgba(10, 10, 10, 0), 0 0 0 rgba(10, 10, 10, 0), 0 0 0 rgba(10, 10, 10, 0);
-webkit-transform: scale(0.86);
transform: scale(0.86);
}
25% { opacity: 100; }
67% {
box-shadow: 0 0 0 rgba(10, 10, 10, 0), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2);
-webkit-transform: scale(1);
transform: scale(1);
}
100% {
box-shadow: 0 20px 60px rgba(10, 10, 10, 0.05), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2);
-webkit-transform: scale(1);
transform: scale(1);
}
}
/* ------------------
PANEL
------------------ */
section#login input, section#login p.control a.button {
border-left: 0px;
border-top: 0px;
border-right: 0px;
border-radius: 0px;
box-shadow: 0 0 0;
}
section#login p.control a.button { margin-left: 10px; }
section#login p.control a#loginBtn { border-right: 0px; }
section#login p.control a#registerBtn { border-left: 0px; }
section#auth, section#dashboard { display: none }
section#auth input { background: rgba(0, 0, 0, 0); }
section#auth input, section#auth a {
border-left: 0px;
border-top: 0px;
border-right: 0px;
border-radius: 0px;
box-shadow: 0 0 0;
}
section#dashboard .table { font-size: 12px }
section#dashboard div#table div.column { display:flex; width: 200px; height: 200px; margin: 9px; background: #f9f9f9; overflow: hidden; align-items: center; }
section#dashboard div#table div.column a { width: 100%; }
section#dashboard div#table div.column a img { width:200px; }
.select-wrapper {
text-align: center;
margin-bottom: 10px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/images/icons/mstile-150x150.png?v=XBreOJMe24"/>
<TileColor>#00aba9</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,18 +0,0 @@
{
"name": "lolisafe",
"icons": [
{
"src": "/images/icons/android-chrome-192x192.png?v=XBreOJMe24",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/images/icons/android-chrome-384x384.png?v=XBreOJMe24",
"sizes": "384x384",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -1,47 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,16.000000) scale(0.003765,-0.003765)"
fill="#000000" stroke="none">
<path d="M2397 4143 c-3 -3 -19 -7 -34 -8 -162 -16 -259 -56 -351 -144 -65
-64 -123 -152 -156 -239 -22 -61 -27 -80 -40 -148 -6 -29 -18 -161 -20 -209
-1 -40 -2 -40 -81 -80 l-81 -40 58 -50 c32 -28 70 -55 85 -62 l28 -11 -25 -10
-25 -10 29 -13 28 -13 -6 -106 c-3 -58 -7 -114 -9 -125 -3 -11 -8 -45 -11 -76
-4 -31 -9 -67 -11 -80 -3 -13 -12 -60 -20 -104 -21 -108 -99 -327 -177 -497
-29 -64 -32 -67 -62 -64 -17 2 -36 4 -42 5 -25 2 -95 171 -108 259 -9 62 -14
55 -73 -98 -35 -89 -48 -133 -59 -190 -2 -14 -6 -34 -9 -45 -21 -99 -22 -255
-2 -370 8 -44 14 -81 13 -82 -5 -6 -97 76 -131 117 -47 57 -97 151 -111 205
-30 123 -36 217 -19 300 8 41 -6 24 -48 -60 -36 -72 -72 -169 -81 -221 -3 -16
-10 -44 -16 -63 -10 -36 -7 -255 4 -304 4 -16 15 -51 26 -79 11 -28 17 -54 15
-58 -14 -22 -177 11 -235 48 l-25 16 16 -25 c27 -42 141 -127 204 -153 33 -14
86 -32 117 -41 48 -14 58 -20 63 -44 3 -15 31 -65 61 -112 31 -46 68 -109 84
-139 15 -31 41 -67 57 -80 16 -14 45 -45 64 -70 19 -25 37 -47 40 -50 3 -3 34
-43 69 -90 109 -146 240 -259 412 -353 25 -14 65 -38 89 -53 48 -30 175 -79
229 -88 117 -19 211 -37 250 -49 47 -14 85 -14 160 -3 131 21 171 87 171 281
-1 73 -3 93 -12 110 -4 6 -9 22 -13 37 -6 26 -54 109 -159 278 -26 41 -55 85
-63 97 -14 20 -14 23 -2 23 9 0 32 -21 52 -47 99 -128 366 -377 506 -473 12
-8 43 -30 68 -49 38 -29 57 -35 116 -41 128 -13 166 5 214 103 81 161 76 270
-19 438 -23 41 -45 76 -48 79 -3 3 -31 34 -61 70 -30 36 -57 67 -61 70 -15 13
-169 222 -169 231 0 6 7 23 15 38 26 51 56 185 61 271 3 47 7 93 9 103 5 27
-8 20 -38 -22 -15 -22 -45 -54 -67 -71 l-40 -32 -1 89 c0 48 -3 81 -7 73 -11
-25 -41 -64 -46 -59 -2 2 -7 33 -11 69 -7 65 -7 66 22 80 15 7 30 14 33 14 3
1 26 13 52 28 56 34 121 97 157 153 22 35 30 41 47 34 47 -17 103 -115 91
-160 -6 -22 8 -26 17 -4 3 8 8 36 12 63 10 79 -28 143 -98 165 -15 5 -15 10
-4 49 31 104 15 279 -35 384 -19 40 -29 78 -29 107 0 48 -12 61 -43 45 -9 -5
-23 -5 -31 -1 -31 19 -74 61 -91 91 -13 21 -35 38 -62 48 -24 10 -42 22 -40
28 3 6 9 10 15 9 9 -2 52 84 67 137 4 14 25 42 47 62 22 20 37 42 33 48 -3 5
-11 7 -17 4 -6 -4 -6 1 1 14 6 12 16 24 22 27 6 4 23 25 39 47 29 40 29 41 10
57 -17 13 -18 22 -11 54 5 21 7 50 6 65 -2 15 -6 52 -9 82 -10 114 -77 279
-158 387 -46 63 -167 162 -247 202 -97 49 -263 91 -276 70 -4 -5 -13 -5 -24 1
-10 5 -21 7 -24 3z"/>
<path d="M3111 3011 c-13 -13 -21 -35 -22 -58 0 -21 -1 -53 -2 -71 -1 -17 2
-32 6 -32 4 0 29 11 55 24 74 37 87 86 27 98 -12 2 -19 10 -17 20 1 8 -4 21
-12 27 -12 10 -19 8 -35 -8z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1,56 +0,0 @@
var page = {};
page.do = function(dest){
var user = document.getElementById('user').value;
var pass = document.getElementById('pass').value;
if(user === undefined || user === null || user === '')
return swal('Error', 'You need to specify a username', 'error');
if(pass === undefined || pass === null || pass === '')
return swal('Error', 'You need to specify a username', 'error');
axios.post('/api/' + dest, {
username: user,
password: pass
})
.then(function (response) {
if(response.data.success === false)
return swal('Error', response.data.description, 'error');
localStorage.token = response.data.token;
window.location = '/dashboard';
})
.catch(function (error) {
return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error');
console.log(error);
});
}
page.verify = function(){
page.token = localStorage.token;
if(page.token === undefined) return;
axios.post('/api/tokens/verify', {
token: page.token
})
.then(function (response) {
if(response.data.success === false)
return swal('Error', response.data.description, 'error');
window.location = '/dashboard';
})
.catch(function (error) {
return swal('An error ocurred', 'There was an error with the request, please check the console for more information.', 'error');
console.log(error);
});
}
window.onload = function () {
page.verify();
}

View File

@ -1,624 +0,0 @@
let panel = {}
panel.page;
panel.username;
panel.token = localStorage.token;
panel.filesView = localStorage.filesView;
panel.preparePage = function(){
if(!panel.token) return window.location = '/auth';
panel.verifyToken(panel.token, true);
}
panel.verifyToken = function(token, reloadOnError){
if(reloadOnError === undefined)
reloadOnError = false;
axios.post('/api/tokens/verify', {
token: token
})
.then(function (response) {
if(response.data.success === false){
swal({
title: "An error ocurred",
text: response.data.description,
type: "error"
}, function(){
if(reloadOnError){
localStorage.removeItem("token");
location.location = '/auth';
}
})
return;
}
axios.defaults.headers.common['token'] = token;
localStorage.token = token;
panel.token = token;
panel.username = response.data.username;
return panel.prepareDashboard();
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
}
panel.prepareDashboard = function(){
panel.page = document.getElementById('page');
document.getElementById('auth').style.display = 'none';
document.getElementById('dashboard').style.display = 'block';
document.getElementById('itemUploads').addEventListener('click', function(){
panel.setActiveMenu(this);
});
document.getElementById('itemManageGallery').addEventListener('click', function(){
panel.setActiveMenu(this);
});
document.getElementById('itemTokens').addEventListener('click', function(){
panel.setActiveMenu(this);
});
document.getElementById('itemPassword').addEventListener('click', function(){
panel.setActiveMenu(this);
});
document.getElementById('itemLogout').innerHTML = `Logout ( ${panel.username} )`;
panel.getAlbumsSidebar();
}
panel.logout = function(){
localStorage.removeItem("token");
location.reload('/');
}
panel.getUploads = function(album = undefined, page = undefined){
if(page === undefined) page = 0;
let url = '/api/uploads/' + page
if(album !== undefined)
url = '/api/album/' + album + '/' + page
axios.get(url).then(function (response) {
if(response.data.success === false){
if(response.data.description === 'No token provided') return panel.verifyToken(panel.token);
else return swal("An error ocurred", response.data.description, "error");
}
var prevPage = 0;
var nextPage = page + 1;
if(response.data.files.length < 25)
nextPage = page;
if(page > 0) prevPage = page - 1;
panel.page.innerHTML = '';
var container = document.createElement('div');
var pagination = `<nav class="pagination is-centered">
<a class="pagination-previous" onclick="panel.getUploads(${album}, ${prevPage} )">Previous</a>
<a class="pagination-next" onclick="panel.getUploads(${album}, ${nextPage} )">Next page</a>
</nav>`;
var listType = `
<div class="columns">
<div class="column">
<a class="button is-small is-outlined is-danger" title="List view" onclick="panel.setFilesView('list', ${album}, ${page})">
<span class="icon is-small">
<i class="fa fa-list-ul"></i>
</span>
</a>
<a class="button is-small is-outlined is-danger" title="List view" onclick="panel.setFilesView('thumbs', ${album}, ${page})">
<span class="icon is-small">
<i class="fa fa-th-large"></i>
</span>
</a>
</div>
</div>`
if(panel.filesView === 'thumbs'){
container.innerHTML = `
${pagination}
<hr>
${listType}
<div class="columns is-multiline is-mobile" id="table">
</div>
${pagination}
`;
panel.page.appendChild(container);
var table = document.getElementById('table');
for(var item of response.data.files){
var div = document.createElement('div');
div.className = "column is-2";
if(item.thumb !== undefined)
div.innerHTML = `<a href="${item.file}" target="_blank"><img src="${item.thumb}"/></a>`;
else
div.innerHTML = `<a href="${item.file}" target="_blank"><h1 class="title">.${item.file.split('.').pop()}</h1></a>`;
table.appendChild(div);
}
}else{
var albumOrUser = 'Album';
if(panel.username === 'root')
albumOrUser = 'User';
container.innerHTML = `
${pagination}
<hr>
${listType}
<table class="table is-striped is-narrow is-left">
<thead>
<tr>
<th>File</th>
<th>${albumOrUser}</th>
<th>Date</th>
<th></th>
</tr>
</thead>
<tbody id="table">
</tbody>
</table>
<hr>
${pagination}
`;
panel.page.appendChild(container);
var table = document.getElementById('table');
for(var item of response.data.files){
var tr = document.createElement('tr');
var displayAlbumOrUser = item.album;
if(panel.username === 'root'){
displayAlbumOrUser = '';
if(item.username !== undefined)
displayAlbumOrUser = item.username;
}
tr.innerHTML = `
<tr>
<th><a href="${item.file}" target="_blank">${item.file}</a></th>
<th>${displayAlbumOrUser}</th>
<td>${item.date}</td>
<td>
<a class="button is-small is-danger is-outlined" title="Delete album" onclick="panel.deleteFile(${item.id})">
<span class="icon is-small">
<i class="fa fa-trash-o"></i>
</span>
</a>
</td>
</tr>
`;
table.appendChild(tr);
}
}
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
}
panel.setFilesView = function(view, album, page){
localStorage.filesView = view;
panel.filesView = view;
panel.getUploads(album, page);
}
panel.deleteFile = function(id){
swal({
title: "Are you sure?",
text: "You wont be able to recover the file!",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#ff3860",
confirmButtonText: "Yes, delete it!",
closeOnConfirm: false
},
function(){
axios.post('/api/upload/delete', {
id: id
})
.then(function (response) {
if(response.data.success === false){
if(response.data.description === 'No token provided') return panel.verifyToken(panel.token);
else return swal("An error ocurred", response.data.description, "error");
}
swal("Deleted!", "The file has been deleted.", "success");
panel.getUploads();
return;
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
}
);
}
panel.getAlbums = function(){
axios.get('/api/albums').then(function (response) {
if(response.data.success === false){
if(response.data.description === 'No token provided') return panel.verifyToken(panel.token);
else return swal("An error ocurred", response.data.description, "error");
}
panel.page.innerHTML = '';
var container = document.createElement('div');
container.className = "container";
container.innerHTML = `
<h2 class="subtitle">Create new album</h2>
<p class="control has-addons has-addons-centered">
<input id="albumName" class="input" type="text" placeholder="Name">
<a id="submitAlbum" class="button is-primary">Submit</a>
</p>
<h2 class="subtitle">List of albums</h2>
<table class="table is-striped is-narrow">
<thead>
<tr>
<th>Name</th>
<th>Files</th>
<th>Created At</th>
<th>Public link</th>
<th></th>
</tr>
</thead>
<tbody id="table">
</tbody>
</table>`;
panel.page.appendChild(container);
var table = document.getElementById('table');
for(var item of response.data.albums){
var tr = document.createElement('tr');
tr.innerHTML = `
<tr>
<th>${item.name}</th>
<th>${item.files}</th>
<td>${item.date}</td>
<td><a href="${item.identifier}" target="_blank">Album link</a></td>
<td>
<a class="button is-small is-primary is-outlined" title="Edit name" onclick="panel.renameAlbum(${item.id})">
<span class="icon is-small">
<i class="fa fa-pencil"></i>
</span>
</a>
<a class="button is-small is-danger is-outlined" title="Delete album" onclick="panel.deleteAlbum(${item.id})">
<span class="icon is-small">
<i class="fa fa-trash-o"></i>
</span>
</a>
</td>
</tr>
`;
table.appendChild(tr);
}
document.getElementById('submitAlbum').addEventListener('click', function(){
panel.submitAlbum();
});
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
}
panel.renameAlbum = function(id){
swal({
title: "Rename album",
text: "New name you want to give the album:",
type: "input",
showCancelButton: true,
closeOnConfirm: false,
animation: "slide-from-top",
inputPlaceholder: "My super album"
},function(inputValue){
if (inputValue === false) return false;
if (inputValue === "") {
swal.showInputError("You need to write something!");
return false
}
axios.post('/api/albums/rename', {
id: id,
name: inputValue
})
.then(function (response) {
if(response.data.success === false){
if(response.data.description === 'No token provided') return panel.verifyToken(panel.token);
else if(response.data.description === 'Name already in use') swal.showInputError("That name is already in use!");
else swal("An error ocurred", response.data.description, "error");
return;
}
swal("Success!", "Your album was renamed to: " + inputValue, "success");
panel.getAlbumsSidebar();
panel.getAlbums();
return;
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
});
}
panel.deleteAlbum = function(id){
swal({
title: "Are you sure?",
text: "This won't delete your files, only the album!",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#ff3860",
confirmButtonText: "Yes, delete it!",
closeOnConfirm: false
},
function(){
axios.post('/api/albums/delete', {
id: id
})
.then(function (response) {
if(response.data.success === false){
if(response.data.description === 'No token provided') return panel.verifyToken(panel.token);
else return swal("An error ocurred", response.data.description, "error");
}
swal("Deleted!", "Your album has been deleted.", "success");
panel.getAlbumsSidebar();
panel.getAlbums();
return;
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
}
);
}
panel.submitAlbum = function(){
axios.post('/api/albums', {
name: document.getElementById('albumName').value
})
.then(function (response) {
if(response.data.success === false){
if(response.data.description === 'No token provided') return panel.verifyToken(panel.token);
else return swal("An error ocurred", response.data.description, "error");
}
swal("Woohoo!", "Album was added successfully", "success");
panel.getAlbumsSidebar();
panel.getAlbums();
return;
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
}
panel.getAlbumsSidebar = function(){
axios.get('/api/albums/sidebar')
.then(function (response) {
if(response.data.success === false){
if(response.data.description === 'No token provided') return panel.verifyToken(panel.token);
else return swal("An error ocurred", response.data.description, "error");
}
var albumsContainer = document.getElementById('albumsContainer');
albumsContainer.innerHTML = '';
if(response.data.albums === undefined) return;
for(var album of response.data.albums){
li = document.createElement('li');
a = document.createElement('a');
a.id = album.id;
a.innerHTML = album.name;
a.addEventListener('click', function(){
panel.getAlbum(this);
});
li.appendChild(a);
albumsContainer.appendChild(li);
}
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
}
panel.getAlbum = function(item){
panel.setActiveMenu(item);
panel.getUploads(item.id);
}
panel.changeToken = function(){
axios.get('/api/tokens')
.then(function (response) {
if(response.data.success === false){
if(response.data.description === 'No token provided') return panel.verifyToken(panel.token);
else return swal("An error ocurred", response.data.description, "error");
}
panel.page.innerHTML = '';
var container = document.createElement('div');
container.className = "container";
container.innerHTML = `
<h2 class="subtitle">Manage your token</h2>
<label class="label">Your current token:</label>
<p class="control has-addons">
<input id="token" readonly class="input is-expanded" type="text" placeholder="Your token" value="${response.data.token}">
<a id="getNewToken" class="button is-primary">Request new token</a>
</p>
`;
panel.page.appendChild(container);
document.getElementById('getNewToken').addEventListener('click', function(){
panel.getNewToken();
});
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
}
panel.getNewToken = function(){
axios.post('/api/tokens/change')
.then(function (response) {
if(response.data.success === false){
if(response.data.description === 'No token provided') return panel.verifyToken(panel.token);
else return swal("An error ocurred", response.data.description, "error");
}
swal({
title: "Woohoo!",
text: 'Your token was changed successfully.',
type: "success"
}, function(){
localStorage.token = response.data.token;
location.reload();
})
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
}
panel.changePassword = function(){
panel.page.innerHTML = '';
var container = document.createElement('div');
container.className = "container";
container.innerHTML = `
<h2 class="subtitle">Change your password</h2>
<label class="label">New password:</label>
<p class="control has-addons">
<input id="password" class="input is-expanded" type="password" placeholder="Your new password">
</p>
<label class="label">Confirm password:</label>
<p class="control has-addons">
<input id="passwordConfirm" class="input is-expanded" type="password" placeholder="Verify your new password">
<a id="sendChangePassword" class="button is-primary">Set new password</a>
</p>
`;
panel.page.appendChild(container);
document.getElementById('sendChangePassword').addEventListener('click', function(){
if (document.getElementById('password').value === document.getElementById('passwordConfirm').value) {
panel.sendNewPassword(document.getElementById('password').value);
} else {
swal({
title: "Password mismatch!",
text: 'Your passwords do not match, please try again.',
type: "error"
}, function() {
panel.changePassword();
});
}
});
}
panel.sendNewPassword = function(pass){
axios.post('/api/password/change', {password: pass})
.then(function (response) {
if(response.data.success === false){
if(response.data.description === 'No token provided') return panel.verifyToken(panel.token);
else return swal("An error ocurred", response.data.description, "error");
}
swal({
title: "Woohoo!",
text: 'Your password was changed successfully.',
type: "success"
}, function(){
location.reload();
})
})
.catch(function (error) {
return swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
console.log(error);
});
}
panel.setActiveMenu = function(item){
var menu = document.getElementById('menu');
var items = menu.getElementsByTagName('a');
for(var i = 0; i < items.length; i++)
items[i].className = "";
item.className = 'is-active';
}
window.onload = function () {
panel.preparePage();
}

View File

@ -1,196 +0,0 @@
var upload = {};
upload.isPrivate = true;
upload.token = localStorage.token;
upload.maxFileSize;
// add the album var to the upload so we can store the album id in there
upload.album;
upload.myDropzone;
upload.checkIfPublic = function(){
axios.get('/api/check')
.then(function (response) {
upload.isPrivate= response.data.private;
upload.maxFileSize = response.data.maxFileSize;
upload.preparePage();
})
.catch(function (error) {
swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
return console.log(error);
});
}
upload.preparePage = function(){
if(!upload.isPrivate) return upload.prepareUpload();
if(!upload.token) return document.getElementById('loginToUpload').style.display = 'inline-flex';
upload.verifyToken(upload.token, true);
}
upload.verifyToken = function(token, reloadOnError){
if(reloadOnError === undefined)
reloadOnError = false;
axios.post('/api/tokens/verify', {
token: token
})
.then(function (response) {
if(response.data.success === false){
swal({
title: "An error ocurred",
text: response.data.description,
type: "error"
}, function(){
if(reloadOnError){
localStorage.removeItem("token");
location.reload();
}
})
return;
}
localStorage.token = token;
upload.token = token;
return upload.prepareUpload();
})
.catch(function (error) {
swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
return console.log(error);
});
}
upload.prepareUpload = function(){
// I think this fits best here because we need to check for a valid token before we can get the albums
if (upload.token) {
var select = document.getElementById('albumSelect');
select.addEventListener('change', function() {
upload.album = select.value;
});
axios.get('/api/albums', { headers: { token: upload.token }})
.then(function(res) {
var albums = res.data.albums;
// if the user doesn't have any albums we don't really need to display
// an album selection
if (albums.length === 0) return;
// loop through the albums and create an option for each album
for (var i = 0; i < albums.length; i++) {
var opt = document.createElement('option');
opt.value = albums[i].id;
opt.innerHTML = albums[i].name;
select.appendChild(opt);
}
// display the album selection
document.getElementById('albumDiv').style.display = 'block';
})
.catch(function(e) {
swal("An error ocurred", 'There was an error with the request, please check the console for more information.', "error");
return console.log(e);
})
}
div = document.createElement('div');
div.id = 'dropzone';
div.innerHTML = 'Click here or drag and drop files';
div.style.display = 'flex';
document.getElementById('maxFileSize').innerHTML = 'Maximum upload size per file is ' + upload.maxFileSize;
document.getElementById('loginToUpload').style.display = 'none';
if(upload.token === undefined)
document.getElementById('loginLinkText').innerHTML = 'Create an account and keep track of your uploads';
document.getElementById('uploadContainer').appendChild(div);
upload.prepareDropzone();
}
upload.prepareDropzone = function(){
var previewNode = document.querySelector('#template');
previewNode.id = '';
var previewTemplate = previewNode.parentNode.innerHTML;
previewNode.parentNode.removeChild(previewNode);
var dropzone = new Dropzone('div#dropzone', {
url: '/api/upload',
paramName: 'files[]',
maxFilesize: upload.maxFileSize.slice(0, -2),
parallelUploads: 2,
uploadMultiple: false,
previewsContainer: 'div#uploads',
previewTemplate: previewTemplate,
createImageThumbnails: false,
maxFiles: 1000,
autoProcessQueue: true,
headers: {
'token': upload.token
},
init: function() {
upload.myDropzone = this;
this.on('addedfile', function(file) {
document.getElementById('uploads').style.display = 'block';
});
// add the selected albumid, if an album is selected, as a header
this.on('sending', function(file, xhr) {
if (upload.album) {
xhr.setRequestHeader('albumid', upload.album)
}
});
}
});
// Update the total progress bar
dropzone.on('uploadprogress', function(file, progress) {
file.previewElement.querySelector('.progress').setAttribute('value', progress);
file.previewElement.querySelector('.progress').innerHTML = progress + '%';
});
dropzone.on('success', function(file, response) {
// Handle the responseText here. For example, add the text to the preview element:
if(response.success === false){
var span = document.createElement('span');
span.innerHTML = response.description;
file.previewTemplate.querySelector('.link').appendChild(span);
return;
}
a = document.createElement('a');
a.href = response.files[0].url;
a.target = '_blank';
a.innerHTML = response.files[0].url;
file.previewTemplate.querySelector('.link').appendChild(a);
file.previewTemplate.querySelector('.progress').style.display = 'none';
});
}
//Handle image paste event
window.addEventListener('paste', function(event) {
var items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (index in items) {
var item = items[index];
if (item.kind === 'file') {
var blob = item.getAsFile();
console.log(blob.type);
var file = new File([blob], "pasted-image."+blob.type.match(/(?:[^\/]*\/)([^;]*)/)[1]);
file.type = blob.type;
console.log(file);
upload.myDropzone.addFile(file);
}
}
});
window.onload = function () {
upload.checkIfPublic();
};

View File

@ -1,56 +0,0 @@
const config = require('../config.js');
const routes = require('express').Router();
const db = require('knex')(config.database);
const path = require('path');
const utils = require('../controllers/utilsController.js');
routes.get('/a/:identifier', async (req, res, next) => {
let identifier = req.params.identifier;
if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' });
const album = await db.table('albums').where({ identifier, enabled: 1 }).first();
if (!album) return res.status(404).sendFile('404.html', { root: './pages/error/' });
const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC');
let thumb = '';
const basedomain = config.domain;
for (let file of files) {
file.file = `${basedomain}/${file.name}`;
let ext = path.extname(file.name).toLowerCase();
if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) {
file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`;
/*
If thumbnail for album is still not set, do it.
A potential improvement would be to let the user upload a specific image as an album cover
since embedding the first image could potentially result in nsfw content when pasting links.
*/
if (thumb === '') {
thumb = file.thumb;
}
file.thumb = `<img src="${file.thumb}"/>`;
} else {
file.thumb = `<h1 class="title">.${ext}</h1>`;
}
}
let enableDownload = false;
if (config.uploads.generateZips) enableDownload = true;
return res.render('album', {
layout: false,
title: album.name,
count: files.length,
thumb,
files,
identifier,
enableDownload
});
});
module.exports = routes;

View File

@ -1,37 +0,0 @@
const config = require('../config.js');
const routes = require('express').Router();
const uploadController = require('../controllers/uploadController');
const albumsController = require('../controllers/albumsController');
const tokenController = require('../controllers/tokenController');
const authController = require('../controllers/authController');
routes.get('/check', (req, res, next) => {
return res.json({
private: config.private,
maxFileSize: config.uploads.maxSize
});
});
routes.post('/login', (req, res, next) => authController.verify(req, res, next));
routes.post('/register', (req, res, next) => authController.register(req, res, next));
routes.post('/password/change', (req, res, next) => authController.changePassword(req, res, next));
routes.get('/uploads', (req, res, next) => uploadController.list(req, res, next));
routes.get('/uploads/:page', (req, res, next) => uploadController.list(req, res, next));
routes.post('/upload', (req, res, next) => uploadController.upload(req, res, next));
routes.post('/upload/delete', (req, res, next) => uploadController.delete(req, res, next));
routes.post('/upload/:albumid', (req, res, next) => uploadController.upload(req, res, next));
routes.get('/album/get/:identifier', (req, res, next) => albumsController.get(req, res, next));
routes.get('/album/zip/:identifier', (req, res, next) => albumsController.generateZip(req, res, next));
routes.get('/album/:id', (req, res, next) => uploadController.list(req, res, next));
routes.get('/album/:id/:page', (req, res, next) => uploadController.list(req, res, next));
routes.get('/albums', (req, res, next) => albumsController.list(req, res, next));
routes.get('/albums/:sidebar', (req, res, next) => albumsController.list(req, res, next));
routes.post('/albums', (req, res, next) => albumsController.create(req, res, next));
routes.post('/albums/delete', (req, res, next) => albumsController.delete(req, res, next));
routes.post('/albums/rename', (req, res, next) => albumsController.rename(req, res, next));
routes.get('/albums/test', (req, res, next) => albumsController.test(req, res, next));
routes.get('/tokens', (req, res, next) => tokenController.list(req, res, next));
routes.post('/tokens/verify', (req, res, next) => tokenController.verify(req, res, next));
routes.post('/tokens/change', (req, res, next) => tokenController.change(req, res, next));
module.exports = routes;

View File

@ -0,0 +1,94 @@
exports.up = async knex => {
await knex.schema.createTable('users', table => {
table.increments();
table.string('username');
table.text('password');
table.boolean('enabled');
table.boolean('isAdmin');
table.string('apiKey');
table.timestamp('passwordEditedAt');
table.timestamp('apiKeyEditedAt');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
await knex.schema.createTable('albums', table => {
table.increments();
table.integer('userId');
table.string('name');
table.timestamp('zippedAt');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
await knex.schema.createTable('files', table => {
table.increments();
table.integer('userId');
table.string('name');
table.string('original');
table.string('type');
table.integer('size');
table.string('hash');
table.string('ip');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
await knex.schema.createTable('links', table => {
table.increments();
table.integer('userId');
table.integer('albumId');
table.string('identifier');
table.integer('views');
table.boolean('enabled');
table.boolean('enableDownload');
table.timestamp('expiresAt');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
await knex.schema.createTable('albumsFiles', table => {
table.increments();
table.integer('albumId');
table.integer('fileId');
});
await knex.schema.createTable('albumsLinks', table => {
table.increments();
table.integer('albumId');
table.integer('linkId');
});
await knex.schema.createTable('tags', table => {
table.increments();
table.string('uuid');
table.integer('userId');
table.string('name');
table.timestamp('createdAt');
table.timestamp('editedAt');
});
await knex.schema.createTable('fileTags', table => {
table.increments();
table.integer('fileId');
table.integer('tagId');
});
await knex.schema.createTable('bans', table => {
table.increments();
table.string('ip');
table.timestamp('createdAt');
});
};
exports.down = async knex => {
await knex.schema.dropTableIfExists('users');
await knex.schema.dropTableIfExists('albums');
await knex.schema.dropTableIfExists('files');
await knex.schema.dropTableIfExists('links');
await knex.schema.dropTableIfExists('albumsFiles');
await knex.schema.dropTableIfExists('albumsLinks');
await knex.schema.dropTableIfExists('tags');
await knex.schema.dropTableIfExists('fileTags');
await knex.schema.dropTableIfExists('bans');
};

View File

@ -0,0 +1,34 @@
exports.up = async knex => {
await knex.schema.alterTable('users', table => {
table.unique(['username', 'apiKey']);
});
await knex.schema.alterTable('albums', table => {
table.boolean('nsfw').defaultTo(false);
table.unique(['userId', 'name']);
});
await knex.schema.alterTable('links', table => {
table.unique(['userId', 'albumId', 'identifier']);
});
await knex.schema.alterTable('albumsFiles', table => {
table.unique(['albumId', 'fileId']);
});
await knex.schema.alterTable('albumsLinks', table => {
table.unique(['linkId']);
});
await knex.schema.alterTable('tags', table => {
table.unique(['userId', 'name']);
});
await knex.schema.alterTable('fileTags', table => {
table.unique(['fileId', 'tagId']);
});
};
exports.down = async () => {
// Nothing
};

View File

@ -0,0 +1,16 @@
exports.up = async knex => {
await knex.schema.createTable('statistics', table => {
table.increments();
table.integer('batchId');
table.string('type');
table.json('data');
table.timestamp('createdAt');
table.unique(['batchId', 'type']);
});
};
exports.down = async knex => {
await knex.schema.dropTableIfExists('statistics');
};

View File

@ -0,0 +1,25 @@
const Util = require('../../utils/Util');
exports.up = async knex => {
await knex.schema.createTable('settings', table => {
table.string('key');
table.string('value');
});
try {
const defaults = Util.getEnvironmentDefaults();
const keys = Object.keys(defaults);
for (const item of keys) {
await knex('settings').insert({
key: item,
value: JSON.stringify(defaults[item])
});
}
} catch (error) {
console.error(error);
}
};
exports.down = async knex => {
await knex.schema.dropTableIfExists('settings');
};

View File

@ -0,0 +1,56 @@
/* eslint-disable no-console */
const bcrypt = require('bcrypt');
const moment = require('moment');
const Util = require('../../utils/Util');
exports.seed = async db => {
const now = moment.utc().toDate();
// Save environment variables to the database
try {
const defaults = Util.getEnvironmentDefaults();
const keys = Object.keys(defaults);
for (const item of keys) {
await Util.writeConfigToDb({
key: item,
value: defaults[item]
});
}
} catch (error) {
console.error(error);
}
// Create admin user if it doesnt exist
const user = await db.table('users').where({ username: 'admin' }).first();
if (user) {
console.log();
console.log('=========================================================');
console.log('== admin account already exists, skipping. ==');
console.log('=========================================================');
console.log('== Run `pm2 start pm2.json` to start the service ==');
console.log('=========================================================');
console.log();
return;
}
try {
const hash = await bcrypt.hash('admin', 10);
await db.table('users').insert({
username: 'admin',
password: hash,
passwordEditedAt: now,
createdAt: now,
editedAt: now,
enabled: true,
isAdmin: true
});
console.log();
console.log('=========================================================');
console.log('== Successfully created the admin account. ==');
console.log('=========================================================');
console.log('== Run `pm2 start pm2.json` to start the service ==');
console.log('=========================================================');
console.log();
} catch (error) {
console.error(error);
}
};

View File

@ -0,0 +1,25 @@
const Route = require('../../structures/Route');
class banIP extends Route {
constructor() {
super('/admin/ban/ip', 'post', { adminOnly: true });
}
async run(req, res, db) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { ip } = req.body;
if (!ip) return res.status(400).json({ message: 'No ip provided' });
try {
await db.table('bans').insert({ ip });
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully banned the ip'
});
}
}
module.exports = banIP;

View File

@ -0,0 +1,32 @@
const Route = require('../../structures/Route');
const Util = require('../../utils/Util');
class filesGET extends Route {
constructor() {
super('/admin/file/:id', 'get', { adminOnly: true });
}
async run(req, res, db) {
const { id } = req.params;
if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' });
let file = await db.table('files').where({ id }).first();
const user = await db.table('users')
.select('id', 'username', 'enabled', 'createdAt', 'editedAt', 'apiKeyEditedAt', 'isAdmin')
.where({ id: file.userId })
.first();
file = Util.constructFilePublicLink(req, file);
// Additional relevant data
const filesFromUser = await db.table('files').where({ userId: user.id }).select('id');
user.fileCount = filesFromUser.length;
return res.json({
message: 'Successfully retrieved file',
file,
user
});
}
}
module.exports = filesGET;

View File

@ -0,0 +1,27 @@
const Route = require('../../structures/Route');
class unBanIP extends Route {
constructor() {
super('/admin/unban/ip', 'post', { adminOnly: true });
}
async run(req, res, db) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { ip } = req.body;
if (!ip) return res.status(400).json({ message: 'No ip provided' });
try {
await db.table('bans')
.where({ ip })
.delete();
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully unbanned the ip'
});
}
}
module.exports = unBanIP;

View File

@ -0,0 +1,29 @@
const Route = require('../../structures/Route');
class userDemote extends Route {
constructor() {
super('/admin/users/demote', 'post', { adminOnly: true });
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id } = req.body;
if (!id) return res.status(400).json({ message: 'No id provided' });
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
try {
await db.table('users')
.where({ id })
.update({ isAdmin: false })
.wasMutated();
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully demoted user'
});
}
}
module.exports = userDemote;

View File

@ -0,0 +1,29 @@
const Route = require('../../structures/Route');
class userDisable extends Route {
constructor() {
super('/admin/users/disable', 'post', { adminOnly: true });
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id } = req.body;
if (!id) return res.status(400).json({ message: 'No id provided' });
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
try {
await db.table('users')
.where({ id })
.update({ enabled: false })
.wasMutated();
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully disabled user'
});
}
}
module.exports = userDisable;

View File

@ -0,0 +1,29 @@
const Route = require('../../structures/Route');
class userEnable extends Route {
constructor() {
super('/admin/users/enable', 'post', { adminOnly: true });
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id } = req.body;
if (!id) return res.status(400).json({ message: 'No id provided' });
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
try {
await db.table('users')
.where({ id })
.update({ enabled: true })
.wasMutated();
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully enabled user'
});
}
}
module.exports = userEnable;

View File

@ -0,0 +1,55 @@
const Route = require('../../structures/Route');
const Util = require('../../utils/Util');
class usersGET extends Route {
constructor() {
super('/admin/users/:id', 'get', { adminOnly: true });
}
async run(req, res, db) {
const { id } = req.params;
if (!id) return res.status(400).json({ message: 'Invalid user ID supplied' });
try {
const user = await db.table('users')
.select('id', 'username', 'enabled', 'createdAt', 'editedAt', 'apiKeyEditedAt', 'isAdmin')
.where({ id })
.first();
let count = 0;
let files = db.table('files')
.where({ userId: user.id })
.orderBy('id', 'desc');
const { page, limit = 100 } = req.query;
if (page && page >= 0) {
files = await files.offset((page - 1) * limit).limit(limit);
const dbRes = await db.table('files')
.count('* as count')
.where({ userId: user.id })
.first();
count = dbRes.count;
} else {
files = await files; // execute the query
count = files.length;
}
for (let file of files) {
file = Util.constructFilePublicLink(req, file);
}
return res.json({
message: 'Successfully retrieved user',
user,
files,
count
});
} catch (error) {
return super.error(res, error);
}
}
}
module.exports = usersGET;

View File

@ -0,0 +1,28 @@
const Route = require('../../structures/Route');
class userPromote extends Route {
constructor() {
super('/admin/users/promote', 'post', { adminOnly: true });
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id } = req.body;
if (!id) return res.status(400).json({ message: 'No id provided' });
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
try {
await db.table('users')
.where({ id })
.update({ isAdmin: true });
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully promoted user'
});
}
}
module.exports = userPromote;

View File

@ -0,0 +1,26 @@
const Route = require('../../structures/Route');
const Util = require('../../utils/Util');
class userDemote extends Route {
constructor() {
super('/admin/users/purge', 'post', { adminOnly: true });
}
async run(req, res) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id } = req.body;
if (!id) return res.status(400).json({ message: 'No id provided' });
try {
await Util.deleteAllFilesFromUser(id);
} catch (error) {
return super.error(res, error);
}
return res.json({
message: 'Successfully deleted the user\'s files'
});
}
}
module.exports = userDemote;

View File

@ -0,0 +1,23 @@
const Route = require('../../structures/Route');
class usersGET extends Route {
constructor() {
super('/admin/users', 'get', { adminOnly: true });
}
async run(req, res, db) {
try {
const users = await db.table('users')
.select('id', 'username', 'enabled', 'isAdmin', 'createdAt');
return res.json({
message: 'Successfully retrieved users',
users
});
} catch (error) {
return super.error(res, error);
}
}
}
module.exports = usersGET;

View File

@ -0,0 +1,39 @@
const Route = require('../../structures/Route');
class albumDELETE extends Route {
constructor() {
super('/album/:id', 'delete');
}
async run(req, res, db, user) {
const { id } = req.params;
if (!id) return res.status(400).json({ message: 'Invalid album ID supplied' });
/*
Check if the album exists
*/
const album = await db.table('albums').where({ id, userId: user.id }).first();
if (!album) return res.status(400).json({ message: 'The album doesn\'t exist or doesn\'t belong to the user' });
try {
// Delete the album
await db.table('albums').where({ id }).delete();
// Delete the relation of any files attached to this album
await db.table('albumsFiles').where({ albumId: id }).delete();
// Delete the relation of any links attached to this album
await db.table('albumsLinks').where({ albumId: id }).delete();
// Delete any album links created for this album
await db.table('links').where({ albumId: id }).delete()
.wasMutated();
return res.json({ message: 'The album was deleted successfully' });
} catch (error) {
return super.error(res, error);
}
}
}
module.exports = albumDELETE;

View File

@ -0,0 +1,33 @@
const Route = require('../../structures/Route');
class albumEditPOST extends Route {
constructor() {
super('/album/edit', 'post');
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { id, name, nsfw } = req.body;
if (!id) return res.status(400).json({ message: 'Invalid album identifier supplied' });
const album = await db.table('albums').where({ id, userId: user.id }).first();
if (!album) return res.status(400).json({ message: 'The album doesn\'t exist or doesn\'t belong to the user' });
try {
const updateObj = {
name: name || album.name,
nsfw: nsfw === true ? true : nsfw === false ? false : album.nsfw
};
await db
.table('albums')
.where({ id })
.update(updateObj);
return res.json({ message: 'Editing the album was successful', data: updateObj });
} catch (error) {
return super.error(res, error);
}
}
}
module.exports = albumEditPOST;

View File

@ -0,0 +1,58 @@
const Route = require('../../structures/Route');
const Util = require('../../utils/Util');
class albumGET extends Route {
constructor() {
super('/album/:id/full', 'get');
}
async run(req, res, db, user) {
const { id } = req.params;
if (!id) return res.status(400).json({ message: 'Invalid id supplied' });
const album = await db
.table('albums')
.where({ id, userId: user.id })
.first();
if (!album) return res.status(404).json({ message: 'Album not found' });
let count = 0;
let files = db
.table('albumsFiles')
.where({ albumId: id })
.join('files', 'albumsFiles.fileId', 'files.id')
.select('files.id', 'files.name', 'files.createdAt')
.orderBy('files.id', 'desc');
const { page, limit = 100 } = req.query;
if (page && page >= 0) {
files = await files.offset((page - 1) * limit).limit(limit);
const dbRes = await db
.table('albumsFiles')
.count('* as count')
.where({ albumId: id })
.first();
count = dbRes.count;
} else {
files = await files; // execute the query
count = files.length;
}
// eslint-disable-next-line no-restricted-syntax
for (let file of files) {
file = Util.constructFilePublicLink(req, file);
}
return res.json({
message: 'Successfully retrieved album',
name: album.name,
files,
count
});
}
}
module.exports = albumGET;

View File

@ -0,0 +1,64 @@
const Route = require('../../structures/Route');
const Util = require('../../utils/Util');
class albumGET extends Route {
constructor() {
super('/album/:identifier', 'get', { bypassAuth: true });
}
async run(req, res, db) {
const { identifier } = req.params;
if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' });
// Make sure it exists and it's enabled
const link = await db.table('links').where({ identifier, enabled: true }).first();
if (!link) return res.status(404).json({ message: 'The album could not be found' });
// Same with the album, just to make sure is not a deleted album and a leftover link
const album = await db.table('albums').where('id', link.albumId).first();
if (!album) return res.status(404).json({ message: 'Album not found' });
let count = 0;
let files = db.table('albumsFiles')
.where({ albumId: link.albumId })
.join('files', 'albumsFiles.fileId', 'files.id')
.select('files.name', 'files.id')
.orderBy('files.id', 'desc');
const { page, limit = 50 } = req.query;
if (page && page >= 0) {
files = await files.offset((page - 1) * limit).limit(limit);
const dbRes = await db.table('albumsFiles')
.where({ albumId: link.albumId })
.join('files', 'albumsFiles.fileId', 'files.id')
.select('files.name', 'files.id')
.orderBy('files.id', 'desc')
.count('* as count')
.first();
count = dbRes.count;
} else {
files = await files; // execute the query
count = files.length;
}
for (let file of files) {
file = Util.constructFilePublicLink(req, file);
}
// Add 1 more view to the link
await db.table('links').where({ identifier }).update('views', Number(link.views) + 1);
return res.json({
message: 'Successfully retrieved files',
name: album.name,
downloadEnabled: link.enableDownload,
isNsfw: album.nsfw,
files,
count
});
}
}
module.exports = albumGET;

View File

@ -0,0 +1,43 @@
const moment = require('moment');
const Route = require('../../structures/Route');
class albumPOST extends Route {
constructor() {
super('/album/new', 'post', { canApiKey: true });
}
async run(req, res, db, user) {
if (!req.body) return res.status(400).json({ message: 'No body provided' });
const { name } = req.body;
if (!name) return res.status(400).json({ message: 'No name provided' });
/*
Check that an album with that name doesn't exist yet
*/
const album = await db
.table('albums')
.where({ name, userId: user.id })
.first();
if (album) return res.status(401).json({ message: "There's already an album with that name" });
const now = moment.utc().toDate();
const insertObj = {
name,
userId: user.id,
createdAt: now,
editedAt: now
};
const dbRes = await db
.table('albums')
.insert(insertObj)
.returning('id')
.wasMutated();
insertObj.id = dbRes.pop();
return res.json({ message: 'The album was created successfully', data: insertObj });
}
}
module.exports = albumPOST;

View File

@ -0,0 +1,30 @@
const Route = require('../../structures/Route');
const Util = require('../../utils/Util');
class albumDELETE extends Route {
constructor() {
super('/album/:id/purge', 'delete');
}
async run(req, res, db, user) {
const { id } = req.params;
if (!id) return res.status(400).json({ message: 'Invalid album ID supplied' });
/*
Check if the album exists
*/
const album = await db.table('albums').where({ id, userId: user.id }).first();
if (!album) return res.status(400).json({ message: 'The album doesn\'t exist or doesn\'t belong to the user' });
try {
await Util.deleteAllFilesFromAlbum(id);
await db.table('albums').where({ id }).delete()
.wasMutated();
return res.json({ message: 'The album was deleted successfully' });
} catch (error) {
return super.error(res, error);
}
}
}
module.exports = albumDELETE;

View File

@ -0,0 +1,90 @@
const path = require('path');
const jetpack = require('fs-jetpack');
const Route = require('../../structures/Route');
const Util = require('../../utils/Util');
const log = require('../../utils/Log');
class albumGET extends Route {
constructor() {
super('/album/:identifier/zip', 'get', { bypassAuth: true });
}
async run(req, res, db) {
const { identifier } = req.params;
if (!identifier) return res.status(400).json({ message: 'Invalid identifier supplied' });
// TODO: Do we really want to let anyone create a zip of an album?
/*
Make sure it exists and it's enabled
*/
const link = await db.table('links')
.where({
identifier,
enabled: true,
enableDownload: true
})
.first();
if (!link) return res.status(400).json({ message: 'The supplied identifier could not be found' });
/*
Same with the album, just to make sure is not a deleted album and a leftover link
*/
const album = await db.table('albums')
.where('id', link.albumId)
.first();
if (!album) return res.status(400).json({ message: 'Album not found' });
/*
If the date when the album was zipped is greater than the album's last edit, we just send the zip to the user
*/
if (album.zippedAt > album.editedAt) {
const filePath = path.join(__dirname, '../../../../uploads', 'zips', `${album.userId}-${album.id}.zip`);
const exists = await jetpack.existsAsync(filePath);
/*
Make sure the file exists just in case, and if not, continue to it's generation.
*/
if (exists) {
const fileName = `${Util.config.serviceName}-${identifier}.zip`;
return res.download(filePath, fileName);
}
}
/*
Grab the files in a very unoptimized way. (This should be a join between both tables)
*/
const fileList = await db.table('albumsFiles')
.where('albumId', link.albumId)
.select('fileId');
/*
If there are no files, stop here
*/
if (!fileList || !fileList.length) return res.status(400).json({ message: 'Can\'t download an empty album' });
/*
Get the actual files
*/
const fileIds = fileList.map(el => el.fileId);
const files = await db.table('files')
.whereIn('id', fileIds)
.select('name');
const filesToZip = files.map(el => el.name);
try {
Util.createZip(filesToZip, album);
await db.table('albums')
.where('id', link.albumId)
.update('zippedAt', db.fn.now())
.wasMutated();
const filePath = path.join(__dirname, '../../../../uploads', 'zips', `${album.userId}-${album.id}.zip`);
const fileName = `${Util.config.serviceName}-${identifier}.zip`;
return res.download(filePath, fileName);
} catch (error) {
log.error(error);
return res.status(500).json({ message: 'There was a problem downloading the album' });
}
}
}
module.exports = albumGET;

View File

@ -0,0 +1,71 @@
/* eslint-disable max-classes-per-file */
const Route = require('../../structures/Route');
const Util = require('../../utils/Util');
class albumsGET extends Route {
constructor() {
super('/albums/mini', 'get', { canApiKey: true });
}
async run(req, res, db, user) {
/*
Let's fetch the albums. This route will only return a small portion
of the album files for displaying on the dashboard. It's probably useless
for anyone consuming the API outside of the chibisafe frontend.
*/
const albums = await db
.table('albums')
.where('albums.userId', user.id)
.select('id', 'name', 'nsfw', 'createdAt', 'editedAt')
.orderBy('createdAt', 'desc');
for (const album of albums) {
// Fetch the total amount of files each album has.
const fileCount = await db // eslint-disable-line no-await-in-loop
.table('albumsFiles')
.where('albumId', album.id)
.count({ count: 'id' });
// Fetch the file list from each album but limit it to 5 per album
const files = await db // eslint-disable-line no-await-in-loop
.table('albumsFiles')
.join('files', { 'files.id': 'albumsFiles.fileId' })
.where('albumId', album.id)
.select('files.id', 'files.name')
.orderBy('albumsFiles.id', 'desc')
.limit(5);
// Fetch thumbnails and stuff
for (let file of files) {
file = Util.constructFilePublicLink(req, file);
}
album.fileCount = fileCount[0].count;
album.files = files;
}
return res.json({
message: 'Successfully retrieved albums',
albums
});
}
}
class albumsDropdownGET extends Route {
constructor() {
super('/albums/dropdown', 'get', { canApiKey: true });
}
async run(req, res, db, user) {
const albums = await db
.table('albums')
.where('userId', user.id)
.select('id', 'name');
return res.json({
message: 'Successfully retrieved albums',
albums
});
}
}
module.exports = [albumsGET, albumsDropdownGET];

Some files were not shown because too many files have changed in this diff Show More