414 Commits
0.7 ... master

Author SHA1 Message Date
Luc Didry
95c5620178 🔖 — Bump version (0.18.0) 2025-10-13 09:23:52 +02:00
Luc Didry
117d619056 🔀 Merge remote-tracking branch 'origin/development' 2025-10-13 09:22:17 +02:00
Luc Didry
717b0562c2 📝 — Update CHANGELOG 2025-10-13 09:21:42 +02:00
Ghost of Sparta
64492955ff 🌐 — Translated using Weblate (Hungarian)
Currently translated at 99.2% (141 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/hu/
2025-10-13 09:14:50 +02:00
Luc Didry
54b461617e 🌐 — Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (142 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/fr_FR/
2025-10-13 09:14:50 +02:00
Luc Didry
46fcc036e6 🌐 — Translated using Weblate (French)
Currently translated at 100.0% (142 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/fr/
2025-10-13 09:14:50 +02:00
Luc Didry
e14cdd2ca1 🌐 — Translated using Weblate (French (France) (fr_FR))
Currently translated at 99.2% (141 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/fr_FR/
2025-10-13 09:14:50 +02:00
Luc Didry
bd72394835 🌐 — Translated using Weblate (French)
Currently translated at 99.2% (141 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/fr/
2025-10-13 09:14:49 +02:00
Luc Didry
476dcfc4ff 🌐 — Update translations 2025-10-13 09:04:50 +02:00
Ian Gruson
98326f53b2 🌐 — Translated using Weblate (French (France) (fr_FR))
Currently translated at 99.2% (141 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/fr_FR/
2025-10-13 08:59:06 +02:00
Luc Didry
202c529983 🔀 Merge branch 'fix-mojo-user-agent' into 'development'
Fix use of deprecated method success in Mojo::Transaction

See merge request fiat-tux/hat-softwares/lutim!97
2025-09-01 18:57:08 +02:00
Ghost of Sparta
e0fc6cfe9f 🌐 — Translated using Weblate (Hungarian)
Currently translated at 100.0% (142 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/hu/
2025-09-01 18:53:34 +02:00
Ploc
3c8d69f746 chore: update alpine version
Update alpine version from version 3.15 to version 3.22.
2025-09-01 18:42:42 +02:00
Luc Didry
ddbf43d11a 🔀 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Framasoft Weblate

See merge request fiat-tux/hat-softwares/lutim!102
2024-12-25 10:31:36 +00:00
Quentin PAGÈS
c72ba813c2 🌐 — Translated using Weblate (Occitan)
Currently translated at 100.0% (142 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/oc/
2024-12-21 22:15:42 +01:00
Quentin PAGÈS
9f5bb58eb5 🌐 — Translated using Weblate (Occitan)
Currently translated at 99.2% (141 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/oc/
2024-12-21 09:33:12 +01:00
Luc Didry
f42ab6c532 🔀 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Framasoft Weblate

See merge request fiat-tux/hat-softwares/lutim!101
2024-12-11 18:34:42 +00:00
Giuseppe De Ninno
a09c2af956 🌐 — Translated using Weblate (Italian)
Currently translated at 98.5% (140 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/it/
2024-12-11 18:28:38 +01:00
Luc Didry
b5c6acaa70 🔀 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Framasoft Weblate

See merge request fiat-tux/hat-softwares/lutim!100
2024-06-07 09:08:10 +00:00
Milo Ivir
037afeaade 🌐 — Translated using Weblate (Croatian)
Currently translated at 99.2% (141 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/hr/
2024-06-06 18:59:57 +02:00
Luc Didry
9364601ee9 🔀 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Framasoft Weblate

See merge request fiat-tux/hat-softwares/lutim!99
2024-06-05 06:15:40 +00:00
Goudarz Jafari
3e73d58325 🌐 — Translated using Weblate (Persian)
Currently translated at 11.2% (16 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/fa/
2024-06-04 23:20:23 +02:00
Goudarz Jafari
1e93cf9cc5 🌐 — Added translation using Weblate (Persian) 2024-06-04 19:37:11 +02:00
Luc Didry
81e3a00f14 🔀 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Framasoft Weblate

See merge request fiat-tux/hat-softwares/lutim!98
2024-03-12 07:31:20 +00:00
Milo Ivir
ac76032745 🌐 — Translated using Weblate (Croatian)
Currently translated at 99.2% (141 of 142 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/hr/
2024-03-10 17:38:10 +01:00
Luc Didry
c62bf20a92 🔀 Merge branch 'configurable-tmpdir' into 'development'
Ability to configure MOJO_TMPDIR via environment

See merge request fiat-tux/hat-softwares/lutim!96
2024-01-08 07:14:38 +00:00
s7b4
b862249c0c Fix use of deprecated method success in Mojo::Transaction 2024-01-06 00:51:14 +01:00
s7b4
aaa0ae4858 Ability to configure MOJO_TMPDIR via environment 2024-01-05 19:09:58 +01:00
Luc Didry
8d20b3d934 🔖 — Bump version (0.17.0) 2023-12-30 09:12:51 +01:00
Luc Didry
9563e3a701 🔀 Merge remote-tracking branch 'origin/development' 2023-12-30 09:12:19 +01:00
Luc Didry
8f5b1df337 🩹 — Fix test configuration files 2023-12-30 09:07:17 +01:00
Luc Didry
e77943b1cc — Improve accessibility on index page 2023-12-30 09:04:32 +01:00
Luc Didry
0617683c63 — Mass delete button in "My images" page (#47) 2023-12-30 08:29:27 +01:00
Luc Didry
81701b6f60 — Ask for confirmation before deleting image on index page (#92) 2023-12-30 08:03:58 +01:00
Luc Didry
82f57fc72d — Allow to configure the directory where to store the images (#125) 2023-12-30 07:42:25 +01:00
Luc Didry
4de01f5f8b 🌐 — Correcting and improving pronunciation (fix #133) 2023-12-30 07:04:00 +01:00
Luc Didry
7e15ca9174 🔀 Merge remote-tracking branch 'nicofrand/master' into development 2023-12-30 06:45:35 +01:00
Luc Didry
5c0504d5d4 🐛 — AVIF format support (fix #137) 2023-12-30 06:43:51 +01:00
nicofrand
0080e26333 [themes/korrigan] Fix gallery's duplicated images 2023-12-29 11:04:43 +01:00
Luc Didry
f5867f4fd6 🔖 — Bump version (0.16.0) 2023-12-29 08:39:14 +01:00
Luc Didry
03f63e4ec6 🔀 Merge branch 'development' 2023-12-29 08:38:27 +01:00
Luc Didry
2a466f0140 👷 — Fix missing CI stage 2023-12-29 08:31:46 +01:00
Luc Didry
613ba30752 👷 — Update the create-release and pouet-it snippet’s URLs 2023-12-29 08:27:08 +01:00
Luc Didry
125a7103a0 🩹 — Fix use of deprecated method after dependencies upgrade 2023-12-29 08:09:34 +01:00
Luc Didry
9477e1add1 🩹 — Replace old URL of the project 2023-12-29 08:02:47 +01:00
Luc Didry
03dbeecac0 — Replace moment.js with Date().toLocaleDateString(…) 2023-12-29 07:50:49 +01:00
Luc Didry
a76b240ed8 🐛 — update gallery, zip and random URLs when closing image dialog box 2023-12-29 07:43:33 +01:00
Luc Didry
5d6f715396 🩹 — Remove twitter.css minification in Makefile
And update minified CSS files
2023-12-29 07:33:31 +01:00
Luc Didry
5bf1a9d717 🎨 — Use template literals in js 2023-12-29 07:30:15 +01:00
Luc Didry
fff56a89bb 💥 No more twitter cards 2023-12-28 07:33:30 +01:00
Luc Didry
2cb07f8565 ⬆ Update jQuery 2023-12-27 08:27:13 +01:00
Luc Didry
ebf9f66e47 🔖 — Bump version (0.15.0) 2023-12-19 14:55:26 +01:00
Luc Didry
ed70fbac2e 🔀 Merge branch 'development' 2023-12-19 14:54:34 +01:00
Luc Didry
af11a1e7ec 🔀 Merge branch 'fix-134' into development 2023-12-19 14:40:40 +01:00
Luc Didry
438ce5050e — Add --nuke option to image command (fix #134) 2023-12-19 14:40:03 +01:00
Luc Didry
d67d66d0fd 🔖 — Bump version (0.14.0) 2023-12-18 04:47:19 +01:00
Luc Didry
cc03ba6d3f 🔀 Merge branch 'development' into 'master'
Development

See merge request fiat-tux/hat-softwares/lutim!94
2023-12-18 03:43:02 +00:00
Luc Didry
5b4f56b9f6 🔀 Merge branch 'update-deps' into 'development'
Update deps

See merge request fiat-tux/hat-softwares/lutim!93
2023-12-18 03:31:42 +00:00
Luc Didry
8b6766f498 🩹 — Update tests and code after dependencies upgrade 2023-12-17 09:51:06 +01:00
Luc Didry
68518dd85c ⬆️ — Upgrade dependencies 2023-12-17 05:37:50 +01:00
Luc Didry
e7d0821cff 🔖 — Bump version (0.13.0) 2023-04-26 13:04:42 +02:00
Luc Didry
121c00167c 🔀 Merge branch 'development' 2023-04-26 13:02:52 +02:00
Luc Didry
c3c3c7a780 🐛 — Fix bug introduced in !87 2023-04-26 12:26:45 +02:00
Luc Didry
47a67a428d Update CHANGELOG 2023-04-26 11:56:57 +02:00
Luc Didry
a9b514a30c 🔀 Merge branch 'issue_129' into 'development'
 Add a config flag to disable API

See merge request fiat-tux/hat-softwares/lutim!87
2023-04-26 09:52:41 +00:00
Luc Didry
680b3d7057 👷 — Fix CI 2023-02-28 08:38:26 +01:00
Luc Didry
18888c05b3 Update changelog 2023-02-27 14:25:21 +01:00
Luc Didry
d0e472fe95 🔀 Merge branch 'yechedmad' into development 2023-02-27 14:17:27 +01:00
Luc Didry
10f1bd58c5 🔥 — Remove zanata stuff 2023-02-27 13:46:45 +01:00
Luc Didry
34be40d928 Fix use of $ip 2023-02-27 12:27:38 +00:00
Luc Didry
e7d87dac3c 🔀 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!92
2022-12-31 15:50:39 +00:00
Vri
2bff3fcc51 🌐 — Translated using Weblate (German)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/de/
2022-12-24 18:38:03 +01:00
Luc Didry
c0da308963 🔀 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!91
2022-12-16 09:13:01 +00:00
Дмитрий Кузнецов
8515a2b2c7 🌐 — Translated using Weblate (Russian)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/ru/
2022-12-15 22:38:00 +01:00
Luc Didry
3dc46ebad5 🔀 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!89
2022-07-29 15:56:06 +00:00
Milo Ivir
c5a69e6128 🌐 — Translated using Weblate (Croatian)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/hr/
2022-07-24 20:36:56 +02:00
Milo Ivir
45b8e6c3e2 🌐 — Added translation using Weblate (Croatian) 2022-07-23 16:07:10 +02:00
Luc Didry
8c40ae36bc 🔀 Merge branch 'ploc-master-patch-88214' into 'development'
feat: update Docker build

See merge request fiat-tux/hat-softwares/lutim!83
2022-06-22 12:07:57 +00:00
Luc Didry
6b5e52fba3 Merge branch 'ansible-tarraform-provisioning' into 'development'
Add lutim_startup.sh

See merge request fiat-tux/hat-softwares/lutim!88
2022-05-13 08:03:43 +00:00
arunodhayamsam
5db403f040 Add lutim_startup.sh 2022-05-13 10:38:08 +05:30
Luc Didry
3a0f39761c Tester aussi le referer sans quoi ça ne peut pas fonctionner (à squasher avec le commit précédent). 2022-05-12 14:19:07 +00:00
brunob
691e0c3592 Add a config flag to disable API
fix #129
2022-05-12 15:35:43 +02:00
Luc Didry
8e4f6c7b22 Merge branch 'ansible-tarraform-provisioning' into 'development'
Adhere to ansible styling guide

See merge request fiat-tux/hat-softwares/lutim!86
2022-04-04 05:54:30 +00:00
arunodhayamsam
21b592cb51 Applied ansible styling best practices and Terraform data sorces 2022-03-31 21:28:11 +05:30
Luc Didry
c15f6dea68 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!84
2022-03-29 10:12:52 +00:00
J. Lavoie
c9a5cfdcd4 🌐 — Translated using Weblate (German)
Currently translated at 89.9% (125 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/de/
2022-03-29 12:09:03 +02:00
J. Lavoie
e4d78e5433 🌐 — Translated using Weblate (German)
Currently translated at 88.4% (123 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/de/
2022-03-29 12:09:02 +02:00
Berto Te
bb6b0c30a8 Translated using Weblate (Spanish)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/es/
2022-03-29 12:09:02 +02:00
Luc Didry
a46aab6ffa Merge branch 'ansible-tarraform-provisioning' into 'development'
Added IaC and ConfigManagement

See merge request fiat-tux/hat-softwares/lutim!85
2022-03-29 10:08:59 +00:00
arunodhayamsam
f610608aa1 Added IaC and ConfigManagement 2022-03-29 15:23:51 +05:30
Ploc
52b436657f fix: follow hadolint hints
Follow hadolint best practices in order to have a docker build that is as reliable as possible.

- first best practice is to "Pin versions in apk add. Instead of `apk add <package>` use `apk add <package>=<version>`" (see [DL3018](https://github.com/hadolint/hadolint/wiki/DL3018))
- second best practice is to use "`--no-cache` switch to avoid the need to use `--update`"
2021-12-03 16:50:45 +01:00
Ploc
4486b32da5 fix: image label name
Fix the label "name" of the image so that kaniko build does not fail:

> error building image: error building stage: failed to execute command: failed to process "Let's Upload That Image": unexpected end of statement while looking for matching single-quote
2021-12-03 16:50:34 +01:00
Ploc
17c862ae19 chore: update alpine docker source image
Update alpine docker source image from version 3.9 to version 3.15.
2021-12-03 16:50:28 +01:00
Ploc
124ca306b4 chore: merge shell into Dockerfile
Merge build.sh shell into Dockerfile.
2021-12-03 16:50:22 +01:00
Ploc
7d845b9e64 chore: merge subshell into shell
Merge install-dev-env.sh subshell into build.sh shell.
2021-12-03 16:50:16 +01:00
Ploc
3dd1fdca56 chore: use imagemagick package from alpine
Use imagemagick package from alpine instead of rebuilding it from source.
2021-12-03 16:50:05 +01:00
Ploc
87efb615bb feat: container is able to run as non-root 2021-12-03 16:50:00 +01:00
Ploc
e17a51d8d5 chore: update alpine package list on package install 2021-12-03 16:49:50 +01:00
Ploc
c40d22427a feat: expose container port 2021-12-03 16:49:37 +01:00
Luc Didry
d10ecc41fb 🐛 — Avoid DB conflicts when provisioning 2021-06-20 07:59:26 +02:00
nicofrand
4384bd1b9f Add korrigan theme 2021-05-12 16:33:35 +02:00
Luc Didry
614a561e00 🔀 Merge branch 'Quent--y/lutim-master' into development 2021-01-15 08:01:31 +01:00
Luc Didry
09887ed01a Merge branch 'feature_png_optimization' into 'development'
Lossless PNG optimization

See merge request fiat-tux/hat-softwares/lutim!81
2021-01-15 07:56:02 +01:00
Aleksey Lobanov
f68353fa1b assets: png optimization 2021-01-15 07:56:02 +01:00
Luc Didry
6aa0c43b55 Merge branch 'development' into 'development'
✏️ Replace name of wrong software name in config file

See merge request fiat-tux/hat-softwares/lutim!80
2021-01-15 07:56:01 +01:00
Luc Didry
cf3f0e0250 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!79
2021-01-15 07:56:01 +01:00
Benpro
31df50d6c9 ✏️ Replace name of wrong software name in config file
Commit 247cb41c has added config variables but with a small typo in the
comments
2021-01-15 07:56:01 +01:00
Валентин Бородко
ab6febf2ea Translated using Weblate (Russian)
Currently translated at 99.2% (138 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/ru/
2021-01-15 07:56:00 +01:00
Валентин Бородко
a6b0a9961a Translated using Weblate (Russian)
Currently translated at 93.5% (130 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/ru/
2021-01-15 07:56:00 +01:00
Luc Didry
d939ec30c7 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!77
2021-01-15 07:55:59 +01:00
Filip Bengtsson
62519948a6 Translated using Weblate (Swedish)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/sv/
2021-01-15 07:55:59 +01:00
Luc Didry
6ee0dc022c 🔥 — Remove tap.xml from git 2021-01-15 07:55:58 +01:00
Luc Didry
10daa1cd02 Merge branch 'improve-tests' into 'development'
Improve tests

See merge request fiat-tux/hat-softwares/lutim!76
2021-01-15 07:55:57 +01:00
Luc Didry
f4b5a780f6 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!75
2021-01-15 07:55:57 +01:00
Luc Didry
1142006456 👷 — Improve CI
- Refactor .gitlab-ci.yml
- Update Makefile
- Introduce junit test output
- Improve minion test
2021-01-15 07:55:57 +01:00
Luc Didry
9d265879a5 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!74
2021-01-15 07:55:56 +01:00
Konstantin Timashov
d296fdcd2c Translated using Weblate (Russian)
Currently translated at 94.2% (131 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/ru/
2021-01-15 07:55:56 +01:00
roberto marcolin
33b81c9e40 Translated using Weblate (Italian)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/it/
2021-01-15 07:55:47 +01:00
Quentin
d11a70c7f6 Update moment-with-locales.min.js to add Occitan like the master Github of moment + change oc-lnc to oc to fit the project iso code used. 2021-01-14 18:50:36 +01:00
Luc Didry
456b8cfc67 🔖 — Bump version (0.12.1) 2020-10-08 09:24:52 +02:00
Luc Didry
34a9e7b1e8 ⬆️ — Update jQuery 2020-10-08 09:23:56 +02:00
Luc Didry
fe286c947b 🏷 Bump version (0.12.0) 2020-04-17 18:53:47 +02:00
Luc Didry
1bf33e5d3b Merge branch 'development' 2020-04-17 18:52:45 +02:00
Luc Didry
72a5e2a543 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!71
2020-04-10 20:38:25 +02:00
Balázs Meskó
2ee0d9d9d1 Translated using Weblate (Hungarian)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/hu/
2020-04-10 19:08:46 +02:00
Luc Didry
90fd6ee30f Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!70
2020-04-06 08:23:05 +02:00
Balázs Meskó
acc4c5849b Translated using Weblate (Hungarian)
Currently translated at 91.3% (127 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/hu/
2020-04-04 12:08:44 +02:00
spf
7a14ad11e8 Translated using Weblate (French)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/fr/
2020-04-04 12:08:43 +02:00
Luc Didry
17f0beb7cf Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!69
2020-04-01 15:21:14 +02:00
Balázs Meskó
c12977b0c0 Translated using Weblate (Hungarian)
Currently translated at 73.3% (102 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/hu/
2020-04-01 15:09:42 +02:00
Luc Didry
e5740c820f Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!68
2020-04-01 09:58:49 +02:00
Balázs Meskó
045f4ae5ea Translated using Weblate (Hungarian)
Currently translated at 52.5% (73 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/hu/
2020-04-01 09:48:05 +02:00
Luc Didry
521245ce87 Merge branch 'weblate-lutim-default-theme' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!67
2020-03-09 17:51:06 +01:00
Quentin PAGÈS
226cb56b6f Translated using Weblate (Occitan)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/oc/
2020-03-09 17:30:55 +01:00
Luc Didry
63d7e1cbe8 Translated using Weblate (Swedish)
Currently translated at 96.4% (134 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/sv/
2020-03-09 17:23:09 +01:00
Luc Didry
5a79b4ced2 Translated using Weblate (Russian)
Currently translated at 86.3% (120 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/ru/
2020-03-09 17:23:09 +01:00
Luc Didry
0aafa19cf7 Translated using Weblate (Occitan)
Currently translated at 97.8% (136 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/oc/
2020-03-09 17:23:08 +01:00
Luc Didry
b078707ddf Translated using Weblate (Italian)
Currently translated at 97.8% (136 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/it/
2020-03-09 17:23:08 +01:00
Luc Didry
f0254a9247 Translated using Weblate (Hungarian)
Currently translated at 48.2% (67 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/hu/
2020-03-09 17:23:07 +01:00
Luc Didry
acf91e799f Translated using Weblate (French (France))
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/fr_FR/
2020-03-09 17:23:07 +01:00
Luc Didry
56dea43802 Translated using Weblate (French)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/fr/
2020-03-09 17:23:06 +01:00
Luc Didry
08d301688f Translated using Weblate (Spanish)
Currently translated at 97.8% (136 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/es/
2020-03-09 17:23:06 +01:00
Luc Didry
79835941a5 Translated using Weblate (German)
Currently translated at 86.3% (120 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/de/
2020-03-09 17:23:05 +01:00
Luc Didry
1efae05eda Translated using Weblate (Arabic)
Currently translated at 86.3% (120 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/default-theme/ar/
2020-03-09 17:23:05 +01:00
Luc Didry
3a3e42b352 🌐 — Update en translation + project’s URL in about page 2020-03-09 15:39:51 +01:00
Luc Didry
dbfaab5f7e Merge branch 'fix-112' into 'development'
Fix #112 — Add watermarking feature

See merge request fiat-tux/hat-softwares/lutim!66
2020-03-09 15:14:56 +01:00
Luc Didry
b7edbc73cd Fix #112 — Add watermarking feature
- tiling, unique or no watermark available in UI
- watermark enforcing available in configuration
2020-03-09 14:55:36 +01:00
Luc Didry
a416a8ad07 ♻ — Use a DefaultConfig module 2020-03-09 08:50:40 +01:00
Luc Didry
2071a84ff1 Merge branch 'weblate-lutim-development' into 'development'
Translations update from Weblate

See merge request fiat-tux/hat-softwares/lutim!65
2020-03-04 11:19:59 +01:00
ButterflyOfFire
4ed87a082f 🌐 Translated using Weblate (Arabic)
Currently translated at 89.2% (124 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/ar/
2020-03-04 11:08:32 +01:00
Luc Didry
fcba4f25b5 Merge branch 'weblate-lutim-development' into 'development'
Update from Weblate

See merge request fiat-tux/hat-softwares/lutim!64
2020-03-01 18:38:05 +01:00
ButterflyOfFire
eacce37d88 🌐 Translated using Weblate (Arabic)
Currently translated at 88.4% (123 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/ar/
2020-02-29 17:08:30 +01:00
Alberto Teira
e075543a0b 🌐 Translated using Weblate (Spanish)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/es/
2020-02-26 15:17:12 +01:00
Filip Bengtsson
4e9169d5f1 🌐 Translated using Weblate (Swedish)
Currently translated at 98.5% (134 of 136 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/sv/
2020-02-25 02:08:29 +01:00
Balázs Úr
8d7f3d18e1 🌐 Translated using Weblate (Hungarian)
Currently translated at 50.3% (70 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/hu/
2020-02-25 02:08:28 +01:00
Filip Bengtsson
e1f62f9d46 🌐 Translated using Weblate (Swedish)
Currently translated at 93.3% (127 of 136 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/sv/
2020-02-22 22:08:28 +01:00
Filip Bengtsson
366bd28e5d 🌐 Translated using Weblate (Swedish)
Currently translated at 80.1% (109 of 136 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/sv/
2020-02-02 02:07:49 +01:00
Filip Bengtsson
7884280a46 🌐 Translated using Weblate (Swedish)
Currently translated at 77.9% (106 of 136 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/sv/
2020-01-03 22:14:39 +01:00
Filip Bengtsson
678eb86b27 🌐 Translated using Weblate (Swedish)
Currently translated at 68.4% (93 of 136 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/sv/
2020-01-01 03:14:32 +01:00
Filip Bengtsson
891c0fc0c2 🌐 Added translation using Weblate (Swedish) 2019-12-31 01:24:14 +01:00
Luc Didry
8701c1a99e Merge branch 'weblate-lutim-development' into 'development'
Update from Weblate

See merge request fiat-tux/hat-softwares/lutim!63
2019-11-19 10:40:09 +01:00
ButterflyOfFire
12901f00d2 🌐 Translated using Weblate (Arabic)
Currently translated at 79.1% (110 of 139 strings)

Translation: Lutim/Default theme
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/ar/
2019-11-19 10:11:55 +01:00
Luc Didry
0973ca335d 👷 Disable cover job for tags 2019-11-16 16:21:48 +01:00
Luc Didry
8c2996fb31 🌐 Update Weblate stuff 2019-11-16 16:19:55 +01:00
Luc Didry
0ddf81573c 🌐 Use Weblate for translations 2019-11-16 16:18:12 +01:00
Luc Didry
442c2c35ed 🏷 Bump version (0.11.6) 2019-11-16 16:14:49 +01:00
Luc Didry
1d9c1d9d95 Merge branch 'development' into 'master'
Time for a new release (0.11.6)

Closes #105

See merge request fiat-tux/hat-softwares/lutim!62
2019-11-16 16:12:58 +01:00
Luc Didry
b701575e03 📝 Update i18n doc and other projects used on README.md 2019-11-16 16:04:56 +01:00
Luc Didry
8bc6157325 🌐 Update lutim.pot 2019-11-16 15:51:04 +01:00
Luc Didry
ef8bdb6c83 Update CHANGELOG and AUTHORS.md 2019-11-16 15:49:58 +01:00
Armando Lüscher
e0feb50dec Fix the group button links to make the whole button a link. 2019-11-16 15:45:09 +01:00
Luc Didry
787193a1f3 Merge branch 'feat-docker' into 'development'
Dockerized

See merge request fiat-tux/hat-softwares/lutim!56
2019-11-16 15:44:05 +01:00
Armando Lüscher
453b476b6a Bump toastify-js to version 1.4.0 2019-11-16 15:41:49 +01:00
Armando Lüscher
420c726258 Fix some minor typos and grammar. 2019-11-16 15:41:30 +01:00
Luc Didry
0f31fe9bd1 👷 Improve CI 2019-11-16 15:28:35 +01:00
Luc Didry
2fa9da7fa7 Merge branch 'weblate-lutim-development' into 'development'
Update from Weblate

See merge request fiat-tux/hat-softwares/lutim!61
2019-11-16 15:02:30 +01:00
Luc Didry
6ba40384bd 🌐 Translated using Weblate (Occitan)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Development
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/oc/
2019-11-16 14:46:21 +01:00
Luc Didry
d5d2720719 🌐 Translated using Weblate (Italian)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Development
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/it/
2019-11-16 14:46:20 +01:00
Luc Didry
0627a3708d 🌐 Translated using Weblate (French (France))
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Development
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/fr_FR/
2019-11-16 14:46:19 +01:00
Luc Didry
ce2af1df23 🌐 Translated using Weblate (French)
Currently translated at 100.0% (139 of 139 strings)

Translation: Lutim/Development
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/fr/
2019-11-16 14:46:19 +01:00
Luc Didry
1ebe905e3b Merge branch 'weblate-lutim-development' into 'development'
Update from Weblate

See merge request fiat-tux/hat-softwares/lutim!60
2019-11-16 14:32:49 +01:00
ButterflyOfFire
eed9bccd45 Translated using Weblate (Arabic)
Currently translated at 74.1% (103 of 139 strings)

Translation: Lutim/Development
Translate-URL: https://weblate.framasoft.org/projects/lutim/development/ar/
2019-11-16 10:11:46 +01:00
Luc Didry
87e0e4e28e Fix #105 Add a "select all" checkbox on /myfiles 2019-09-20 09:25:09 +02:00
Luc Didry
dc3735b31b 🔕 — Remove "Support the author" dropdown 2019-09-19 17:19:30 +02:00
Luc Didry
56e0849533 [i18n] Update translations 2019-09-19 17:14:24 +02:00
Luc Didry
a262ed325d Merge branch 'typos' into 'development'
Fix some minor typos and grammar.

See merge request fiat-tux/hat-softwares/lutim!57
2019-09-19 17:14:24 +02:00
Armando Lüscher
56ffd65fe8 Fix some minor typos and grammar. 2019-09-19 17:14:01 +02:00
Arnaud de Mouhy
b65f37293b Dockerized 2019-04-24 10:37:16 +02:00
Luc Didry
4a37e722fe Bump version (0.11.5) 2019-04-19 18:18:46 +02:00
Luc Didry
55dec02d71 Merge branch 'development' 2019-04-19 18:17:48 +02:00
Luc Didry
78da85115c Update CHANGELOG 2019-04-19 18:11:24 +02:00
Luc Didry
d709a607af Fix #97 — Revert "Fix #90 — catch Image::Magick problems"
This reverts commit 998db0cb90.
2019-04-19 18:11:24 +02:00
Luc Didry
dee0101496 [gallery] Fix #99 — Use JS to find images width/heigth if not provided 2019-04-19 18:11:24 +02:00
Luc Didry
6cdcd9a88d Remove transifex files 2019-04-19 18:11:24 +02:00
Luc Didry
8994ceb405 [CI] Push release notes to Gitlab tag 2019-04-19 18:11:24 +02:00
Luc Didry
ffcb48ba84 Bump version (0.11.4) 2018-11-18 11:02:52 +01:00
Luc Didry
285e3a0811 [i18n] Update it translation (100% complete \o/) 2018-11-16 21:55:42 +01:00
Luc Didry
998db0cb90 Fix #90 — catch Image::Magick problems 2018-11-07 13:20:28 +01:00
Luc Didry
0cfd5b2533 [i18n] update arabic translation 2018-11-06 12:56:47 +01:00
Luc Didry
a03e3549a8 [i18n] Add it translation 2018-11-06 12:56:03 +01:00
Luc Didry
b8d2defe61 Fix gallery bug 2018-07-31 13:23:56 +02:00
Luc Didry
c030e371ae Fix stats graph if no upload happens in the time range 2018-07-31 12:55:15 +02:00
Luc Didry
85d0aabef8 Add upload_enabled info to server infos endpoint 2018-07-30 13:45:48 +02:00
Luc Didry
da8a0642ec Fix year_disabled_in_month_pct stat 2018-07-30 13:36:24 +02:00
Luc Didry
9696da7c01 Bump version (0.11) 2018-07-29 22:19:43 +02:00
Luc Didry
4585b7af8f [i18n] Update translations 2018-07-29 22:18:40 +02:00
Luc Didry
3feeeac39d Merge branch 'development' into 'master'
Time for a new release

Closes #82, #61, #84, #67, #70, #5, #76, #79, #56, #77, #78, #48, #80, #83 et #81

See merge request luc/lutim!51
2018-07-29 22:06:42 +02:00
Luc Didry
f7b81e701f Remove Bitcoin support 2018-07-29 22:01:50 +02:00
Luc Didry
8c7a2a75a1 [i18n] Update translations 2018-07-29 21:59:02 +02:00
Luc Didry
e1533a8804 Add /about/image forgotten endpoint 2018-07-29 21:33:41 +02:00
Luc Didry
f8b28525f6 Merge branch 'fix-82' into 'development'
Fix #82 - Add optional authentication

See merge request luc/lutim!50
2018-07-29 20:19:43 +02:00
Luc Didry
182f4ccf4e Fix #82 - Add optional authentication 2018-07-29 20:15:01 +02:00
Quentin
07344aa0c9 Update oc.po 2018-07-29 18:49:19 +02:00
Quentin
b8d74beea3 Update oc.po 2018-07-29 18:48:48 +02:00
Luc Didry
985a781a5f Merge branch 'fix-61' into 'development'
Fix #61 - Add modal to modify expiration delay from "myfiles" page

See merge request luc/lutim!49
2018-07-29 17:48:15 +02:00
Luc Didry
ee936c1f79 Fix #61 - Add modal to modify expiration delay from "myfiles" page 2018-07-29 17:43:32 +02:00
Luc Didry
9ea2234f7d [i18n] Update translations 2018-07-29 15:58:11 +02:00
Luc Didry
7c199a5c87 Fix gallery/zip/random link generator on myfiles page 2018-07-29 15:48:50 +02:00
Luc Didry
4d5fed4d02 Merge branch 'fix-84' into 'development'
Fix #84 - Add link to generate random file in collection

See merge request luc/lutim!48
2018-07-29 15:48:49 +02:00
Luc Didry
b088db9c5e [i18n] Update translations 2018-07-29 15:48:49 +02:00
Luc Didry
ea358d4a4c Fix #84 - Add link to generate random file in collection 2018-07-29 15:48:49 +02:00
Luc Didry
47b38daad5 Merge branch 'fix-67' into 'development'
Fix #67 - Add localStorage export and import feature

See merge request luc/lutim!47
2018-07-29 15:48:49 +02:00
Luc Didry
46dcb0256f Add fix #70 item in CHANGELOG 2018-07-29 15:48:49 +02:00
Luc Didry
8fe738adc4 Fix #67 - Add localStorage export and import feature 2018-07-29 15:48:49 +02:00
Luc Didry
9b90119061 Merge branch 'fix-5' into 'development'
Fix #5 - Allow to use a fixed domain

See merge request luc/lutim!46
2018-07-29 15:48:49 +02:00
Luc Didry
78bb3f37c7 Merge branch 'fix-70' into 'development'
Fix #70 - Fix stats files generation with non-default theme

See merge request luc/lutim!45
2018-07-29 15:48:48 +02:00
Luc Didry
f2f785e6d4 Fix #5 - Allow to use a fixed domain 2018-07-29 15:48:48 +02:00
Luc Didry
15e587464d Merge branch 'fix-76' into 'development'
Fix #76 Add .zip file check in the tests

See merge request luc/lutim!44
2018-07-29 15:48:48 +02:00
Luc Didry
4ef5e67f4b Fix #70 - Fix stats files generation with non-default theme 2018-07-29 15:48:48 +02:00
Luc Didry
ef090954c3 Merge branch 'fix-79' into 'development'
Fix #79 Add CLI command

See merge request luc/lutim!43
2018-07-29 15:48:48 +02:00
Luc Didry
b42bf08363 Fix #76 Add .zip file check in the tests
This commit is dedicated to Nartagnan, who is supporting me with Ğ1.
Many thanks :-)
2018-07-29 15:48:48 +02:00
Luc Didry
b5ea181bea Improve image CLI command
- Add CLI command to remove images
- Add CLI command to search images based on the uploader's IP address

This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2018-07-29 15:48:48 +02:00
Luc Didry
b84420e6dc [i18n] Update oc translation 2018-07-29 15:48:47 +02:00
Luc Didry
701a78ac94 Add CLI command to print informations about images
This commit is dedicated to JCB, who is supporting me with Ğ1.
Many thanks :-)
2018-07-29 15:48:47 +02:00
Luc Didry
0d1ca8aa98 [i18n] Use ISO::639_1
This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2018-07-29 15:48:47 +02:00
Luc Didry
fbe3c3bc13 [zanata] update Makefile to push or pull only on master and dev branch 2018-07-29 15:48:47 +02:00
Luc Didry
a7f2fd051d [i18n] update locales 2018-07-29 15:48:47 +02:00
Luc Didry
c18e6c84af [zanata] update CI 2018-07-29 15:48:47 +02:00
Luc Didry
10eafcfdc3 Merge branch 'fix-56' into 'development'
Fix #56 Add a message saying how many images there is in the gallery

See merge request luc/lutim!42
2018-07-29 15:48:47 +02:00
Luc Didry
033ff3c54f [zanata] update CI 2018-07-29 15:48:46 +02:00
Luc Didry
9645c8a2f3 Fix #56 Add a message saying how many images there is in the gallery 2018-07-29 15:48:46 +02:00
Luc Didry
4193f03d7a Remove @framasky as default tweet_card_via setting 2018-07-29 15:48:46 +02:00
Luc Didry
8a92ef9567 Merge branch 'fix-77' into 'development'
Fix #77 Add X-Content-Type-Options, X-XSS-Protection, X-Frame-Options headers

See merge request luc/lutim!41
2018-07-29 15:48:46 +02:00
Luc Didry
ed47431415 Merge branch 'fix-78' into 'development'
Fix #78 Add CSP Header

See merge request luc/lutim!40
2018-07-29 15:48:46 +02:00
Luc Didry
247cb41cc5 Fix #77 Add X-Content-Type-Options, X-XSS-Protection, X-Frame-Options headers 2018-07-29 15:48:46 +02:00
Luc Didry
acf0ec75e7 [i18n] Update translations 2018-07-29 15:48:45 +02:00
Luc Didry
83a8fbeeeb Fix #78 Add CSP Header
+ update morris and raphael graph libraries
+ some changes in "myfiles" table
2018-07-29 15:48:45 +02:00
Luc Didry
707e434d2c [zanata] Fix Makefile for CI 2018-07-29 15:48:45 +02:00
Luc Didry
8a412a33c3 Update Changelog 2018-07-29 15:48:45 +02:00
Luc Didry
3eb8db123e Fix #48 No more scroll to top on click + add notifications to actions 2018-07-29 15:48:45 +02:00
Luc Didry
dd9dc7bd7d Fix #80 Gzip static assets with Mojolicious::Plugin::GzipStatic 2018-07-29 15:48:45 +02:00
Luc Didry
c91d46bc68 Fix #83 Use Mojolicious::Plugin::Chi + Add memcached ability 2018-07-29 15:48:45 +02:00
Luc Didry
8dd2ab87f9 Add code coverage in CI 2018-07-29 15:48:44 +02:00
Luc Didry
b3ec85daf3 Better .gitlab-ci.yml
- change order
- use postgresql service
- install only needed deps

This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2018-07-29 15:48:44 +02:00
Luc Didry
dc2b17c7af Fix #81 Allow to install only needed deps
This commit is dedicated to Agnès Maillard, who is supporting me with Ğ1.
Many thanks :-)
2018-07-29 15:48:44 +02:00
Luc Didry
4c53669caa [zanata] Use new Zanata URL 2018-07-29 15:47:58 +02:00
Luc Didry
b5e73e09bf Change internationalization link 2018-07-19 17:05:20 +02:00
Luc Didry
e62cb50ab1 Fix bug in cache system 2018-05-07 18:27:07 +02:00
Luc Didry
cf9504bb30 Fix another bug on zip file creation (missing IV on decryption) 2018-04-26 21:10:02 +02:00
Luc Didry
36a007d2f7 Bump version (0.10.2) 2018-04-24 18:39:28 +02:00
Luc Didry
f013359314 Fix Zip files creation 2018-04-24 18:37:51 +02:00
Luc Didry
1fda560056 Fix #73 bug on theme creation 2018-04-15 13:39:33 +02:00
Luc Didry
adc2d00552 Merge branch 'development' 2018-04-07 12:19:41 +02:00
Luc Didry
95f4c372e3 Bump version 2018-04-07 12:18:43 +02:00
Luc Didry
4f39a86b0a Add partial index on SQLite 2018-04-07 12:11:27 +02:00
Luc Didry
09f1492486 Create merge conflict on zanata.xml 2018-04-07 12:07:43 +02:00
Luc Didry
163a1e2a66 Disable images' counter option 2018-04-07 11:53:51 +02:00
Luc Didry
f26e7ba5ff Add option to disable logs 2018-04-07 11:44:34 +02:00
Luc Didry
7d29e3d3dd [i18n] Correctly handle regionalized languages 2018-04-03 12:03:35 +02:00
Luc Didry
a7bb73b158 Merge branch 'development' into 'development'
suite de luc/lutim!26

See merge request luc/lutim!33
2018-03-19 10:42:47 +01:00
brunob
dc371daf15 suite de luc/lutim!26
ref #63 ; my bad...
2018-03-19 10:36:39 +01:00
Luc Didry
bb097294e2 Built-in image cache system 2018-03-17 15:55:34 +01:00
Luc Didry
cfab86c4b4 Move some tasks to recurring instead of being in after_dispatch hook 2018-03-17 10:30:09 +01:00
Luc Didry
fb562dd9b8 [postgresql] Add partial index to speed up some queries 2018-03-17 10:24:47 +01:00
Luc Didry
0e63bf766e Merge remote-tracking branch 'origin/master' into development 2018-03-12 10:09:01 +01:00
Luc Didry
8ab966f83c Update translations 2018-03-12 10:05:24 +01:00
Luc Didry
86b3652471 Bump version 2018-03-11 23:07:22 +01:00
Luc Didry
c29acb8944 Update translations 2018-03-11 23:06:55 +01:00
Alexander Sapozhnikov
564a0ef69d Russian translation added 2018-03-11 23:06:50 +01:00
Luc Didry
781d1bc721 Update translations 2018-03-11 22:44:08 +01:00
Luc Didry
7cf9f8b6ec Merge branch 'shoorick/lutim-master' into development 2018-03-11 21:44:51 +01:00
Alexander Sapozhnikov
1405b078c0 Russian translation added 2018-03-12 00:58:34 +05:00
Luc Didry
95db024ab8 Merge remote-tracking branch 'origin/master' into development 2018-03-11 14:34:27 +01:00
Luc Didry
fe07945af7 Replace Twitter account URL with Mastodon one 2018-03-11 14:30:35 +01:00
Luc Didry
a59b728aa6 Merge remote-tracking branch 'origin/master' into development 2018-03-11 10:29:15 +01:00
Luc Didry
f27cf293dd Create a conflict on zanata.xml between master and dev branches 2018-03-11 10:26:54 +01:00
Luc Didry
85c025f2c3 Zanata.xml -> development branch 2018-03-11 10:25:28 +01:00
Luc Didry
0295ba3666 Use woff2 format for HennyPenny font 2018-03-11 10:14:10 +01:00
Luc Didry
485436aa67 Bump version 2018-03-09 23:45:21 +01:00
Luc Didry
0e0e6da677 Fix langage drop-down 2018-03-09 23:44:12 +01:00
Luc Didry
781e96c073 Allow to use HTML in broadcast message 2018-03-09 23:43:10 +01:00
Luc Didry
fb610e1a80 Remove old targets from Makefile 2018-03-09 23:42:40 +01:00
Luc Didry
13b7ece5e2 Add db_path default value 2018-03-09 23:19:28 +01:00
Luc Didry
7446edba48 Bump version 2018-03-09 20:03:32 +01:00
Luc Didry
35eb60a27f Remove now unused Lutim::DB::SQLite 2018-03-09 20:03:07 +01:00
Luc Didry
0b1f21be31 Fix push-trad-to-zanata.sh perms 2018-03-09 19:57:33 +01:00
Luc Didry
b1e1e88af4 Merge branch 'development' into 'master'
Add script to push po files to zanata

See merge request luc/lutim!31
2018-03-09 19:54:33 +01:00
Luc Didry
68d3ddb61a Add script to push po files to zanata 2018-03-09 19:52:19 +01:00
Luc Didry
b6ac7d2818 Set zanata.xml to master version 2018-03-09 19:51:33 +01:00
Luc Didry
da36aad520 Merge branch 'development' into 'master'
Development

Closes #64, #68, #66 et #63

See merge request luc/lutim!30
2018-03-09 19:47:37 +01:00
Luc Didry
d00f151a2e Merge branch 'fix-64' into 'development'
Fix #64

See merge request luc/lutim!29
2018-03-09 19:43:16 +01:00
Luc Didry
9c79c9a392 Merge branch 'fix-68' into 'development'
Fix #68

See merge request luc/lutim!28
2018-03-09 19:39:01 +01:00
Luc Didry
eb575519ed Fix #64 2018-03-09 19:36:55 +01:00
Luc Didry
125bda1687 Fix #68 2018-03-09 19:33:07 +01:00
Luc Didry
b5836d88bb Merge branch 'fix-66' into 'development'
Fix #66

See merge request luc/lutim!27
2018-03-09 19:27:19 +01:00
Luc Didry
8daef83846 Fix #66
This commit is dedicated to Squeeek, who is supporting me with Ǧ1.
Many thanks :-)
2018-03-09 18:52:00 +01:00
Luc Didry
883004b7b6 Use random Initialization Vector for encryption
+ now use Mojo::SQLite instead of ORLite

This commit is dedicated to Antonio Ferreira, who is supporting me with Ǧ1.
Many thanks :-)
2018-03-09 16:53:38 +01:00
Luc Didry
5a5e629e29 Rename this informations in these details 2018-03-03 18:35:50 +01:00
Luc Didry
6daabaf054 Zanata integration 2018-03-03 18:12:40 +01:00
brunob
f94d72f978 précision pour le format de la valeur de conf proposed_delays 2018-03-03 18:02:35 +01:00
brunob
41abd109b6 Fix #63 : ajout d'une option de conf pour personnaliser les délais proposés
Pas certain que les modifications dans lib/ soient nécessaires, dans le doute, je les envoie.
2018-03-03 18:02:35 +01:00
Luc Didry
91d2be631b Regenerate locales with -u option + fr update 2018-03-03 18:02:35 +01:00
Luc Didry
56b87bb7c7 Fix ar.po (strings were deleted in previous merge) 2018-03-03 18:02:35 +01:00
Butterflyoffire Butterflyoffire
ab48e8976f Update ar.po. Left 9 strings to translate. 2018-03-03 18:02:35 +01:00
Luc Didry
fa5c3e465b Change bitcoin address 2018-03-03 18:02:35 +01:00
Luc Didry
474680e600 Rename template.pot to lutim.pot 2018-03-03 18:02:35 +01:00
Luc Didry
b19a3f6005 Delete Mojolicious::Plugin::AssetPack from cpanfile 2018-03-03 18:02:35 +01:00
Quentin
8e7065e714 Update oc.po 2018-03-03 18:02:35 +01:00
Luc Didry
2529234df9 template.pot generation 2018-03-03 18:02:35 +01:00
Luc Didry
5ad1caea68 Make gallery generation really sequential 2018-03-03 18:02:35 +01:00
Luc Didry
ef1297be7d Use Photoswipe for the gallery instead of Unite gallery 2018-03-03 18:02:35 +01:00
Luc Didry
d6b59c45a6 Fix Lutim::Plugin::Helpers needed in Mounter.pm 2018-03-03 18:02:35 +01:00
Luc Didry
59000d53d6 Add language changing dropdown 2018-03-03 18:02:35 +01:00
Luc Didry
4e1d36ce60 Small default UI change 2018-03-03 18:02:35 +01:00
Luc Didry
bcb0710fde Fix render_not_found crash (from a Mojolicious update) 2018-03-03 18:02:35 +01:00
Luc Didry
799124a58d Use Mojolicious::Plugin::StaticCache instead of AssetPack 2018-03-03 18:02:35 +01:00
Luc Didry
c9a6fd2be4 Change for a non-fluid layout 2018-03-03 18:02:35 +01:00
Luc Didry
9958a9cca9 Include arabic translation in Makefile, theme command + update AUTHORS 2018-03-03 18:02:35 +01:00
Butterflyoffire Butterflyoffire
6b012b7382 Adding i18n arabic. 2018-03-03 18:02:35 +01:00
Luc Didry
6921496a19 Update Changelog 2018-02-07 22:12:46 +01:00
Luc Didry
6ab6b76c8f Fix #65 2018-02-07 22:11:37 +01:00
Luc Didry
8b9791b6b4 Update AUTHORS.md 2018-01-15 09:14:26 +01:00
Luc Didry
6f43566664 Fix bug if dbtype not configured in lutim.conf
This commit is dedicated to Liandri, who is supporting me with great bzh food.
Many thanks :-)
2017-12-22 19:17:57 +01:00
Luc Didry
e7e02931ac Fix bug resulting in no EXIF tags deletion 2017-11-18 17:37:41 +01:00
Luc Didry
210a2a8df2 Update CHANGELOG for 0.8.5 2017-07-09 13:44:32 +02:00
Luc Didry
41e5f292eb Fix hennypenny.css for asset pack 2017-07-09 13:40:19 +02:00
Luc Didry
72f0469674 Udpate Changelog - release 0.8.4 2017-06-24 17:46:04 +02:00
Luc Didry
a842311304 Mitigate a bug using the same empty record twice
On the official instance, which is heavily used, some empty records are
used twice since the migration to PostgreSQL. Trying to choose randomly
among the available empty records to fix that and immediatly make them
not empty (add a fake path).

+ force lowest version of Net::SSLeay used, since the latest (more or
less) version is needed on Debian Stretch.
2017-06-24 17:43:29 +02:00
Luc Didry
ce84d403df Fix donut stats call 2017-06-15 10:29:25 +02:00
Luc Didry
ec6b9ce028 Update cpanfile: enforce Mojolicious::Plugin::AssetPack version 2017-06-14 12:00:35 +02:00
Luc Didry
321b8bbf97 Update CHANGELOG - 0.8.1 2017-06-13 23:08:08 +02:00
Luc Didry
78ce5dc69d Fix CI oblivion 2017-06-13 22:42:27 +02:00
Luc Didry
82289ece59 Fix #46 Server error if trying to zip an unexisting file 2017-06-13 22:10:59 +02:00
Luc Didry
32ca358886 Release 0.8 2017-06-13 20:08:02 +02:00
Luc Didry
c5dac2d5e9 Merge branch 'development' into 'master'
Merge development

Closes #40, #14, #27, #33, #39, and #9

See merge request !19
2017-06-13 19:57:50 +02:00
Luc Didry
88b77f91fb Fix CI 2017-06-12 21:41:03 +02:00
Quentin
75b645e6d4 Update oc.po 2017-06-12 19:38:40 +02:00
Quentin
9cfb694779 Update oc.po 2017-06-12 18:40:37 +02:00
Luc Didry
37b6f82f32 Add lutim-minion@.service 2017-06-11 20:45:44 +02:00
Luc Didry
65403d934c Update CI configuration
This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2017-06-11 19:58:07 +02:00
Luc Didry
36bae6e042 Add Minion support
This commit is dedicated to Brigitte, the queen of elves, who is supporting me.
Many thanks :-)
2017-06-11 19:56:59 +02:00
Luc Didry
2a0f2ef4a2 Improve cache (and so, load speed)
- Add Cache-control headers for static files
- Put almost all js/css stuff outside template
2017-06-11 11:25:34 +02:00
Luc Didry
54374765e7 Update .gitignore 2017-06-08 23:15:32 +02:00
Luc Didry
0cb82fee0b Update .gitlab-ci.yml (add test-pg and podcheck) 2017-06-08 23:13:46 +02:00
Luc Didry
b7e799353f Add stats in JSON format 2017-06-08 21:27:51 +02:00
Luc Didry
c5831168af Fix #40 2017-06-08 01:15:23 +02:00
Luc Didry
8a8e331de9 Merge branch 'issue-14-mr-5' into 'development'
Fix #14 and !5 Allow to paste images to upload them

See merge request !17
2017-06-07 23:47:52 +02:00
Luc Didry
790da8deeb Fix #14 and !5 Allow to paste images to upload them
This wouldn't have been possible without the great work of Alexis
Clairet (MR !5). I had to close the MR and report his work because of
the many changes in Lutim since he worked on it.

This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2017-06-07 23:43:14 +02:00
Luc Didry
c7b15dc952 Small changes for my git pre-commit hook 2017-06-07 22:52:33 +02:00
Luc Didry
4f8a27a10b Update Changelog 2017-06-07 22:50:29 +02:00
Luc Didry
6014ea4889 Fix bug: the localStorage wasn't modified if image's delay was modified
This commit is dedicated to Brigitte, the queen of elves, who is supporting me.
Many thanks :-)
2017-06-07 22:47:41 +02:00
Luc Didry
e389869414 Fix bug in image's delay modification 2017-06-07 22:00:10 +02:00
Luc Didry
c9dda1c720 Update Changelog 2017-06-07 20:56:06 +02:00
Luc Didry
3b1a6af092 Merge branch 'issue-27' into 'development'
Fix #27 Handle too much images in zip download URL

See merge request !16
2017-06-05 17:59:45 +02:00
Luc Didry
b7d4ea0a23 Fix #27 Handle too much images in zip download URL
This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2017-06-05 17:58:18 +02:00
Luc Didry
05dcdc2729 Merge branch 'issue-33' into 'development'
Fix #33 Add gallery constructor in "my files" list

See merge request !15
2017-06-05 11:55:06 +02:00
Luc Didry
b8212e4920 Fix #33 Add gallery constructor in "my files" list
This commit is dedicated to guilhemB, who is supporting me on Tipeee.
Many thanks :-)
2017-06-05 11:53:45 +02:00
Luc Didry
3faee2402c Update CHANGELOG 2017-06-05 11:10:23 +02:00
Luc Didry
ef1463f8c9 Merge branch 'issue-39' into 'development'
Issue 39

See merge request !14
2017-06-05 11:05:29 +02:00
Luc Didry
028961113c Fix #39 2017-06-05 11:04:20 +02:00
Luc Didry
63a7ad74cd Fix markdown font pb 2017-06-05 10:30:05 +02:00
Luc Didry
3f21ddb35a Merge branch 'issue-42' into 'development'
Issue 42

See merge request !13
2017-06-05 10:02:15 +02:00
Luc Didry
a8d38f6ea8 Fix bug 2017-06-05 10:01:28 +02:00
Luc Didry
efb71654d6 Update CHANGELOG 2017-06-05 09:58:38 +02:00
Luc Didry
8421efc3da Fix #9 Add functional tests
This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2017-06-04 17:38:00 +02:00
Luc Didry
e0f8ddec64 Add functional tests
This commit is dedicated to Brigitte, the queen of elves, who is supporting me.
Many thanks :-)
2017-06-04 11:05:31 +02:00
Luc Didry
1f03678348 Extract i18n strings from directories, not files 2017-06-04 11:00:52 +02:00
Luc Didry
9aed9a0f03 Fix typos and oblivions 2017-06-03 22:22:08 +02:00
Luc Didry
c2110dc171 [Not tested] Add Pg support
This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2017-06-02 19:18:10 +02:00
Luc Didry
381f4e934e Putting helpers in separate file
This commit is dedicated to guilhemB, who is supporting me on Tipeee.
Many thanks :-)
2017-06-02 18:21:03 +02:00
Luc Didry
b710c3250b Add missing vim modeline
This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2017-06-02 18:20:39 +02:00
Luc Didry
6738c49730 Add Liberapay and Tipeee buttons to support the author 2017-05-27 20:43:51 +02:00
Luc Didry
9318058f2b Update Changelog 2017-05-27 20:14:39 +02:00
Luc Didry
d2cd4b8335 Merge branch 'issue-42' into 'development'
Issue 42

See merge request !12
2017-05-27 15:57:20 +02:00
Luc Didry
b6d7860472 Update modules + ask at least Mojolicious 7.31
This commit is dedicated to guilhemB, who is supporting me on Tipeee.
Many thanks :-)
2017-05-27 15:53:51 +02:00
Luc Didry
9a4a5a5799 Issue #42; abstraction layer finished
This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2017-05-27 15:53:51 +02:00
Luc Didry
1a8d2ea171 Fix namespace change oblivion
This commit is dedicated to guilhemB, who is supporting me on Tipeee.
Many thanks :-)
2017-05-27 15:53:51 +02:00
Luc Didry
179def2d3e Work on #42: cleanbdd command
This commit is dedicated to Schoumi, who is supporting me on Tipeee.
Many thanks :-)
2017-05-27 15:53:51 +02:00
Luc Didry
0de43b74db First work on #42
- Start creating a DB abstraction layer
- Use this abstraction layer in watch and cleanfiles commands

This commit is dedicated to guilhemB, who is supporting me on Tipeee.
Many thanks :-)
2017-05-27 15:53:51 +02:00
Luc Didry
5dffc402c9 Merge branch 'deprec_fix' into 'master'
Fix #45

Closes #45

See merge request !11
2017-05-27 12:33:42 +02:00
Roberto Benfatto
b6cfec60e2 Fix #45 2017-05-27 12:20:26 +02:00
Luc Didry
efec636f2e Merge branch 'patch-2' into 'master'
Update oc.po

See merge request !9
2017-05-20 09:36:52 +02:00
Quentin
eb6dc4235a Update oc.po 2017-05-05 20:08:15 +02:00
Luc Didry
d1b6c00a55 Improve stats pages translation 2017-05-04 12:59:40 +02:00
Luc Didry
7af0ee5df4 Merge branch 'improve-stats-page' into 'master'
Improve statistics page

See merge request !8
2017-05-04 12:52:36 +02:00
Luc Didry
1b9dad0f3f Improve statistics page 2017-05-04 12:48:01 +02:00
Luc Didry
4c0df8f8b1 Force download if image is SVG 2017-02-13 21:35:44 +01:00
Luc Didry
4629d2ae2d Fix #37 2017-01-05 08:39:30 +01:00
Luc Didry
f4f25aab4f Add GET /infos API endpoint
See https://framagit.org/luc/lutim/wikis/API for details

This commit is dedicated to Schoumi. Thx for your support on Tipeee :-)
2016-11-14 13:13:07 +01:00
Luc Didry
c6ee5408da Merge branch 'housekeeping' into 'master'
General housekeeping

Correct various typos.
Change GitLab URL to framagit.org.

See merge request !6
2016-09-26 09:20:50 +02:00
Luc Didry
6cc48bb991 Merge branch 'patch-1' into 'master'
Update oc.po

Suites recommandations au sujet de l'occitan "général".

See merge request !7
2016-09-26 09:18:52 +02:00
Quentin
91174081b5 Update oc.po
Suites recommandations au sujet de l'occitan "général".
2016-09-25 19:12:59 +02:00
Luc Didry
a69e0df68d Add occitan translation, thanks to Quentin Pagès 2016-09-16 23:17:25 +02:00
Armando Lüscher
545059cbe5 Correct various typos.
Change GitLab URL to framagit.org.
2016-09-13 10:43:26 +02:00
Luc Didry
a848d7aef8 Add tmp/ to .gitignore 2016-07-19 14:27:09 +02:00
Luc Didry
cc3281fe2f Adapt stats task to theme system 2016-07-19 14:26:07 +02:00
Luc Didry
48cc398efb Small UI changes
+ updated bootstrap
+ add .gitignore when creating themes
+ moving bootstrap and fontello config.json into utilities folder
2016-07-19 13:23:59 +02:00
Luc Didry
4d2721f533 Fix Copy fallback instruction 2016-07-19 08:58:06 +02:00
Luc Didry
2923180f0c Add links to Tipeee and Liberapay 2016-07-18 21:41:10 +02:00
Luc Didry
4130a77aa0 Add theme system
Thanks to e-Jim to support me on Tipeee :-)
2016-07-18 21:27:53 +02:00
Luc Didry
dd4ca47ac0 Update cpanfile (fix #25) 2016-06-25 12:37:36 +02:00
Luc Didry
b63eebbefa Update Morris.js URL 2016-06-22 08:59:37 +02:00
Luc Didry
e767f850bf update CHANGELOG 2016-06-21 23:26:53 +02:00
Luc Didry
374b99f77d Add CSS::Minifier::XS dependency
It seems that it was not automatically installed as a dependency of
Mojolicious::Plugin::AssetPack :-(
2016-06-21 23:26:40 +02:00
397 changed files with 30813 additions and 5805 deletions

8
.dockerignore Normal file
View File

@@ -0,0 +1,8 @@
.ash_history
.git
.cpan
.cpanm
/files/
/local/
/lutim.conf
/lutim.db

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
zanata.xml merge=ours

27
.gitignore vendored
View File

@@ -1,10 +1,27 @@
*.swp
lutim.conf
lutim.db
*.db
*.db-shm
*.db-wal
script/hypnotoad.pid
local/*
files/*
templates/data.html.ep
public/img/rezopole.png
public/img/rezopole.xcf
public/packed/*
stop-upload
tap.xml
themes/*
!themes/default
!themes/default/*
themes/default/templates/data.html.ep
themes/default/templates/raw.html.ep
themes/default/templates/stats.json.ep
themes/default/templates/partial/raw.js.ep
!themes/korrigan
!themes/korrigan/*
tmp/*
.zanata-cache/*
cover_db/*
*.passwd
.ash_history
.vscode/
.DS_Store
.cpanm/

154
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,154 @@
image: hatsoftwares/lutim-test-ci:latest
stages:
- create_release
- pouet_it
- podcheck
- carton
- carton_bdd
- tests
- cover
before_script:
- rm -f *.db
variables:
POSTGRES_DB: lutim_db
POSTGRES_USER: lutim
POSTGRES_PASSWORD: lutim_pwd
### Jobs templates
##
#
.retry: &retry
retry: 2
except:
- tags
.carton_bdd_template: &carton_bdd_definition
<<: *retry
stage: carton_bdd
artifacts:
paths:
- local/
needs:
- carton
.test_template: &test_definition
<<: *retry
stage: tests
script:
- MOJO_CONFIG=t/$CI_JOB_NAME.conf make test
- MOJO_CONFIG=t/$CI_JOB_NAME.conf make watch
- MOJO_CONFIG=t/$CI_JOB_NAME.conf make cleanbdd
- MOJO_CONFIG=t/$CI_JOB_NAME.conf make cleanfiles
- MOJO_CONFIG=t/$CI_JOB_NAME.conf make stats
- MOJO_CONFIG=t/$CI_JOB_NAME.conf make test-junit-output
artifacts:
paths:
- tap.xml
- cover_db/
.sqlite_template: &sqlite_definition
<<: *test_definition
needs:
- carton_sqlite
.pg_template: &pg_definition
<<: *test_definition
needs:
- carton_postgresql
services:
- name: postgres:9.6
alias: postgres
### Publish tag changelog and create a toot
##
#
include:
- 'https://framagit.org/fiat-tux/gitlabci-snippets/-/raw/2aac6c1f3dd725d9aed57549da67a92759f9f9ec/create-release-from-ci.gitlab-ci.yml'
- 'https://framagit.org/fiat-tux/gitlabci-snippets/-/raw/41345a919d3c927991782f5fd17e0c7b338a3f3a/pouet-it-from-ci.gitlab-ci.yml'
### Podcheck
##
#
podcheck:
<<: *retry
stage: podcheck
script:
- make podcheck
### Install common dependencies
##
#
carton:
<<: *retry
stage: carton
artifacts:
paths:
- local/
dependencies: []
script:
- cpanm -l local Devel::Cover~1.29
- carton install --deployment --without=sqlite --without=postgresql --without=minion --without=cache --without=memcached
when: always
### Install DB related dependencies
##
#
carton_sqlite:
<<: *carton_bdd_definition
script:
- carton install --deployment --without=postgresql --without=minion --without=cache --without=memcached
carton_postgresql:
<<: *carton_bdd_definition
script:
- carton install --deployment --without=sqlite --without=minion --without=cache --without=memcached
### SQLite tests
##
#
sqlite1:
<<: *sqlite_definition
before_script:
- carton install --deployment --without=postgresql --without=minion --without=cache --without=memcached
sqlite2:
<<: *sqlite_definition
before_script:
- carton install --deployment --without=postgresql --without=cache --without=memcached
sqlite3:
<<: *sqlite_definition
services:
- name: postgres:9.6
alias: postgres
before_script:
- carton install --deployment --without=cache --without=memcached
- export PGPASSWORD=lutim_pwd; echo 'CREATE DATABASE lutim_minion WITH OWNER lutim;' | psql -h postgres -U lutim lutim_db
### PostgreSQL tests
##
#
postgresql1:
<<: *pg_definition
before_script:
- carton install --deployment --without=sqlite --without=minion --without=cache --without=memcached
postgresql2:
<<: *pg_definition
before_script:
- carton install --deployment --without=cache --without=memcached
postgresql3:
<<: *pg_definition
before_script:
- carton install --deployment --without=sqlite --without=cache --without=memcached
- export PGPASSWORD=lutim_pwd; echo 'CREATE DATABASE lutim_minion WITH OWNER lutim;' | psql -h postgres -U lutim lutim_db
### Code coverage
##
#
cover:
stage: cover
script:
- make cover
coverage: '/Total.* (\d+\.\d+)$/'
artifacts:
reports:
junit: tap.xml
except:
- tags

7
.provision/README.md Normal file
View File

@@ -0,0 +1,7 @@
## ansible-role-lutim
An ansible role deploy the application on host machine(Ubuntu 20.04)
## terraform-aws-lutim
A terraform plan creates necessary AWS infrastructure and deploy the lutim. This terraform plan uses the `lutim_startup.sh` script to deploy lufi on AWS and also uses above ansible role `ansible-role-lutim` to configure the application on AWS.

View File

@@ -0,0 +1,50 @@
Ansible-Role-lutim
=========
This role installs the and configures lutim on Debian/Ubuntu servers with nginx web server configuration.
Role Variables
--------------
| Variable name | Value | Description |
| ------------- | ----- | ----------- |
| `app_dir` | /var/www/lutim | Set the application directory for the best practice |
| `lutim_owner` | www-data | Set the application user for the best practice |
| `lutim_group` | www-data | Set the application group for the best practice |
| `_contact` | contact.example.com | Contact option (mandatory), where you have to put some way for the users to contact you. |
| `_secrets` | ffyg7kbkjba | Secrets option (mandotory), which is array of random string. Used by Mojolicious for encrypting session cookies |
| `_project_version` | master | We can chose the project version either Master branch, Dev branch or tag based |
| `_server_name` | IP address (or) CNAME/FQDN | Mention the Server Name for the Nginx configurations |
Sample example of use in a playbook
--------------
The following code has been tested with Ubuntu 20.04
```yaml
- name: "install lutim"
hosts: enter your hosts file
become: yes
role:
- ansible-role-lutim
vars:
lutim_owner: "www-data"
lutim_group: "www-data"
contact: "contact.example.com"
secrets: "yigavlvlivwe"
app_dir: "/var/www/lutim"
project_version: "master"
servername: "IP address (or) CNAME/FQDN"
```
Contributing
------------
Dont hesitate to create a pull request

View File

@@ -0,0 +1,6 @@
#Path of the script
PATH=/var/www/lutim
carton exec script/lutim cron cleanbdd --mode production
carton exec script/lutim cron cleanfiles --mode production
carton exec script/lutim cron watch --mode production

View File

@@ -0,0 +1,5 @@
---
# handlers file for ansible-role-lutim
- name: restart nginx
service: name=nginx state=restarted

View File

@@ -0,0 +1,23 @@
#apprun.yml
---
- name: This command will install the postgress module
ansible.builtin.shell:
cmd: carton install --deployment --without=test --without=sqlite
chdir: "{{ app_dir }}"
- name: Upload application config file
ansible.builtin.template:
src: ../templates/lutim.conf.j2
dest: "{{ app_dir }}/lutim.conf"
- name: App executes
ansible.builtin.shell:
cmd: carton exec hypnotoad script/lutim
chdir: "{{ app_dir }}"
- name: Nginx configuration file add
ansible.builtin.template:
src: ../templates/app.conf
dest: /etc/nginx/conf.d/
mode: '0644'
notify: restart nginx

View File

@@ -0,0 +1,21 @@
---
- name: Copy the cronjob file
ansible.builtin.copy:
src: ../files/cronjob
dest: /etc/cron.d/lutim
owner: www-data
group: www-data
- name: "example cronjob"
ansible.builtin.cron:
name: "cronjob"
state: present
user: www-data
minute: "0"
hour: "0"
day: "*"
month: "*"
weekday: "*"
job: |
carton exec script/lutim cron cleanbdd --mode production; carton exec script/lutim cron cleanfiles --mode production; carton exec script/lutim cron watch --mode production

View File

@@ -0,0 +1,23 @@
# dependencies.yaml
---
- name: Lutim | Update apt cache
ansible.builtin.apt: update_cache=yes
changed_when: no
- name: Install Dependencies
ansible.builtin.apt:
name:
- nginx
- carton
- build-essential
- libssl-dev
- libpq-dev
- libio-socket-ssl-perl
- zlib1g-dev
- libmojo-sqlite-perl
- shared-mime-info
- perlmagick
state: present

View File

@@ -0,0 +1,18 @@
#gitclone
---
- name: clone the repository
ansible.builtin.git:
repo: 'https://framagit.org/fiat-tux/hat-softwares/lutim.git'
dest: "{{ app_dir }}"
clone: yes
update: yes
version: "{{ project_version }}"
- name: Change the owner
ansible.builtin.file:
path: "{{ app_dir }}"
owner: "{{ lutim_owner }}"
group: "{{ lutim_group }}"
state: directory
recurse: yes

View File

@@ -0,0 +1,7 @@
---
# tasks file for ansible-role-lutim
- include: dependencies.yaml
- include: gitclone.yaml
- include: apprun.yaml
- include: cronjob.yaml

View File

@@ -0,0 +1,43 @@
server {
listen 80;
# No need to have a `root` parameter.
server_name {{ _server_name }};
# This is important for user's privacy !
access_log off;
error_log /var/log/nginx/lutim.error.log;
# This is important ! Make it OK with your Lutim configuration
client_max_body_size 40M;
location ~* ^/(img|css|font|js)/ {
try_files $uri @lutim;
add_header Expires "Thu, 31 Dec 2037 23:55:55 GMT";
add_header Cache-Control "public, max-age=315360000";
# HTTPS only header, improves security
#add_header Strict-Transport-Security "max-age=15768000";
}
location / {
try_files $uri @lutim;
# HTTPS only header, improves security
#add_header Strict-Transport-Security "max-age=15768000";
}
location @lutim {
# Adapt this to your configuration
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# If you want to log the remote port of the image senders, you'll need that
proxy_set_header X-Remote-Port $remote_port;
proxy_set_header X-Forwarded-Proto $scheme;
# We expect the downsteam servers to redirect to the right hostname, so don't do any rewrites here.
proxy_redirect off;
}
}

View File

@@ -0,0 +1,317 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
{
####################
# Hypnotoad settings
####################
# see http://mojolicio.us/perldoc/Mojo/Server/Hypnotoad for a full list of settings
hypnotoad => {
# array of IP addresses and ports you want to listen to
listen => ['http://127.0.0.1:8080'],
# if you use Lutim behind a reverse proxy like Nginx, you want to set proxy to 1
# if you use Lutim directly, let it commented
#proxy => 1,
},
################
# Lutim settings
################
# put a way to contact you here and uncomment it
# mandatory
contact => '{{ _contact }}',
# random string used to encrypt cookies
# mandatory
secrets => ['{{ _secrets }}'],
# choose a theme. See the available themes in `themes` directory
# optional, default is 'default'
#theme => 'default',
# length of the images random URL
# optional, default is 8
#length => 8,
# length of the encryption key
# optional, default is 8
#crypto_key_length => 8,
# how many URLs will be provisioned in a batch ?
# optional, default is 5
#provis_step => 5,
# max number of URLs to be provisioned
# optional, default is 100
#provisioning => 100,
# anti-flood protection delay, in seconds
# users won't be able to ask Lutim to download images more than one per anti_flood_delay seconds
# optional, default is 5
#anti_flood_delay => 5,
# twitter account which will appear on twitter cards
# see https://dev.twitter.com/docs/cards/validation/validator to register your Lutim instance on twitter
# optional, no default
#tweet_card_via => '@foo',
# max image size, in octets
# you can write it 10*1024*1024
# optional, default is 10485760
#max_file_size => 10485760,
# if you want to have piwik statistics, provide a piwik image tracker
# only the image tracker is allowed, no javascript
# optional, no default
#piwik_img => 'https://piwik.example.org/piwik.php?idsite=1&amp;rec=1',
# if you want to include something in the right of the screen, put it here
# here's an example to put the logo of your hoster
# optional, no default
#hosted_by => 'My super hoster <img src="http://hoster.example.com" alt="Hoster logo">',
# DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED
# Lutim now checks if the X-Forwarded-Proto header is present and equal to https.
# set to 1 if you use Lutim behind a secure web server
# optional, default is 0
#https => 0,
# broadcast_message which will displayed on all pages of Lutim (but no in json response)
# optional, no default
#broadcast_message => 'Maintenance',
# array of authorized domains for API calls.
# if you want to authorize everyone to use the API: ['*']
# optional, no domains allowed by default
#allowed_domains => ['http://1.example.com', 'http://2.example.com'],
# default time limit for files
# valid values are 0, 1, 7, 30 and 365
# optional, default is 0 (no limit)
#default_delay => 0,
# comma-separated values proposed for delays
# optional, default is '0,1,7,30,365'
#proposed_delays => '0,1,7,30,365',
# number of days after which the images will be deleted, even if they were uploaded with "no delay" (or value superior to max_delay)
# a warning message will be displayed on homepage
# optional, default is 0 (no limit)
#max_delay => 0,
# if set to 1, all the images will be encrypted and the encryption option will no be displayed
# optional, default is 0
#always_encrypt => 0,
# you can allow to use a watermark on the uploaded images (or enforce its use)
# define a path to the watermark image (provide an image with alpha channel)
# you can define the path relative to lutim directory or set an absolute path
# to disable the usage of a watermark, leave it blank or commented
# optional, no default
#watermark_path => '',
# the watermark can be a tiling one or a single one
# when using a small one, you can choose where to place it
# valid values are 'Center', 'North', 'NorthEast', 'East', 'SouthEast', 'South', 'SouthWest', 'West' and 'NorthWest' (case insensitive)
# optional, default is 'SouthEast'
#watermark_placement => 'SouthEast',
# choose which watermark (tiling, single or none) should be used by default
# valid values are 'tiling', 'single' or 'none' (case insensitive)
# optional, default is 'none'
#watermark_default => 'none',
# choose which watermark (tiling, single or none) should be enforced (users will always have a watermark and wont be able to disable it)
# valid values are 'tiling', 'single' or 'none' (case insensitive)
# optional, default is 'none'
#watermark_enforce => 'none',
# length of the image's delete token
# optional, default is 24
#token_length => 24,
# URL sub-directory in which you want Lutim to be accessible
# example: you want to have Lutim under https://example.org/lutim/
# => set prefix to '/lutim' or to '/lutim/', it doesn't matter
# optional, defaut is /
#prefix => '/',
# choose what database you want to use
# valid choices are sqlite and postgresql (all lowercase)
# optional, default is sqlite
#dbtype => 'sqlite',
# SQLite ONLY - only used if dbtype is set to sqlite
# define a path to the SQLite database
# you can define it relative to lutim directory or set an absolute path
# remember that it has to be in a directory writable by Lutim user
# optional, default is lutim.db
#db_path => 'lutim.db',
# PostgreSQL ONLY - only used if dbtype is set to postgresql
# these are the credentials to access the PostgreSQL database
# mandatory if you choosed postgresql as dbtype
pgdb => {
database => 'lutim',
host => 'localhost',
user => 'DBUSER',
pwd => 'DBPASSWORD'
},
# use Minion instead of directly increase counters
# need to launch a minion worker service if enabled
# optional, Minion is disabled by default
minion => {
enabled => 0,
# # Which Minion backend to use?
# # valid values are sqlite and postgresql (all lowercase)
# # mandatory if Minion is enabled, default is sqlite
# dbtype => 'sqlite',
# # SQLite ONLY - only used if if you choose sqlite as Minion backend, define the path to the minion database
# # you can define it relative to lutim directory or set an absolute path
# # remember that it has to be in a directory writable by Lutim user
# # optional, default is minion.db
db_path => 'minion.db',
# # PostgreSQL ONLY - only used if you choose postgresql as Minion backend
# # these are the credentials to access the Minion's PostgreSQL database
# # mandatory if you choosed postgresql as Minion backend, no default
pgdb => {
database => 'lutim_minion',
host => 'localhost',
user => 'DBUSER',
pwd => 'DBPASSWORD'
}
},
# set `ldap` if you want that only authenticated users can shorten URLs
# please note that everybody can still use shortend URLs
# optional, no default
#ldap => {
# uri => 'ldaps://ldap.example.org', # server URI
# user_tree => 'ou=users,dc=example,dc=org', # search base DN
# bind_dn => 'uid=ldap_user,ou=users,dc=example,dc=org', # search bind DN
# bind_pwd => 'secr3t', # search bind password
# user_attr => 'uid', # user attribute (uid, mail, sAMAccountName, etc.)
# user_filter => '(!(uid=ldap_user))', # user filter (to exclude some users, etc.)
#},
# set `htpasswd` if you want to use an htpasswd file instead of ldap
# create the file with `htpasswd -c lutim.passwd user`, update it with `htpasswd lutim.passwd user2`
# make sure that lutim can read the file!
# optional, no default
#htpasswd => 'lutim.passwd',
# if you've set ldap or htpasswd above, the session will last `session_duration` seconds before
# the user needs to reauthenticate
# optional, default is 3600
#session_duration => 3600,
# disable counters of images
# set to 1 to disable counters
# optional, counters are enabled by default
#disable_img_stats => 0,
# define the height of the thumbnails generated at users' will
# this is not the height of the thumbnails send after upload,
# we're talking about thumbnails generated when someone asked for
# https://example.org/lutim/tesrinp?thumb
# this works only if you have ImageMagick
# optional, default is 100 (pixels)
#thumbnail_size => 100,
# maximum number of files that can be downloaded as a single zip archive
# if too many files are asked, it results a timeout, so Lutim split the zip URL
# in multiple URLs, each with max_file_size images.
# timeout behavior depends heavily on your server ressources (CPU) and if images
# are encrypted
# optional, default is 15
#max_files_in_zip => 15,
# maximum size (in MB) of memory allowed for the image cache
# Lutim has a built-in memory-based image cache to accelerate responses to often-viewed images.
# This setting makes the cache remove oldest viewed image if the cache size is over it.
# WARNING: a cache is created for each hypnotoad worker, which by default is twice the number of
# CPUs you have. See http://mojolicious.org/perldoc/Mojo/Server/Hypnotoad#workers for details
# So, if you have 4 workers and set cache_max_size to 100, the real maximum size of RAM used for
# cache is 400MB.
# If set to 0, the cache is disabled
# optional, default is 0
#cache_max_size => 0,
# array of memcached servers to cache URL in order to accelerate responses to often-viewed URL.
# If set to [], the use of memcached is disabled.
# If you use memcached, the internal cache (see cache_max_size option above) will not be used.
# Please see https://framagit.org/fiat-tux/hat-softwares/lutim/-/wikis/memcached to know how to configure your memcached
# servers.
# exemple of valid value: ['127.0.0.1:11211']
# optional, default is []
#memcached_servers => [],
# enable or disable Lutim built-in logs
# set to 1 to disable logs
# optional, default is 0
#quiet_logs => 0,
# Content-Security-Policy header that will be sent by Lstu
# Set to '' to disable CSP header
# https://content-security-policy.com/ provides a good documentation about CSP.
# https://report-uri.com/home/generate provides a tool to generate a CSP header.
# optional, default is "base-uri 'self'; connect-src 'self'; default-src 'none'; font-src 'self'; form-action 'self'; img-src 'self' data:; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
# NB: unsafe-inline for script-src and style-src are here only because morris,
# the graph library used in the stats page requires it
# the default value is good for `default` theme
#csp => "base-uri 'self'; connect-src 'self'; default-src 'none'; font-src 'self'; form-action 'self'; img-src 'self' data:; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'",
# X-Frame-Options header that will be sent by Lstu
# Valid values are: 'DENY', 'SAMEORIGIN', 'ALLOW-FROM https://example.com/'
# Set to '' to disable X-Frame-Options header
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
# Please note that this will add a "frame-ancestors" directive to the CSP header (see above) accordingly
# to the chosen setting (See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors)
# optional, default is 'DENY'
#x_frame_options => 'DENY',
# X-Content-Type-Options that will be sent by Lstu
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
# Set to '' to disable X-Content-Type-Options header
# optional, default is 'nosniff'
#x_content_type_options => 'nosniff',
# X-XSS-Protection that will be sent by Lstu
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
# Set to '' to disable X-XSS-Protection header
# optional, default is '1; mode=block'
#x_xss_protection => '1; mode=block',
# if set, the uploaded images will use this domain
# optional
#fixed_domain => 'example.org',
##########################
# Lutim cron jobs settings
##########################
# number of days shown in /stats page (used with script/lutim cron stats)
# optional, default is 365
stats_day_num => 365,
# number of days senders' IP addresses are kept in database
# after that delay, they will be deleted from database (used with script/lutim cron cleanbdd)
# optional, default is 365
keep_ip_during => 365,
# max size of the files directory, in octets
# used by script/lutim cron watch to trigger an action
# optional, no default
max_total_size => 10*1024*1024*1024,
# default action when files directory is over max_total_size (used with script/lutim cron watch)
# valid values are 'warn', 'stop-upload' and 'delete'
# please, see readme
# optional, default is 'warn'
#policy_when_full => 'warn',
# images which are not viewed since delete_no_longer_viewed_files days will be deleted by the cron cleanfiles task
# if delete_no_longer_viewed_files is not set, the no longer viewed files will NOT be deleted
# optional, no default
delete_no_longer_viewed_files => 90
};

View File

@@ -0,0 +1,16 @@
---
# vars file for ansible-role-lutim
lutim_owner: "www-data"
lutim_group: "www-data"
app_dir: "/var/www/lutim"
_contact: ""
_secrets: ""
_project_version: ""
_servername: ""

View File

@@ -0,0 +1,92 @@
# Terraform-AWS-Deploy
This terraform plan create the resourcess of EC2 instance
## Terraform Variables
Edit the `vars.tf` file to add the variables as per your need.
| Variable name | Value | Description |
| ------------- | ----- | ----------- |
| `aws_region` | us-east-1 | Set the region |
| `vpc_cidr` | 10.0.0.0/16 | Set the cidr value for the vpc |
| `public_subnet_cidr` | 10.0.2.0/24 | Set the cidr value for the public subnet |
| `user` | ubuntu | Set the EC2 instance user name |
| `public_key` | /home/user_name/.ssh/id_rsa_pub | Set the publickey value for the ec2 instance from the host machine |
| `private_key` | /home/user_name/.ssh/id_rsa | Set the private key value for the ec2 instance from the hostmachine |
| `aws_access_key` | AWSACCESSKEY | Enter your aws access key |
| `aws_secrete_key` | AWSSECRETEKEY | Enter your aws secrete key |
| `instance_name` | lutim_app_instance | Set the name for instance |
| `app_dir` | /var/www/lutim | Set the application directory for the best practice |
| `lutim_owner` | www-data | Set the application user for the best practice |
| `lutim_group` | www-data | Set the application group for the best practice |
| `contact` | contact.example.com | Contact option (mandatory), where you have to put some way for the users to contact you. |
| `contact_user` | name | Name of the user |
| `secrets` | ffyg7kbkjba | Secrets option (mandotory), which is array of random string. Used by Mojolicious for encrypting session cookies |
| `app_dir` | /var/www/lutim | Set the application directory for the best practice |
| `lutim_owner` | www-data | Set the application user for the best practice |
| `lutim_group` | www-data | Set the application group for the best practice |
| `contact` | contact.example.com | Contact option (mandatory), where you have to put some way for the users to contact you. |
| `contact_user` | name | Name of the user |
| `secrets` | ffyg7kbkjba | Secrets option (mandotory), which is array of random string. Used by Mojolicious for encrypting session cookies |
## Usage of terraform plan with lufi deploy script
```sh
git clone https://framagit.org/fiat-tux/hat-softwares/lutim.git
cd lutim/.provision/terraform-aws-lutim
terraform init
terraform plan
terraform apply
```
## Usage of terraform plan with ansible role
- Comment out the below `locals` and `user_data` source in __main.tf__ file
```hcl
locals {
user_data_vars = {
user = var.lutim_owner
group = var.lutim_group
directory = var.app_dir
contact_user = var.contact_user
contact_lutim = var.contact
secret_lutim = var.secret
}
}
```
```hcl
user_data = templatefile("${path.module}/lutim_startup.sh", local.user_data_vars)
```
- Add the below provisioner data in __main.tf__ file at the `aws_instance` resource
```sh
connection {
agent = false
type = "ssh"
host = aws_instance.ec2_instance.public_dns
private_key = "${file(var.private_key)}"
user = "${var.user}"
}
provisioner "remote-exec" {
inline = [
"sudo apt update -y",
"sudo apt install python3.9 -y",
]
}
provisioner "local-exec" {
command = <<EOT
sleep 120 && \
> hosts && \
echo "[Lutim]" | tee -a hosts && \
echo "${aws_instance.ec2_instance.public_ip} ansible_user=${var.user} ansible_ssh_private_key_file=${var.private_key}" | tee -a hosts && \
export ANSIBLE_HOST_KEY_CHECKING=False && \
ansible-playbook -u ${var.user} --private-key ${var.private_key} -i hosts site.yml
EOT
}
```

View File

@@ -0,0 +1,66 @@
#!/bin/bash
echo "**********************************************************************"
echo " *"
echo "Install dependencies *"
echo " *"
echo "**********************************************************************"
SUDO=sudo
$SUDO apt update
$SUDO apt install jq -y
$SUDO apt install wget -y
$SUDO apt install unzip
$SUDO apt install carton -y
$SUDO apt install build-essential -y
$SUDO apt install nginx -y
$SUDO apt install libssl-dev -y
$SUDO apt install libio-socket-ssl-perl -y
$SUDO apt install liblwp-protocol-https-perl -y
$SUDO apt install zlib1g-dev -y
$SUDO apt install libmojo-sqlite-perl -y
$SUDO apt install libpq-dev -y
echo "**********************************************************************"
echo " *"
echo "Configuring the Application *"
echo " *"
echo "**********************************************************************"
sleep 10;
version=$(curl -s https://framagit.org/api/v4/projects/1/releases | jq '.[]' | jq -r '.name' | head -1)
echo $version
pushd ${directory}
$SUDO wget https://framagit.org/fiat-tux/hat-softwares/lutim/-/archive/$version/lutim-$version.zip
$SUDO unzip lutim-$version.zip
$SUDO chown ${user} lutim-$version
$SUDO chgrp ${group} lutim-$version
pushd lutim-$version
echo "**********************************************************************"
echo " *"
echo "Install Carton Packages *"
echo " *"
echo "**********************************************************************"
$SUDO carton install --deployment --without=test --without=sqlite --without=mysql
sleep 10;
$SUDO cp lutim.conf.template lutim.conf
sed -i 's/127.0.0.1/0.0.0.0/' lutim.conf
sed -i 's/#contact/contact/g' lutim.conf
sed -i "s/John Doe/${contact_user}/g" lutim.conf
sed -i "s/admin[at]example.com/${contact_lutim}/g" lutim.conf
sed -i "s/fdjsofjoihrei/${secret_lutim}/g" lutim.conf
sed -i '153 , 158 s/#/ /g' lutim.conf
echo "**********************************************************************"
echo " *"
echo "Run the Application *"
echo " *"
echo "**********************************************************************"
$SUDO carton exec hypnotoad script/lutim

View File

@@ -0,0 +1,123 @@
locals {
user_data_vars = {
user = var.lutim_owner
group = var.lutim_group
directory = var.app_dir
contact_user = var.contact_user
contact_lutim = var.contact
secret_lutim = var.secret
}
}
#Create the VPC
resource "aws_vpc" "vpc" {
cidr_block = "${var.vpc_cidr}"
enable_dns_hostnames = true
enable_dns_support = true
instance_tenancy = "default"
tags = {
Name = "lutim-master-vpc"
}
}
# Create InternetGateWay and attach to VPC
resource "aws_internet_gateway" "IGW" {
vpc_id = "${aws_vpc.vpc.id}"
tags = {
"Name" = "lutim-master-igw"
}
}
# Create a public subnet
resource "aws_subnet" "publicsubnet" {
vpc_id = "${aws_vpc.vpc.id}"
cidr_block = "${var.public_subnet_cidr}"
map_public_ip_on_launch = true
tags = {
Name = "lutim-master-us-east-1-public"
}
}
# Create routeTable
resource "aws_route_table" "publicroute" {
vpc_id = "${aws_vpc.vpc.id}"
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.IGW.id}"
}
tags = {
Name = "lutim-master-us-east-1-public-rt"
}
}
resource "aws_main_route_table_association" "mainRTB" {
vpc_id = "${aws_vpc.vpc.id}"
route_table_id = "${aws_route_table.publicroute.id}"
}
## Create security group
resource "aws_security_group" "security" {
name = "lutim-master-sg"
description = "allow all traffic"
vpc_id = "${aws_vpc.vpc.id}"
ingress {
description = "allow all traffic"
from_port = "0"
to_port = "65535"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "allow port SSH"
from_port = "22"
to_port = "22"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Add ubuntu AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
}
#Create key_pair for the instance
resource "aws_key_pair" "genkey" {
key_name = "lutim.webapp"
public_key = "${file(var.public_key)}"
}
# Craete ec2 instance
resource "aws_instance" "ec2_instance" {
ami = "${data.aws_ami.ubuntu.id}"
instance_type = "t2.medium"
associate_public_ip_address = "true"
subnet_id = "${aws_subnet.publicsubnet.id}"
vpc_security_group_ids = ["${aws_security_group.security.id}"]
user_data = templatefile("${path.module}/lutim_startup.sh", local.user_data_vars)
key_name = "lutim.webapp"
tags = {
Name = "${var.instance_name}"
}
}

View File

@@ -0,0 +1,7 @@
output "public_ip" {
value = "${aws_instance.ec2_instance.public_ip}"
}
output "App_running_at" {
value = "http://${aws_instance.ec2_instance.public_ip}:8080"
}

View File

@@ -0,0 +1,14 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}
provider "aws" {
access_key = "${var.aws_access_key}"
secret_key = "${var.aws_secret_key}"
region = "${var.aws_region}"
}

View File

@@ -0,0 +1,60 @@
variable "aws_region" {
default = "aws_region"
}
variable "vpc_cidr" {
default = "cidr_value"
}
variable "public_subnet_cidr" {
default = "cidr_value"
}
variable "public_subnet1_cidr" {
default = "cidr_value"
}
variable "user" {
default = "user_of_instance"
}
variable "public_key" {
default = "$PWD_publickey"
}
variable "private_key" {
default = "$PWD_privatekey"
}
variable "aws_access_key" {
default = "aws_access_key"
}
variable "aws_secret_key" {
default = "aws_secrete_key"
}
variable "instance_name" {
default = "instance_name"
}
variable "lutim_owner" {
default = ""
}
variable "lutim_group" {
default = ""
}
variable "app_dir" {
default = ""
}
variable "contact_user" {
default = ""
}
variable "contact" {
default = ""
}
variable "secret" {
default = ""
}

View File

@@ -1,8 +0,0 @@
[main]
host = https://www.transifex.com
[lutim.enpo]
source_file = lib/Lutim/I18N/en.po
source_lang = en
type = PO
file_filter = lib/Lutim/I18N/<lang>.po

3
.weblate Normal file
View File

@@ -0,0 +1,3 @@
[weblate]
url = https://weblate.framasoft.org/api/
translation = lutim/development

View File

@@ -2,7 +2,7 @@
## Main developers
* Luc Didry, aka Sky (<http://www.fiat-tux.fr>), core developer, @framasky on [Twitter](https://twitter.com/framasky) and on [Diaspora*](https://framasphere.org/public/framasky)
* Luc Didry, aka Sky (<http://www.fiat-tux.fr>), core developer, @framasky on [Mastodon](https://framapiaf.org/@framasky) and on [Diaspora*](https://framasphere.org/public/framasky)
* Dattaz (<http://dattaz.fr>), webapp developer, [@dat_taz](https://twitter.com/dat_taz)
## Contributors
@@ -13,3 +13,9 @@
* Sandro Cazzaniga, aka Kharec (<http://sandrocazzaniga.fr>), [@Kharec](https://twitter.com/Kharec)
* Laura Arjona Reina (<https://wiki.debian.org/LauraArjona>), spanish translation
* Thor77 (<http://thor77.org>), german translation, among other things
* Quentin Pagès, occitan translation
* Alexis Clairet (<https://github.com/Turboconnard>), paste image to upload ability
* ButterflyOfFire (<https://mastodon.tetaneutral.net/@BoF>), arabic translation
* Alexander Sapozhnikov (<http://shoorick.ru>), russian translation
* Arnaud de Mouhy, Docker support
* Armando Lüscher (<https://noplanman.ch/>)

189
CHANGELOG
View File

@@ -1,5 +1,192 @@
Revision history for Lutim
0.19.0 ????-??-??
0.18.0 2025-10-13
- 🌐 — Update translations
- 📦 — Update alpine version by @ploc
- 🔧 — Ability to configure MOJO_TMPDIR via environment by @s7b4
0.17.0 2023-12-30
- 🐛 — AVIF format support (#139)
- ✨ — Allow to configure the directory where to store the images (#125)
- ✨ — Ask for confirmation before deleting image on index page (#92)
- ✨ — Mass delete button in "My images" page (#47)
- ♿ — Improve accessibility on index page
0.16.0 2023-12-29
- ⬆️ Update jQuery
- 💥 BREAKING CHANGE: no more twitter cards
- 🎨 — Use template literals in js
- 🐛 — Gallery, zip and random URLs are now updated when closing image dialog box
— Replace moment.js with Date().toLocaleDateString(…)
- 🩹 — Replace old URL of the project
0.15.0 2023-12-19
- ✨ — Add --nuke option to image command
0.14.0 2023-12-18
- ⬆️ Update dependencies
- 💥 BREAKING CHANGE: Use `?_format=json` instead of `?format=json`
- 💥 BREAKING CHANGE: Use `?_format=json` instead of terminating the URL with `.json`
0.13.0 2023-04-26
- 💄 — Add Korrigan theme (Nicolas Frandeboeuf)
- 🔥 — Remove zanata stuff
- ✨ — Add a config flag to disable API (@b_b)
- 🌐 — Update translations
0.12.1 2020-10-08
- ⬆️ Update jQuery
0.12.0 2020-04-17
- Add watermarking feature (#112)
0.11.6 2019-11-16
- Remove the "Support the author" dropdown
- Add a "select all" checkbox on /myfiles (#105)
- Update arabic translation
- Group button links fixed to make the whole button a link (Armando Lüscher)
- Docker support (Arnaud de Mouhy)
- Bump toastify-js to version 1.4.0 (Armando Lüscher)
0.11.5 2019-04-19
- Revert catching Image::Magick problems
- In gallery, use JS to find image's width and height if not provided by image's infos
0.11.4 2018-11-18
- Catch Image::Magick problems
- Update arabic translation
- Add italian translation
0.11.3 2018-07-31
- Fix gallery bug
0.11.2 2018-07-31
- Fix stats graph if no upload happens in the time range
0.11.1 2018-07-30
- Add upload_enabled info to server infos endpoint
- Add log message when failing to render an image
0.11.0 2018-07-29
- Allow to install only needed deps
- Notification when copying to clipboard or deleting images
- Allow to use memcached as cache system
- Use Mojolicious::Plugin::Chi
- Gzip static assets with Mojolicious::Plugin::GzipStatic
- Fix scroll-to-top when clicking on delete image
- Add CSP header
- Add X-Content-Type-Options, X-XSS-Protection, X-Frame-Options headers
- Remove @framasky as default tweet_card_via setting
- Add a message saying how many images there is in the gallery
- Use ISO::639_1 for languages' native names
- Add CLI command to print informations about images
- Add CLI command to remove images
- Add CLI command to search images based on the uploader's IP address
- Add .zip file check in the tests
- Fix stats files place when using non-default theme
- Allow to use a fixed domain
- Add localStorage export and import feature
- Add link to generate random file in collection
- Add modal to modify expiration delay from "myfiles" page
- Add optional authentication (LDAP or htpasswd file)
0.10.4 2018-05-07
- Fix bug in cache system that would allow someone to view an image with an incorrect decryption key
0.10.3 2018-04-26
- Fix another bug on zip file creation
0.10.2 2018-04-24
- Fix bug on zip file creation
0.10.1 2018-04-15
- Fix bug on theme creation (#73)
0.10.0 2018-04-07
- PostgreSQL performance improvments
- Move some tasks to recurring instead of being in after_dispatch hook
- Built-in image cache system \o/
- Disable logs option
- Disable images' counter option
0.9.6 2018-03-12
- Update translations
0.9.5 2018-03-11
- Add russian translation, thanks to Alexander Sapozhnikov
0.9.4 2018-03-11
- Replace Twitter account URL with Mastodon one
0.9.3 2018-03-11
- Use woff2 format for HennyPenny font
0.9.2 2018-03-09
- Fix langage drop-down
- Allow to use HTML in broadcast message
- Remove old targets from Makefile
0.9.1 2018-03-09
- Fix default setting bug (db_path)
0.9.0 2018-03-09
- Added partial arabic translation (thx to ButterflyOfFire)
- Default theme is now non-fluid (ie don't take all the width of the screen)
- Use Photoswipe for the gallery instead of Unite gallery
- Use Zanata for translations (https://trad.framasoft.org)
- Add an option to personnalize proposed retention delays
- Use random Initialization Vector for encryption
- Use Mojo::SQLite instead of ORLite
- Fix various bugs
0.8.8 2018-02-07
- Fix security issues, thanks to SecuNinja
0.8.7 2017-12-22
- Fix bug if dbtype not configured in lutim.conf
0.8.6 2017-11-18
- Fix bug resulting in no EXIF tags deletion
0.8.5 2017-07-09
- Fix Henny Penny font path in css
0.8.4 2017-06-24
- Mitigate a bug using the same empty record twice
0.8.3 2017-06-15
- Fix the donuts charts in the /stats page.
0.8.2 2017-06-14
- Enforce Mojolicious::Plugin::AssetPack version
0.8.1 2017-06-13
- Fix #46
0.8 2017-06-13
- Improve statistics page
- Add database abstraction layer (#42)
- Add PostgreSQL support (#42)
- Asks for Mojolicious 7.31 minimum (to install it: `carton update`)
- Add Liberapay and Tipeee buttons
- Remove Flattr button
- Handle MOJO_CONFIG env variable (#44)
- Fix bug #39
- Add gallery constructor to "my files" list (#33)
- Handle too much images in zip download URL (#27)
- LocalStorage is now updated if an image's delay is modified
- Allow user to paste image from clipboard to upload images (#14 and !5)
- Fix #40
- Add stats in JSON format (GET /stats.json)
- Add Cache-control headers for static files
- Put almost all js/css stuff outside templates
- Allow to use Minion to increment counter (#43)
0.7.1 2016-06-21
- Fix dependency bug
0.7 2016-06-21
- Use .po files for internationalization
- Add spanish translation (thx to Laura Arjona Reina)
@@ -10,7 +197,7 @@ Revision history for Lutim
- Autorotate images
- Purge images from their EXIF tags (for more privacy)
- Provide markdown syntax at upload for using lutim's hosted images
- Now using a wiki: makes README lighter (https://git.framasoft.org/luc/lutim/wikis/home)
- Now using a wiki: makes README lighter (https://framagit.org/luc/lutim/wikis/home)
- Update twitter bootstrap
- Add "Copy to clipboard" buttons to each link
- Add "Copy all links" button

View File

@@ -1,3 +1,3 @@
# Contributing
See how to contribute on the [wiki](https://framagit.org/luc/lutim/wikis/contribute).
See how to contribute on the [wiki](https://framagit.org/fiat-tux/hat-softwares/lutim/-/wikis/contribute).

48
Dockerfile Normal file
View File

@@ -0,0 +1,48 @@
FROM alpine:3.22
ARG BUILD_DATE
ARG VCS_REF
ARG VERSION
LABEL org.label-schema.build-date=$BUILD_DATE \
org.label-schema.name="Lets Upload That Image" \
org.label-schema.url="https://lutim.fiat-tux.fr/" \
org.label-schema.vcs-ref=$VCS_REF \
org.label-schema.vcs-url="https://framagit.org/fiat-tux/hat-softwares/lutim" \
org.label-schema.vendor="Luc Didry" \
org.label-schema.version=$VERSION \
org.label-schema.schema-version="1.0"
RUN adduser -D lutim \
&& addgroup lutim root
COPY . /home/lutim
RUN chmod -R g+rwX /home/lutim
WORKDIR /home/lutim
RUN apk --no-cache add perl~=5 \
libpq~=17 \
perl-crypt-rijndael~=1 \
perl-io-socket-ssl~=2 \
perl-net-ssleay~=1 \
su-exec~=0.2 \
shared-mime-info~=2 \
libretls~=3 \
imagemagick~=7 \
imagemagick-perlmagick~=7 \
bash~=~5 \
&& apk --no-cache add --virtual .build-deps build-base~=0.5 \
perl-utils~=5 \
perl-dev~=5 \
postgresql17-dev~=17 \
vim~=9 \
wget~=1 \
zlib-dev~=1 \
&& cpan notest Carton Config::FromHash \
&& carton install --without test \
&& apk del .build-deps \
&& rm -rf /var/cache/apk/* /root/.cpan*
USER lutim
EXPOSE 8080
ENTRYPOINT ["/bin/sh", "/home/lutim/docker/entrypoint.sh"]

View File

@@ -1,21 +1,70 @@
EXTRACTFILES=utilities/locales_files.txt
EN=lib/Lutim/I18N/en.po
FR=lib/Lutim/I18N/fr.po
DE=lib/Lutim/I18N/de.po
ES=lib/Lutim/I18N/es.po
XGETTEXT=carton exec local/bin/xgettext.pl
EXTRACTDIR=-D lib -D themes/default/templates
POT=themes/default/lib/Lutim/I18N/lutim.pot
ENPO=themes/default/lib/Lutim/I18N/en.po
XGETTEXT=carton exec local/bin/xgettext.pl -u
CARTON=carton exec
LUTIM=script/lutim
REAL_LUTIM=script/application
HARNESS_PERL_SWITCHES=-MDevel::Cover=+ignore,local
HEAD := $(shell git rev-parse --abbrev-ref HEAD)
minify:
@echo "CSS concatenation"
@cd ./themes/default/public/css/ && cat bootstrap.min.css fontello.css hennypenny.css lutim.css toastify.css | csso > common.min.css
@cd ./themes/default/public/css/ && cat animation.css uploader.css markdown.css | csso > not_stats.min.css
@cd ./themes/default/public/css/ && cat photoswipe.css default-skin/default-skin.css | csso > gallery.min.css
locales:
$(XGETTEXT) -W -f $(EXTRACTFILES) -o $(EN) 2>/dev/null
$(XGETTEXT) -W -f $(EXTRACTFILES) -o $(FR) 2>/dev/null
$(XGETTEXT) -W -f $(EXTRACTFILES) -o $(DE) 2>/dev/null
$(XGETTEXT) -W -f $(EXTRACTFILES) -o $(ES) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(POT) 2>/dev/null
$(XGETTEXT) $(EXTRACTDIR) -o $(ENPO) 2>/dev/null
dev:
rm public/packed/*
$(CARTON) morbo $(LUTIM) --listen http://0.0.0.0:3000
podcheck:
podchecker lib/Lutim/DB/Image.pm
check-syntax:
find lib/ themes/ -name \*.pm -exec $(CARTON) perl -Ilib -c {} \;
find t/ -name \*.t -exec $(CARTON) perl -Ilib -c {} \;
cover:
PERL5OPT='-Ilib' $(CARTON) cover --ignore_re '^local'
test:
@PERL5OPT='-Ilib/' HARNESS_PERL_SWITCHES='$(HARNESS_PERL_SWITCHES)' $(CARTON) -- prove -l --failures
test-junit-output:
@PERL5OPT='-Ilib/' HARNESS_PERL_SWITCHES='$(HARNESS_PERL_SWITCHES)' $(CARTON) -- prove -l --failures --formatter TAP::Formatter::JUnit > tap.xml
full-test: podcheck just-test
clean:
rm -rf lutim.db files/
dev: minify
$(CARTON) morbo $(LUTIM) --listen http://0.0.0.0:3000 --watch lib/ --watch script/ --watch themes/ --watch lutim.conf
devlog:
multitail log/development.log
prod:
$(CARTON) hypnotoad -f $(LUTIM)
prodlog:
multitail log/production.log
minion:
$(CARTON) $(REAL_LUTIM) minion worker
create-pg-test-db:
sudo -u postgres psql -f t/create-pg-testdb.sql
stats:
$(CARTON) $(LUTIM) cron stats -m production
watch:
$(CARTON) $(LUTIM) cron watch -m production
cleanfiles:
$(CARTON) $(LUTIM) cron cleanfiles -m production
cleanbdd:
$(CARTON) $(LUTIM) cron cleanbdd -m production

View File

@@ -4,51 +4,51 @@
It means Let's Upload That Image.
## What does it do?
It stores images and allows you to see them, download them or share them on social networks. From version 0.5, the gif images can be displayed as animated gifs in Twitter, but you need a HTTPS server (Twitter requires that. Lutim detects if you have a HTTPS server and displays an static image twitter card if you don't);
It stores images and allows you to see them, download them or share them on social networks.
Images are indefinitly stored unless you request that they will be deleted at first view or after 24 hours / one week / one month / one year.
Images are indefinitely stored unless you request that they will be deleted at first view or after 24 hours / one week / one month / one year.
## License
Lutim is licensed under the terms of the AGPL. See the LICENSE file.
## Official instance
You can see it working at https://lut.im.
## Logo
Lutim's logo is an adaptation of [Lutin](http://commons.wikimedia.org/wiki/File:Lutin_by_godo.jpg) by [Godo](http://godoillustrateur.wordpress.com/), licensed under the terms of the CC-BY-SA 3.0 license.
![Lutim's logo](https://lut.im/img/Lutim_small.png)
![Lutim's logo](https://lutim.fiat-tux.fr/img/Lutim_small.png)
## Wiki
The official wiki contains all what you need to know about Lutim (installation, API, etc). Go to <https://framagit.org/luc/lutim/wikis/home> or clone it:
The official wiki contains all you need to know about Lutim (installation, API, etc.). Go to <https://framagit.org/fiat-tux/hat-softwares/lutim/-/wikis/home> or clone it:
```
git clone https://framagit.org/luc/lutim.wiki.git
git clone https://framagit.org/fiat-tux/hat-softwares/lutim.wiki.git
```
## Encryption
Lutim does encryption on the server if asked to, but does not store the key.
The encryption is made on the server since Lutim is made to be usable even without javascript. If you want to add client-side encryption for javascript-enabled browsers, patches are welcome.
The encryption is done on the server since Lutim is made to be usable even without javascript. If you want to add client-side encryption for javascript-enabled browsers, patches are welcome.
## Internationalization
Lutim comes with English, French and Spanish languages. It will choose the language to display from the browser's settings.
## Authors
See [AUTHORS.md](AUTHORS.md) file.
## Contribute!
Please consider contributing, either by [reporting issues](https://framagit.org/luc/lutim/issues) or by helping the [internationalization](https://pootle.framasoft.org/projects/lutim/). And of course, code contribution are welcome!
Please consider contributing, either by [reporting issues](https://framagit.org/fiat-tux/hat-softwares/lutim/issues) or by helping the [internationalization](https://weblate.framasoft.org/projects/lutim/). And of course, code contribution are welcome!
The details on how to contribute are on the [wiki](https://framagit.org/luc/lutim/wikis/contribute).
The details on how to contribute are on the [wiki](https://framagit.org/fiat-tux/hat-softwares/lutim/wikis/contribute).
## Others projects dependancies
## Make a donation
You can make a donation to the author on [Tipeee](https://www.tipeee.com/fiat-tux) or on [Liberapay](https://liberapay.com/sky/).
## Others projects dependencies
Lutim is written in Perl with the [Mojolicious](http://mojolicio.us) framework.
@@ -56,11 +56,16 @@ It uses:
* [Twitter bootstrap](http://getbootstrap.com) framework to look not too ugly
* [JQuery](http://jquery.com) and [JQuery File Uploader](https://github.com/danielm/uploader/) (slightly modified) to add some modernity
* [Raphaël](http://raphaeljs.com/) and [morris.js](http://www.oesmith.co.uk/morris.js/) for stats graphs
* [Raphaël](http://raphaeljs.com/) and [morris.js](https://morrisjs.github.io/morris.js/) for stats graphs
* [freezeframe.js](http://freezeframe.chrisantonellis.com/) (slightly modified) to be able to freeze animated gifs in twitter card
* [Moment.js](http://momentjs.com/) for displaying real dates instead of unix timestamps.
* [Fontello](http://fontello.com/) and the [markdown font](https://github.com/dcurtis/markdown-mark/) for the icons, licenses for the fontello icons fonts are in `public/font/LICENSE.txt`
* [Henny Penny](https://www.google.com/fonts/specimen/Henny+Penny) font designed by Olga Umpeleva for [Brownfox](http://brownfox.org)
* [Unite gallery](http://unitegallery.net/) for the gallery
* [PhotoSwipe](http://photoswipe.com/) for the gallery
* [JSZip](https://stuk.github.io/jszip/) for generating a zip containing all the images in the gallery
* [FileSaver.js](https://github.com/eligrey/FileSaver.js/) for saving the zip
* [Toastify JS](https://apvarun.github.io/toastify-js/) for notifications
## Deploy lutim
An ansible role and a terraform plan reside under the `.provision` directory. An user could utilize the terraform plan if they chose to deploy lutim on AWS, if that's not the goal, they could simply execute the ansible role in part. Usage docs for both are present in their respective directories.

View File

@@ -1,19 +1,21 @@
requires 'Mojolicious';
requires 'Mojolicious', '>= 7.31';
requires 'EV';
requires 'IO::Socket::SSL';
requires 'Net::SSLeay', '>= 1.81';
requires 'Data::Validate::URI';
requires 'Net::Domain::TLD', '>= 1.73'; # Must have the last version to handle (at least) .xyz and .link
requires 'Net::Domain::TLD', '>= 1.75'; # Must have the last version to handle (at least) .xyz and .link
requires 'Mojolicious::Plugin::I18N';
requires 'Mojolicious::Plugin::AssetPack';
requires 'Mojolicious::Plugin::DebugDumperHelper';
requires 'ORLite';
requires 'Mojolicious::Plugin::StaticCache';
requires 'Mojolicious::Plugin::GzipStatic';
requires 'Mojolicious::Plugin::CSPHeader';
requires 'Text::Unidecode';
requires 'DateTime';
requires 'Filesys::DiskUsage';
requires 'Switch';
requires 'Data::Validate::URI';
requires 'Crypt::CBC';
requires 'Crypt::Blowfish';
requires 'Digest::MD5';
requires 'Locale::Maketext';
requires 'Locale::Maketext::Extract';
requires 'File::MimeInfo';
@@ -22,3 +24,37 @@ requires 'Image::ExifTool';
requires 'Data::Entropy';
requires 'List::MoreUtils', '> 0.33';
requires 'Archive::Zip';
requires 'ISO::639_1';
feature 'postgresql', 'PostgreSQL support' => sub {
requires 'Mojo::Pg';
requires 'Mojolicious::Plugin::PgURLHelper';
};
feature 'sqlite', 'SQLite support' => sub {
requires 'Mojo::SQLite', '>= 3.000';
requires 'Minion::Backend::SQLite', '>= 4.001';
requires 'DBD::SQLite', '>= 1.66';
};
feature 'minion', 'Minion support' => sub {
requires 'Minion';
};
feature 'cache', 'Cache system' => sub {
requires 'Mojolicious::Plugin::CHI';
requires 'Data::Serializer';
};
feature 'memcached', 'Cache system using Memcached' => sub {
requires 'Mojolicious::Plugin::CHI';
requires 'CHI::Driver::Memcached';
requires 'Cache::Memcached';
};
feature 'ldap', 'LDAP authentication support' => sub {
requires 'Net::LDAP';
requires 'Mojolicious::Plugin::Authentication';
};
feature 'htpasswd', 'Htpasswd authentication support' => sub {
requires 'Apache::Htpasswd';
requires 'Mojolicious::Plugin::Authentication';
};
feature 'test' => sub {
requires 'Devel::Cover';
};

File diff suppressed because it is too large Load Diff

22
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,22 @@
version: '3.3'
services:
app_dev:
build: .
ports:
- 8080:8080
volumes:
- .:/home/lutim
command: dev
postgres_dev:
image: postgres:11.2-alpine
environment:
POSTGRES_PASSWORD: password
POSTGRES_USER: lutim
POSTGRES_DB: lutim
memcached:
image: memcached:1.5-alpine
adminer:
image: dehy/adminer
ports:
- 8081:80

28
docker-compose.yml Normal file
View File

@@ -0,0 +1,28 @@
version: '3.3'
services:
app:
build: .
ports:
- 8080:8080
volumes:
- ./lutim.conf:/home/lutim/lutim.conf:ro
db:
image: postgres:11.2-alpine
environment:
POSTGRES_PASSWORD: password
POSTGRES_USER: lutim
POSTGRES_DB: lutim
cache:
image: memcached:1.5-alpine
minion:
build: .
command: minion
volumes:
- ./lutim.conf:/home/lutim/lutim.conf:ro
minion_db:
image: postgres:11.2-alpine
environment:
POSTGRES_PASSWORD: password
POSTGRES_USER: lutim_minion
POSTGRES_DB: lutim_minion

39
docker-stack.yml Normal file
View File

@@ -0,0 +1,39 @@
version: '3.3'
services:
app:
image: aquinum/lutim
configs:
- source: lutim.conf
target: /home/lutim/lutim.conf
uid: '1000'
gid: '1000'
mode: 0440
deploy:
replicas: 1
db:
image: postgres:11.2-alpine
environment:
POSTGRES_PASSWORD: <changeme>
POSTGRES_USER: lutim
POSTGRES_DB: lutim
cache:
image: memcached:1.5-alpine
minion:
image: aquinum/lutim
command: minion
configs:
- source: lutim.conf
target: /home/lutim/lutim.conf
uid: '1000'
gid: '1000'
mode: 0440
minion_db:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: <changeme>
MYSQL_DATABASE: lutim_minion
configs:
lutim.conf:
file: ./lutim.conf

44
docker/entrypoint.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/bash
set -eu
cd ~lutim
if [ "${1:-}" == "dev" ]
then
echo ""
echo ""
echo "Container started in dev mode. Connect to the container with the following command:"
echo " docker-compose -f docker-compose.dev.yml exec -u root app_dev sh"
echo ""
echo ""
echo "You can then install the build dependencies with this command"
echo " sh ~lutim/docker/install-dev-env.sh"
tail -f /dev/null
exit 0
fi
# If MySQL/PostgreSQL, wait for database to be up
DB_TYPE=$(perl utilities/read_conf.pl dbtype sqlite)
DB_HOST=
DB_PORT=
if [ "$DB_TYPE" == "postgresql" ]
then
DB_HOST=$(perl utilities/read_conf.pl pgdb/host db)
DB_PORT=$(perl utilities/read_conf.pl pgdb/port 5432)
fi
if [ -n "$DB_HOST" ] && [ -n "$DB_PORT" ]
then
while ! nc -vz "${DB_HOST}" "${DB_PORT}"; do
echo "Waiting for database..."
sleep 1;
done
fi
if [ "${1:-}" == "minion" ]
then
exec carton exec script/application minion worker
fi
exec carton exec hypnotoad -f script/lutim

View File

@@ -1,9 +1,9 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim;
use Mojo::Base 'Mojolicious';
use Mojo::Util qw(quote);
use LutimModel;
use Crypt::CBC;
use Data::Entropy qw(entropy_source);
use Mojo::IOLoop;
use Lutim::DB::Image;
use Lutim::DefaultConfig qw($default_config);
use vars qw($im_loaded);
BEGIN {
@@ -16,7 +16,7 @@ BEGIN {
}
}
$ENV{MOJO_TMPDIR} = 'tmp';
$ENV{MOJO_TMPDIR} = 'tmp' unless (defined $ENV{MOJO_TMPDIR});
mkdir($ENV{MOJO_TMPDIR}, 0700) unless (-d $ENV{MOJO_TMPDIR});
# This method will run once at server start
sub startup {
@@ -24,255 +24,118 @@ sub startup {
$self->{wait_for_it} = {};
$self->plugin('I18N');
$self->plugin('AssetPack');
push @{$self->commands->namespaces}, 'Lutim::Command';
$self->plugin('DebugDumperHelper');
my $config = $self->plugin('Config', {
default => {
provisioning => 100,
provis_step => 5,
length => 8,
always_encrypt => 0,
anti_flood_delay => 5,
tweet_card_via => '@framasky',
max_file_size => 10*1024*1024,
https => 0,
default_delay => 0,
max_delay => 0,
token_length => 24,
crypto_key_length => 8,
thumbnail_size => 100,
}
default => $default_config
});
if ($config->{watermark_path}) {
die sprintf('%s does not exist or is not readable.', $config->{watermark_path}) unless -r $config->{watermark_path};
my $valid = {
center => 1,
north => 1,
northeast => 1,
east => 1,
southeast => 1,
south => 1,
southwest => 1,
west => 1,
northwest => 1
};
die sprintf('%s is not a valid value for watermark_placement.', $config->{watermark_placement}) unless $valid->{lc($config->{watermark_placement})};
$valid = {
'tiling' => 1,
'single' => 1,
'none' => 1
};
die sprintf('%s is not a valid value for watermark_default.', $config->{watermark_default}) unless $valid->{lc($config->{watermark_default})};
die sprintf('%s is not a valid value for watermark_enforce.', $config->{watermark_enforce}) unless $valid->{lc($config->{watermark_enforce})};
}
if (scalar(@{$config->{memcached_servers}})) {
$self->plugin(CHI => {
lutim_images_cache => {
driver => 'Memcached',
servers => $config->{memcached_servers},
expires_in => '1 day',
expires_on_backend => 1,
}
});
} elsif ($config->{cache_max_size} != 0) {
my $cache_max_size = 8 * 1024 * 1024 * $config->{cache_max_size};
$self->plugin(CHI => {
lutim_images_cache => {
driver => 'Memory',
global => 1,
is_size_aware => 1,
max_size => $cache_max_size,
expires_in => '1 day'
}
});
}
die "You need to provide a contact information in lutim.conf !" unless (defined($config->{contact}));
$ENV{MOJO_MAX_MESSAGE_SIZE} = $config->{max_file_size};
$self->secrets($config->{secrets});
$self->helper(
render_file => sub {
my $c = shift;
my ($filename, $path, $mediatype, $dl, $expires, $nocache, $key, $thumb) = @_;
# Themes handling
shift @{$self->renderer->paths};
shift @{$self->static->paths};
if ($config->{theme} ne 'default') {
my $theme = $self->home->rel_file('themes/'.$config->{theme});
push @{$self->renderer->paths}, $theme.'/templates' if -d $theme.'/templates';
push @{$self->static->paths}, $theme.'/public' if -d $theme.'/public';
}
push @{$self->renderer->paths}, $self->home->rel_file('themes/default/templates');
push @{$self->static->paths}, $self->home->rel_file('themes/default/public');
$filename = quote($filename);
# Internationalization
my $lib = $self->home->rel_file('themes/'.$config->{theme}.'/lib');
eval qq(use lib "$lib");
$self->plugin('I18N');
my $asset;
unless (-f $path && -r $path) {
$c->app->log->error("Cannot read file [$path]. error [$!]");
$c->flash(
msg => $c->l('Unable to find the image: it has been deleted.')
);
return 500;
# Static assets gzipping
$self->plugin('GzipStatic');
# Headers
$self->plugin('Lutim::Plugin::Headers');
# Helpers
$self->plugin('Lutim::Plugin::Helpers');
$self->plugin('Lutim::Plugin::Lang');
# Create directory if needed
mkdir($self->config('upload_dir'), 0700) unless (-d $self->config('upload_dir'));
die ('The upload directory ('.$self->config('upload_dir').') is not writable') unless (-w $self->config('upload_dir'));
# Minion
if ($config->{minion}->{enabled}) {
$self->config->{minion}->{dbtype} = 'sqlite' unless defined $config->{minion}->{dbtype};
if ($config->{minion}->{dbtype} eq 'sqlite') {
$config->{minion}->{db_path} = 'minion.db' unless defined $config->{minion}->{db_path};
$self->plugin('Minion' => { SQLite => 'sqlite:'.$config->{minion}->{db_path} });
} elsif ($config->{minion}->{dbtype} eq 'postgresql') {
$self->plugin('PgURLHelper');
$self->plugin('Minion' => { Pg => $self->pg_url($config->{minion}->{'pgdb'}) });
}
$self->app->minion->add_task(
accessed => sub {
my $job = shift;
my $short = $job->args->[0];
my $time = $job->args->[1];
my $img = Lutim::DB::Image->new(app => $job->app, short => $short);
$img->accessed($time) if $img->path;
}
);
}
$mediatype =~ s/x-//;
my $headers = Mojo::Headers->new();
if ($nocache) {
$headers->add('Cache-Control' => 'no-cache, no-store, max-age=0, must-revalidate');
} else {
$headers->add('Expires' => $expires);
}
$headers->add('Content-Type' => $mediatype.';name='.$filename);
$headers->add('Content-Disposition' => $dl.';filename='.$filename);
$c->res->content->headers($headers);
if ($key) {
$asset = $c->decrypt($key, $path);
} else {
$asset = Mojo::Asset::File->new(path => $path);
}
if (defined $thumb && $im_loaded && $mediatype ne 'image/svg+xml' && $mediatype !~ m#image/(x-)?xcf# && $mediatype ne 'image/webp') { # ImageMagick don't work in Debian with svg (for now?)
my $im = Image::Magick->new;
$im->BlobToImage($asset->slurp);
# Create the thumbnail
$im->Resize(geometry=>'x'.$c->config('thumbnail_size'));
# Replace the asset with the thumbnail
$asset = Mojo::Asset::Memory->new->add_chunk($im->ImageToBlob());
}
$c->res->content->asset($asset);
$headers->add('Content-Length' => $asset->size);
return $c->rendered(200);
}
);
$self->helper(
ip => sub {
my $c = shift;
my $ip_only = shift || 0;
my $proxy = $c->req->headers->header('X-Forwarded-For');
my $ip = ($proxy) ? $proxy : $c->tx->remote_address;
my $remote_port = (defined($c->req->headers->header('X-Remote-Port'))) ? $c->req->headers->header('X-Remote-Port') : $c->tx->remote_port;
return ($ip_only) ? $ip : "$ip remote port:$remote_port";
}
);
$self->helper(
provisioning => sub {
my $c = shift;
# Create some short patterns for provisioning
if (LutimModel::Lutim->count('WHERE path IS NULL') < $c->config->{provisioning}) {
for (my $i = 0; $i < $c->config->{provis_step}; $i++) {
if (LutimModel->begin) {
my $short;
do {
$short= $c->shortener($c->config->{length});
} while (LutimModel::Lutim->count('WHERE short = ?', $short) || $short eq 'about' || $short eq 'stats' || $short eq 'd' || $short eq 'm' || $short eq 'gallery' || $short eq 'zip');
LutimModel::Lutim->create(
short => $short,
counter => 0,
enabled => 1,
delete_at_first_view => 0,
delete_at_day => 0,
mod_token => $c->shortener($c->config->{token_length})
);
LutimModel->commit;
}
}
}
}
);
$self->helper(
shortener => sub {
my $c = shift;
my $length = shift;
my @chars = ('a'..'z','A'..'Z','0'..'9');
my $result = '';
foreach (1..$length) {
$result .= $chars[entropy_source->get_int(scalar(@chars))];
}
return $result;
}
);
$self->helper(
stop_upload => sub {
my $c = shift;
if (-f 'stop-upload' || -f 'stop-upload.manual') {
$c->stash(
stop_upload => $c->l('Uploading is currently disabled, please try later or contact the administrator (%1).', $config->{contact})
);
return 1;
}
return 0;
}
);
$self->helper(
max_delay => sub {
my $c = shift;
return $c->config->{max_delay} if ($c->config->{max_delay} >= 0);
warn "max_delay set to a negative value. Default to 0.";
return 0;
}
);
$self->helper(
default_delay => sub {
my $c = shift;
return $c->config->{default_delay} if ($c->config->{default_delay} >= 0);
warn "default_delay set to a negative value. Default to 0.";
return 0;
}
);
$self->helper(
is_selected => sub {
my $c = shift;
my $num = shift;
return ($num == $c->default_delay) ? 'selected="selected"' : '';
}
);
$self->helper(
crypt => sub {
my $c = shift;
my $upload = shift;
my $filename = shift;
my $key = $c->shortener($c->config('crypto_key_length'));
my $cipher = Crypt::CBC->new(
-key => $key,
-cipher => 'Blowfish',
-header => 'none',
-iv => 'dupajasi'
);
$cipher->start('encrypting');
my $crypt_asset = Mojo::Asset::File->new;
$crypt_asset->add_chunk($cipher->crypt($upload->slurp));
$crypt_asset->add_chunk($cipher->finish);
my $crypt_upload = Mojo::Upload->new;
$crypt_upload->filename($filename);
$crypt_upload->asset($crypt_asset);
return ($crypt_upload, $key);
}
);
$self->helper(
decrypt => sub {
my $c = shift;
my $key = shift;
my $file = shift;
my $cipher = Crypt::CBC->new(
-key => $key,
-cipher => 'Blowfish',
-header => 'none',
-iv => 'dupajasi'
);
$cipher->start('decrypting');
my $decrypt_asset = Mojo::Asset::File->new;
open(my $f, "<",$file) or die "Unable to read encrypted file: $!";
binmode $f;
while (read($f, my $buffer,1024)) {
$decrypt_asset->add_chunk($cipher->crypt($buffer));
}
$decrypt_asset->add_chunk($cipher->finish) ;
return $decrypt_asset;
}
);
$self->helper(
delete_image => sub {
my $c = shift;
my $image = shift;
unlink $image->path();
$image->update(enabled => 0);
}
);
# Hooks
$self->hook(
before_dispatch => sub {
my $c = shift;
@@ -299,24 +162,116 @@ sub startup {
}
);
$self->hook(
after_dispatch => sub {
my $c = shift;
$c->provisioning();
# Recurrent tasks
Mojo::IOLoop->recurring(5 => sub {
my $loop = shift;
# Purge expired anti-flood protection
my $wait_for_it = $c->app->{wait_for_it};
delete @{$wait_for_it}{grep { time - $wait_for_it->{$_} > $c->config->{anti_flood_delay} } keys %{$wait_for_it}} if (defined($wait_for_it));
$self->provisioning();
# Purge expired anti-flood protection
my $wait_for_it = $self->{wait_for_it};
delete @{$wait_for_it}{grep { time - $wait_for_it->{$_} > $self->config->{anti_flood_delay} } keys %{$wait_for_it}} if (defined($wait_for_it));
});
# Authentication (if configured)
if (defined($config->{ldap}) || defined($config->{htpasswd})) {
if (defined($config->{ldap})) {
require Net::LDAP;
}
);
if (defined($config->{htpasswd})) {
require Apache::Htpasswd;
}
die sprintf('Unable to read %s', $config->{htpasswd}) if (defined($config->{htpasswd}) && !-r $config->{htpasswd});
$self->plugin('Authentication' =>
{
autoload_user => 1,
session_key => 'Lutim',
load_user => sub {
my ($c, $username) = @_;
$self->asset('index.css' => 'css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/animation.css', 'css/uploader.css', 'css/hennypenny.css', 'css/lutim.css', 'css/markdown.css');
$self->asset('stats.css' => 'css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/morris-0.4.3.min.css', 'css/hennypenny.css', 'css/lutim.css');
$self->asset('about.css' => 'css/bootstrap.min.css', 'css/fontello-embedded.css', 'css/hennypenny.css', 'css/lutim.css');
return $username;
},
validate_user => sub {
my ($c, $username, $password, $extradata) = @_;
$self->asset('index.js' => 'js/jquery-2.1.0.min.js', 'js/bootstrap.min.js', 'js/lutim.js', 'js/dmuploader.min.js');
$self->asset('stats.js' => 'js/jquery-2.1.0.min.js', 'js/bootstrap.min.js', 'js/lutim.js', 'js/raphael-min.js', 'js/morris-0.4.3.min.js', 'js/stats.js');
$self->asset('freeze.js' => 'js/jquery-2.1.0.min.js', 'js/freezeframe.min.js');
if (defined($c->config('ldap'))) {
my $ldap = Net::LDAP->new($c->config->{ldap}->{uri});
my $mesg;
if (defined($c->config->{ldap}->{bind_dn}) && defined($c->config->{ldap}->{bind_pwd})) {
# connect to the ldap server using the bind credentials
$mesg = $ldap->bind(
$c->config->{ldap}->{bind_dn},
password => $c->config->{ldap}->{bind_pwd}
);
} else {
# anonymous bind
$mesg = $ldap->bind
}
if ($mesg->code) {
$c->app->log->info('[LDAP INFO] Authenticated bind failed - Login: '.$c->config->{ldap}->{bind_dn}) if defined($c->config->{ldap}->{bind_dn});
$c->app->log->error('[LDAP ERROR] Error on bind: '.$mesg->error);
return undef;
}
my $ldap_user_attr = $c->config->{ldap}->{user_attr};
my $ldap_user_filter = $c->config->{ldap}->{user_filter};
# search the ldap database for the user who is trying to login
$mesg = $ldap->search(
base => $c->config->{ldap}->{user_tree},
filter => "(&($ldap_user_attr=$username)$ldap_user_filter)"
);
if ($mesg->code) {
$c->app->log->error('[LDAP ERROR] Error on search: '.$mesg->error);
return undef;
}
# check to make sure that the ldap search returned at least one entry
my @entries = $mesg->entries;
my $entry = $entries[0];
unless (defined $entry) {
$c->app->log->info("[LDAP INFO] Authentication failed - User $username filtered out, IP: ".$c->ip);
return undef;
}
# retrieve the first user returned by the search
$c->app->log->debug("[LDAP DEBUG] Found user dn: ".$entry->dn);
# Now we know that the user exists
$mesg = $ldap->bind($entry->dn,
password => $password
);
if ($mesg->code) {
$c->app->log->info("[LDAP INFO] Authentication failed - Login: $username, IP: ".$c->ip);
$c->app->log->error('[LDAP ERROR] Authentication failed '.$mesg->error);
return undef;
}
$c->app->log->info("[LDAP INFO] Authentication successful - Login: $username, IP: ".$c->ip);
} elsif (defined($c->config('htpasswd'))) {
my $htpasswd = new Apache::Htpasswd(
{
passwdFile => $c->config('htpasswd'),
ReadOnly => 1
}
);
if (!$htpasswd->htCheckPassword($username, $password)) {
return undef;
}
$c->app->log->info("[Simple authentication successful] login: $username, IP: ".$c->ip);
}
return $username;
}
}
);
$self->app->sessions->default_expiration($config->{session_duration});
}
$self->defaults(layout => 'default');
@@ -325,6 +280,14 @@ sub startup {
# Router
my $r = $self->routes;
$r->add_condition(authorized => sub {
my ($r, $c, $captures) = @_;
return 1 unless (defined($config->{ldap}) || defined($config->{htpasswd}));
return $c->is_user_authenticated;
});
$r->options(sub {
my $c = shift;
$c->res->headers->allow('POST') if (defined($c->config->{allowed_domains}));
@@ -332,63 +295,135 @@ sub startup {
});
$r->get('/')->
to('Controller#home')->
requires('authorized')->
to('Image#home')->
name('index');
$r->get('/')->
to('Authent#index');
if (defined $config->{ldap} || defined $config->{htpasswd}) {
# Login page
$r->get('/login')
->to('Authent#index')
->name('login');
# Authentication
$r->post('/login')
->to('Authent#login');
# Logout page
$r->get('/logout')
->to('Authent#log_out')
->name('logout');
}
$r->get('/about')->
to('Controller#about')->
to('Image#about')->
name('about');
$r->get('/infos')->
to('Image#infos')->
name('infos');
$r->get('/stats')->
to('Controller#stats')->
to('Image#stats')->
name('stats');
$r->get('/lang/:l')->
to('Image#change_lang')->
name('lang');
$r->get('/partial/<:file>.<:f>' => sub {
my $c = shift;
$c->render(
template => 'partial/'.$c->param('file'),
format => 'js',
layout => undef,
d => {
delay_0 => $c->l('no time limit'),
delay_1 => $c->l('24 hours'),
delay_365 => $c->l('1 year')
}
);
})->name('partial');
$r->get('/gallery' => sub {
shift->render(
template => 'gallery',
);
})->name('gallery');
$r->get('/myfiles' => sub {
shift->render(
template => 'myfiles'
);
})->name('myfiles');
$r->get('/myfiles')->
requires('authorized')->
name('myfiles');
$r->get('/myfiles')->
to('Authent#index');
$r->get('/manifest.webapp')->
to('Controller#webapp')->
to('Image#webapp')->
name('manifest.webapp');
$r->get('/zip')
->to('Controller#zip')
->to('Image#zip')
->name('zip');
$r->get('/random')
->to('Image#random')
->name('random');
$r->post('/')->
to('Controller#add')->
requires('authorized')->
to('Image#add')->
name('add');
$r->post('/')->
to('Authent#index');
$r->get('/d/:short/:token')->
to('Controller#delete')->
requires('authorized')->
to('Image#delete')->
name('delete');
$r->get('/d/:short/:token')->
to('Authent#index');
$r->post('/m/:short/:token')->
to('Controller#modify')->
requires('authorized')->
to('Image#modify')->
name('modify');
$r->post('/m/:short/:token')->
to('Authent#index');
$r->post('/c')->
to('Controller#get_counter')->
requires('authorized')->
to('Image#get_counter')->
name('counter');
$r->post('/c')->
to('Authent#index');
$r->get('/(:short).(:f)')->
to('Controller#short')->
$r->get('/about/<:short>')->
to('Image#about_img')->
name('about_img');
$r->get('/about/<:short>.<:f>')->
to('Image#about_img')->
name('about_img');
$r->get('/about/:short/<:key>.<:f>')->
to('Image#about_img')->
name('about_img');
$r->get('/<:short>.<:f>')->
to('Image#short')->
name('short');
$r->get('/:short')->
to('Controller#short');
$r->get('/:short/(:key).(:f)')->
to('Controller#short');
$r->get('/:short')->
to('Image#short');
$r->get('/:short/<:key>.<:f>')->
to('Image#short');
$r->get('/:short/:key')->
to('Controller#short');
to('Image#short');
}
1;

View File

@@ -1,3 +1,4 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Command::cron;
use Mojo::Base 'Mojolicious::Commands';

View File

@@ -1,7 +1,9 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Command::cron::cleanbdd;
use Mojo::Base 'Mojolicious::Command';
use LutimModel;
use Mojo::Util qw(slurp decode);
use Mojo::File;
use Lutim::DB::Image;
use Lutim::DefaultConfig qw($default_config);
use FindBin qw($Bin);
use File::Spec qw(catfile);
@@ -11,20 +13,23 @@ has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $config = $c->app->plugin('Config', {
file => File::Spec->catfile($Bin, '..' ,'lutim.conf'),
default => {
keep_ip_during => 365,
my $cfile = Mojo::File->new($Bin, '..' , 'lutim.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => $default_config
});
my $separation = time() - $config->{keep_ip_during} * 86400;
LutimModel->do(
'UPDATE lutim SET created_by = "" WHERE path IS NOT NULL AND created_at < ?',
{},
$separation
);
my $dbi = Lutim::DB::Image->new(app => $c->app);
$dbi->clean_ips_until($separation);
}
=encoding utf8

View File

@@ -1,8 +1,10 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Command::cron::cleanfiles;
use Mojo::Base 'Mojolicious::Command';
use LutimModel;
use Mojo::File;
use Lutim::DB::Image;
use Lutim::DefaultConfig qw($default_config);
use Lutim;
use Mojo::Util qw(slurp decode);
use FindBin qw($Bin);
use File::Spec qw(catfile);
@@ -11,26 +13,38 @@ has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $l = Lutim->new;
my $time = time();
my @images = LutimModel::Lutim->select('WHERE enabled = 1 AND (delete_at_day * 86400) < (? - created_at) AND delete_at_day != 0', $time);
for my $image (@images) {
$l->app->delete_image($image);
my $cfile = Mojo::File->new($Bin, '..' , 'lutim.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $c->app->plugin('Config', {
file => File::Spec->catfile($Bin, '..' ,'lutim.conf'),
file => $cfile,
default => $default_config
});
if (defined($config->{delete_no_longer_viewed_files}) && $config->{delete_no_longer_viewed_files} > 0) {
$time = time() - $config->{delete_no_longer_viewed_files} * 86400;
@images = LutimModel::Lutim->select('WHERE enabled = 1 AND last_access_at < ?', $time);
my $l = Lutim->new;
for my $image (@images) {
$l->app->delete_image($image);
my $dbi = Lutim::DB::Image->new(app => $c->app);
$dbi->get_images_to_clean()->each(
sub {
my ($img, $num) = @_;
$l->app->delete_image($img);
}
);
if (defined($config->{delete_no_longer_viewed_files}) && $config->{delete_no_longer_viewed_files} > 0) {
my $time = time() - $config->{delete_no_longer_viewed_files} * 86400;
$dbi->get_no_longer_viewed_files($time)->each(
sub {
my ($img, $num) = @_;
$l->app->delete_image($img);
}
);
}
}

View File

@@ -1,11 +1,16 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Command::cron::stats;
use Mojo::Base 'Mojolicious::Command';
use LutimModel;
use Mojo::DOM;
use Mojo::Util qw(slurp spurt decode);
use Mojo::Util qw(encode);
use Mojo::File;
use Mojo::JSON qw(encode_json);
use Lutim::DB::Image;
use Lutim::DefaultConfig qw($default_config);
use DateTime;
use FindBin qw($Bin);
use File::Spec qw(catfile);
use POSIX;
has description => 'Generate statistics about Lutim.';
has usage => sub { shift->extract_usage };
@@ -13,14 +18,26 @@ has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $config = $c->app->plugin('Config', {
file => File::Spec->catfile($Bin, '..' ,'lutim.conf'),
default => {
stats_day_num => 365
my $cfile = Mojo::File->new($Bin, '..' , 'lutim.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => $default_config
});
my $text = slurp('templates/data.html.ep.template');
my $template = 'themes/'.$config->{theme}.'/templates/data.html.ep.template';
unless (-e $template) {
$template = 'themes/default/templates/data.html.ep.template';
}
my $stats = {};
my $text = Mojo::File->new($template)->slurp;
my $dom = Mojo::DOM->new($text);
my $thead_tr = $dom->at('table thead tr');
my $tbody_tr = $dom->at('table tbody tr');
@@ -29,29 +46,163 @@ sub run {
my $separation = time() - $config->{stats_day_num} * 86400;
my %data;
for my $img (LutimModel::Lutim->select('WHERE path IS NOT NULL AND created_at >= ?', $separation)) {
my $time = DateTime->from_epoch(epoch => $img->created_at);
my ($year, $month, $day) = ($time->year(), $time->month(), $time->day());
my $img = Lutim::DB::Image->new(app => $c->app);
my $sca = $img->select_created_after($separation);
if (defined($data{$year}->{$month}->{$day})) {
$data{$year}->{$month}->{$day} += 1;
} else {
$data{$year}->{$month}->{$day} = 1;
}
}
$stats->{total} = $img->count_not_empty;
$stats->{average} = floor($sca->size / $config->{stats_day_num}) if $config->{stats_day_num};
$stats->{for_days} = $config->{stats_day_num};
my $total = LutimModel::Lutim->count('WHERE path IS NOT NULL AND created_at < ?', $separation);
for my $year (sort {$a <=> $b} keys %data) {
for my $month (sort {$a <=> $b} keys %{$data{$year}}) {
for my $day (sort {$a <=> $b} keys %{$data{$year}->{$month}}) {
$thead_tr->append_content('<th>'."$day/$month/$year".'</th>'."\n");
$tbody_tr->append_content('<td>'.$data{$year}->{$month}->{$day}.'</td>'."\n");
$total += $data{$year}->{$month}->{$day};
$tbody_t2->append_content('<td>'.$total.'</td>'."\n");
$sca->each(
sub {
my ($e, $num) = @_;
my $time = DateTime->from_epoch(epoch => $e->created_at);
my ($year, $month, $day) = ($time->year(), $time->month(), $time->day());
if (defined($data{$year}->{$month}->{$day})) {
$data{$year}->{$month}->{$day} += 1;
} else {
$data{$year}->{$month}->{$day} = 1;
}
}
);
my $total = $img->count_created_before($separation);
if (scalar(keys %data)) {
for my $year (sort {$a <=> $b} keys %data) {
for my $month (sort {$a <=> $b} keys %{$data{$year}}) {
for my $day (sort {$a <=> $b} keys %{$data{$year}->{$month}}) {
$thead_tr->append_content(sprintf("<th>%#.2d/%#.2d/%d</th>\n", $day, $month, $year));
$tbody_tr->append_content(sprintf("<td>%d</td>\n", $data{$year}->{$month}->{$day}));
$total += $data{$year}->{$month}->{$day};
$tbody_t2->append_content(sprintf("<td>%d</td>\n", $total));
}
}
}
} else {
my $dt = DateTime->from_epoch(epoch => $separation);
$thead_tr->append_content(sprintf("<th>%#.2d/%#.2d/%d</th>\n", $dt->day(), $dt->month(), $dt->year()));
$tbody_tr->append_content("<td>0</td>\n");
$tbody_t2->append_content(sprintf("<td>%d</td>\n", $total));
$dt = DateTime->now();
$thead_tr->append_content(sprintf("<th>%#.2d/%#.2d/%d</th>\n", $dt->day(), $dt->month(), $dt->year()));
$tbody_tr->append_content("<td>0</td>\n");
$tbody_t2->append_content(sprintf("<td>%d</td>\n", $total));
}
spurt $dom, 'templates/data.html.ep';
my $moy = $total / $config->{stats_day_num};
# Raw datas
my $template2 = 'themes/'.$config->{theme}.'/templates/raw.html.ep.template';
unless (-e $template2) {
$template2 = 'themes/default/templates/raw.html.ep.template';
}
my $text2 = Mojo::File->new($template2)->slurp;
my $dom2 = Mojo::DOM->new($text2);
my $raw = $dom2->at('table tbody');
my $raw_foot = $dom2->at('table tfoot');
my $unlimited_enabled = $img->count_delete_at_day_endis(0, 1);
my $unlimited_disabled = $img->count_delete_at_day_endis(0, 0);
my $day_enabled = $img->count_delete_at_day_endis(1, 1);
my $day_disabled = $img->count_delete_at_day_endis(1, 0);
my $week_enabled = $img->count_delete_at_day_endis(7, 1);
my $week_disabled = $img->count_delete_at_day_endis(7, 0);
my $month_enabled = $img->count_delete_at_day_endis(30, 1);
my $month_disabled = $img->count_delete_at_day_endis(30, 0);
my $year_enabled = $img->count_delete_at_day_endis(365, 1);
my $year_disabled = $img->count_delete_at_day_endis(365, 0);
my $year_disabled_in_month = $img->count_delete_at_day_endis(365, 1, time - 335 * 86400);
$stats->{unlimited} = {
enabled => $unlimited_enabled,
disabled => $unlimited_disabled
};
$stats->{day} = {
enabled => $day_enabled,
disabled => $day_disabled
};
$stats->{week} = {
enabled => $week_enabled,
disabled => $week_disabled
};
$stats->{month} = {
enabled => $month_enabled,
disabled => $month_disabled
};
$stats->{year} = {
enabled => $year_enabled,
disabled => $year_disabled
};
my $year_disabled_in_month_pct = ($year_enabled != 0) ? " (".sprintf('%.2f', 100*$year_disabled_in_month/$year_enabled)."%)" : '';
$raw->append_content("\n<tr><td><%= \$raw[4] %></td><td>".$unlimited_enabled."</td><td>".$unlimited_disabled."</td><td>ø</td></tr>\n");
$raw->append_content("<tr><td><%= \$raw[5] %></td><td>".$day_enabled."</td><td>".$day_disabled."</td><td>".$day_enabled." (100%)</td></tr>\n");
$raw->append_content("<tr><td><%= \$raw[6] %></td><td>".$week_enabled."</td><td>".$week_disabled."</td><td>".$week_enabled." (100%)</td></tr>\n");
$raw->append_content("<tr><td><%= \$raw[7] %></td><td>".$month_enabled."</td><td>".$month_disabled."</td><td>".$month_enabled." (100%)</td></tr>\n");
$raw->append_content("<tr><td><%= \$raw[8] %></td><td>".$year_enabled."</td><td>".$year_disabled."</td><td>".$year_disabled_in_month.$year_disabled_in_month_pct."</td></tr>\n");
$raw_foot->append_content("\n<tr><td><%= \$raw[9] %></td><td>".($unlimited_enabled + $day_enabled + $week_enabled + $month_enabled + $year_enabled)."</td><td>".($unlimited_disabled + $day_disabled + $week_disabled + $month_disabled + $year_disabled)."</td><td>".($day_enabled + $week_enabled + $month_enabled + $year_disabled_in_month)."</td></tr>\n");
$dom2 = <<EOF;
% my \@raw = (
% l('Image delay'),
% l('Active images'),
% l('Deleted images'),
% l('Deleted images in 30 days'),
% l('no time limit'),
% l('24 hours'),
% l('%1 days', 7),
% l('%1 days', 30),
% l('1 year'),
% l('Total')
% );
$dom2
EOF
my $js = <<EOF;
var enabled_donut = {
element: 'raw-enabled-holder',
data: [
{label: "<%= l('no time limit') %>", value: $unlimited_enabled},
{label: "<%= l('24 hours') %>", value: $day_enabled},
{label: "<%= l('%1 days', 7) %>", value: $week_enabled},
{label: "<%= l('%1 days', 30) %>", value: $month_enabled},
{label: "<%= l('1 year') %>", value: $year_enabled},
],
colors: [
'#40b489',
'#40b9b1',
'#40a1be',
'#427dc1',
'#455ac3',
]
};
var disabled_donut = {
element: 'raw-disabled-holder',
data: [
{label: "<%= l('no time limit') %>", value: $unlimited_disabled},
{label: "<%= l('24 hours') %>", value: $day_disabled},
{label: "<%= l('%1 days', 7) %>", value: $week_disabled},
{label: "<%= l('%1 days', 30) %>", value: $month_disabled},
{label: "<%= l('1 year') %>", value: $year_disabled},
],
colors: [
'#40b489',
'#40b9b1',
'#40a1be',
'#427dc1',
'#455ac3',
]
};
EOF
Mojo::File->new('themes/'.$config->{theme}.'/templates/stats.json.ep')->spew(encode_json($stats));
Mojo::File->new('themes/'.$config->{theme}.'/templates/data.html.ep')->spew($dom);
Mojo::File->new('themes/'.$config->{theme}.'/templates/raw.html.ep')->spew(encode('UTF-8', $dom2));
mkdir 'themes/'.$config->{theme}.'/templates/partial/' unless -d 'themes/'.$config->{theme}.'/templates/partial/';
Mojo::File->new('themes/'.$config->{theme}.'/templates/partial/raw.js.ep')->spew(encode('UTF-8', $js));
}
=encoding utf8

View File

@@ -1,8 +1,11 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Command::cron::watch;
use Mojo::Base 'Mojolicious::Command';
use Mojo::Util qw(slurp decode);
use Filesys::DiskUsage qw/du/;
use LutimModel;
use Lutim::DB::Image;
use Lutim::DefaultConfig qw($default_config);
use Lutim;
use Mojo::File;
use Switch;
use FindBin qw($Bin);
use File::Spec qw(catfile);
@@ -13,11 +16,16 @@ has usage => sub { shift->extract_usage };
sub run {
my $c = shift;
my $config = $c->app->plugin('Config', {
file => File::Spec->catfile($Bin, '..' ,'lutim.conf'),
default => {
policy_when_full => 'warn'
my $cfile = Mojo::File->new($Bin, '..' , 'lutim.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => $default_config
});
if (defined($config->{max_total_size})) {
@@ -36,11 +44,15 @@ sub run {
}
case 'delete' {
say '[Lutim cron job watch] Older files are being deleted';
my $dbi = Lutim::DB::Image->new(app => $c->app);
my $l = Lutim->new;
do {
for my $img (LutimModel::Lutim->select('WHERE path IS NOT NULL AND enabled = 1 ORDER BY created_at ASC LIMIT 50')) {
unlink $img->path() or warn "Could not unlink ".$img->path.": $!";
$img->update(enabled => 0);
}
$dbi->get_50_oldest()->each(
sub {
my ($img, $num) = @_;
$l->app->delete_image($img);
}
);
} while (du(qw/files/) > $config->{max_total_size});
}
else {

234
lib/Lutim/Command/image.pm Normal file
View File

@@ -0,0 +1,234 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Command::image;
use Mojo::Base 'Mojolicious::Command';
use Mojo::Util qw(getopt);
use Mojo::Collection 'c';
use Lutim::DB::Image;
use Lutim::DefaultConfig qw($default_config);
use FindBin qw($Bin);
use File::Spec qw(catfile);
has description => 'Manage stored images';
has usage => sub { shift->extract_usage };
my $csv_header = 0;
sub run {
my $c = shift;
my @args = @_;
my $cfile = Mojo::File->new($Bin, '..' , 'lutim.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
}
my $config = $c->app->plugin('Config', {
file => $cfile,
default => $default_config
});
if (scalar(@{$config->{memcached_servers}})) {
$c->app->plugin(CHI => {
lutim_images_cache => {
driver => 'Memcached',
servers => $config->{memcached_servers},
expires_in => '1 day',
expires_on_backend => 1,
}
});
}
getopt \@args,
'i|info=s{1,}' => \my @info,
'c|csv' => \my $csv,
'r|remove=s{1,}' => \my @remove,
'y|yes' => \my $yes,
'q|quiet' => \my $quiet,
's|search=s' => \my $ip,
'n|nuke=s' => \my $nuke,
;
if (scalar @info) {
c(@info)->each(
sub {
my ($e, $num) = @_;
my $i = get_short($c, $e);
print_infos($i, $csv) if $i;
}
);
}
if (scalar @remove) {
c(@remove)->each(
sub {
my ($e, $num) = @_;
my $i = get_short($c, $e);
if ($i) {
if ($i->enabled) {
print_infos($i, 0) unless $quiet;
delete_short($c, $i, $yes);
} else {
say sprintf('The image %s is already disabled', $e);
}
}
}
);
if ($config->{cache_max_size} && !scalar(@{$config->{memcached_servers}})) {
say "\nPlease reload Lutim to be sure that the deleted images are not in the cache anymore.";
}
}
if ($ip) {
my $u = Lutim::DB::Image->new(app => $c->app)->search_created_by($ip);
my @shorts;
$u->each(sub {
my ($e, $num) = @_;
push @shorts, $e->short;
print_infos($e, $csv);
});
say sprintf('%d matching images', $u->size);
say sprintf("If you want to delete those images, please do:\n carton exec script/lutim image --remove %s", join(' ', @shorts)) if @shorts;
}
if ($nuke) {
my $i = get_short($c, $nuke);
if ($i && $i->created_by) {
my $u = Lutim::DB::Image->new(app => $c->app)->search_exact_created_by($i->created_by);
my @shorts;
say sprintf('%d images created by the same IP address (%s) than image %s', $u->size, $i->created_by, $nuke);
my $confirm = ($yes) ? 'yes' : undef;
unless (defined $confirm) {
printf('Are you sure you want to remove those %d images? [N/y] ', $u->size);
$confirm = <STDIN>;
chomp $confirm;
}
if ($confirm =~ m/^y(es)?$/i) {
$u->each(sub {
my ($e, $num) = @_;
my $i = get_short($c, $e->short);
if ($i) {
print_infos($i, $csv);
if ($i->enabled) {
delete_short($c, $i, 1);
} else {
say sprintf('The image %s is already disabled', $e->short);
}
}
});
} else {
say 'Answer was not "y" or "yes". Aborting deletion.';
}
} elsif (! $i->created_by) {
say sprintf('Image %s does not contain its creators IP address.', $nuke);
} else {
say sprintf('Sorry, cant find image %s', $nuke);
}
}
}
sub get_short {
my $c = shift;
my $short = shift;
my $i = Lutim::DB::Image->new(app => $c->app, short => $short);
if ($i->path) {
return $i;
} else {
say sprintf('Sorry, unable to find an image with short = %s', $short);
return undef;
}
}
sub print_infos {
my $i = shift;
my $csv = shift;
my $msg;
if ($i) {
if ($csv) {
if (!$csv_header) {
say 'short,path,footprint,enabled,mediatype,filename,counter,delete_at_first_view,delete_at_day,created_at,created_by,last_access_at,width,height';
$csv_header = 1;
}
$msg = '"%s","%s","%s",%d,"%s","%s",%d,"%s",%d,"%s","%s","%s",%d,%d';
} else {
$msg = <<EOF;
%s
path : %s
footprint : %s
enabled : %d
mediatype : %s
filename : %s
counter : %d
delete_at_first_view : %d
delete_at_day : %d
created_at : %s
created_by : %s
last_access_at : %s
width : %d
height : %d
EOF
}
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($i->created_at);
my $created_at = sprintf('%d-%d-%d %d:%d:%d GMT', $year + 1900, ++$mon, $mday, $hour, $min, $sec);
my $last_access_at = '';
if ($i->last_access_at) {
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($i->last_access_at);
$last_access_at = sprintf('%d-%d-%d %d:%d:%d GMT', $year + 1900, ++$mon, $mday, $hour, $min, $sec);
}
say sprintf($msg,
$i->short,
$i->path,
$i->footprint,
$i->enabled,
$i->mediatype,
$i->filename,
$i->counter,
$i->delete_at_first_view,
$i->delete_at_day,
$created_at,
$i->created_by,
$last_access_at,
$i->width,
$i->height
);
}
}
sub delete_short {
my $c = shift;
my $i = shift;
my $y = shift;
my $confirm = ($y) ? 'yes' : undef;
unless (defined $confirm) {
printf('Are you sure you want to remove this image (%s)? [N/y] ', $i->short);
$confirm = <STDIN>;
chomp $confirm;
}
if ($confirm =~ m/^y(es)?$/i) {
$c->app->delete_image($i);
} else {
say 'Answer was not "y" or "yes". Aborting deletion.';
}
}
=encoding utf8
=head1 NAME
Lutim::Command::image - Manage URL in Lutim's database
=head1 SYNOPSIS
Usage:
carton exec script/lutim image --info <short> <short> [--csv] Print infos about the space-separated images (--csv creates a CSV output)
carton exec script/lutim image --remove <short> <short> [--yes] [--quiet] Delete the space-separated images (--yes disables confirmation, --quiet disables informations printing)
carton exec script/lutim image --search <ip> Print infos about the images uploaded by this IP (database LIKE, may include images uploaded by other IPs)
carton exec script/lutim image --nuke <short> Delete the image and all images sent by the same IP address and print infos about the deleted images
=cut
1;

124
lib/Lutim/Command/theme.pm Normal file
View File

@@ -0,0 +1,124 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Command::theme;
use Mojo::Base 'Mojolicious::Commands';
use FindBin qw($Bin);
use File::Spec qw(catfile cat dir);
use File::Path qw(make_path);
has description => 'Create new theme skeleton.';
has usage => sub { shift->extract_usage };
has message => sub { shift->extract_usage . "\nCreate new theme skeleton:\n" };
has namespaces => sub { ['Lutim::Command::theme'] };
sub run {
my $c = shift;
my $name = shift;
unless (defined $name) {
say $c->extract_usage;
exit 1;
}
my $home = File::Spec->catdir($Bin, '..', 'themes', $name);
unless (-d $home) {
# Create skeleton
mkdir $home;
mkdir File::Spec->catdir($home, 'public');
make_path(File::Spec->catdir($home, 'templates', 'layouts'));
make_path(File::Spec->catdir($home, 'lib', 'Lutim', 'I18N'));
my $i18n = <<EOF;
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::I18N;
use base 'Locale::Maketext';
use File::Basename qw/dirname/;
use Locale::Maketext::Lexicon {
_auto => 1,
_decode => 1,
_style => 'gettext',
'*' => [
Gettext => dirname(__FILE__) . '/I18N/*.po',
Gettext => \$app_dir . 'themes/default/lib/Lutim/I18N/*.po',
]
};
use vars qw(\$app_dir);
BEGIN {
use Cwd;
my \$app_dir = getcwd;
}
1;
EOF
open my $f, '>', File::Spec->catfile($home, 'lib', 'Lutim', 'I18N.pm') or die "Unable to open $home/lib/Lutim/I18N.pm: $!";
print $f $i18n;
close $f;
my $makefile = <<EOF;
EN=lib/Lutim/I18N/en.po
FR=lib/Lutim/I18N/fr.po
DE=lib/Lutim/I18N/de.po
ES=lib/Lutim/I18N/es.po
OC=lib/Lutim/I18N/oc.po
AR=lib/Lutim/I18N/ar.po
SEDOPTS=-e "s\@SOME DESCRIPTIVE TITLE\@Lutim language file\@" \\
-e "s\@YEAR THE PACKAGE'S COPYRIGHT HOLDER\@2015 Luc Didry\@" \\
-e "s\@CHARSET\@utf8\@" \\
-e "s\@the PACKAGE package\@the Lutim package\@" \\
-e '/^\\#\\. (/{N;/\\n\\#\\. (/{N;/\\n.*\\.\\.\\/default\\//{s/\\#\\..*\\n.*\\#\\./\\#. (/g}}}' \\
-e '/^\\#\\. (/{N;/\\n.*\\.\\.\\/default\\//{s/\\n/ /}}'
SEDOPTS2=-e '/^\\#.*\\.\\.\\/default\\//,+3d'
XGETTEXT=carton exec ../../local/bin/xgettext.pl
CARTON=carton exec
locales:
\$(XGETTEXT) -D templates -D ../default/templates -o \$(EN) 2>/dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(FR) 2>/dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(DE) 2>/dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(ES) 2>/dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(OC) 2>/dev/null
\$(XGETTEXT) -D templates -D ../default/templates -o \$(AR) 2>/dev/null
sed \$(SEDOPTS) -i \$(EN)
sed \$(SEDOPTS2) -i \$(EN)
sed \$(SEDOPTS) -i \$(FR)
sed \$(SEDOPTS2) -i \$(FR)
sed \$(SEDOPTS) -i \$(DE)
sed \$(SEDOPTS2) -i \$(DE)
sed \$(SEDOPTS) -i \$(ES)
sed \$(SEDOPTS2) -i \$(ES)
sed \$(SEDOPTS) -i \$(OC)
sed \$(SEDOPTS) -i \$(OC)
sed \$(SEDOPTS2) -i \$(AR)
sed \$(SEDOPTS2) -i \$(AR)
EOF
open $f, '>', File::Spec->catfile($home, 'Makefile') or die "Unable to open $home/Makefile: $!";
print $f $makefile;
close $f;
open $f, '>', File::Spec->catfile($home, '.gitignore') or die "Unable to open $home/.gitignore: $!";
print $f "public/packed/\ntemplates/data.html.ep";
close $f;
} else {
say "$name theme already exists. Aborting.";
exit 1;
}
}
=encoding utf8
=head1 NAME
Lutim::Command::theme - Create new theme skeleton.
=head1 SYNOPSIS
Usage: script/lutim theme THEME_NAME
Your new theme will be available in the themes directory.
=cut
1;

View File

@@ -1,723 +0,0 @@
# vim:set sw=4 ts=4 sts=4 expandtab:
package Lutim::Controller;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Util qw(url_unescape b64_encode);
use Mojo::Asset::Memory;
use Mojo::JSON qw(true false);
use DateTime;
use Digest::file qw(digest_file_hex);
use Text::Unidecode;
use Data::Validate::URI qw(is_http_uri is_https_uri);
use File::MimeInfo::Magic qw(mimetype extensions);
use IO::Scalar;
use Image::ExifTool;
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
use vars qw($im_loaded);
BEGIN {
eval "use Image::Magick";
if ($@) {
warn "You don't have Image::Magick installed so you won't have thumbnails.";
$im_loaded = 0;
} else {
$im_loaded = 1;
}
}
sub home {
my $c = shift;
$c->render(
template => 'index',
max_file_size => $c->req->max_message_size
);
$c->on(finish => sub {
my $c = shift;
$c->app->log->info('[HIT] someone visited site index');
}
);
}
sub about {
shift->render(template => 'about');
}
sub stats {
shift->render(
template => 'stats',
total => LutimModel::Lutim->count('WHERE path IS NOT NULL')
);
}
sub webapp {
my $c = shift;
my $headers = Mojo::Headers->new();
$headers->add('Content-Type' => 'application/x-web-app-manifest+json');
$c->res->content->headers($headers);
$c->render(
template => 'manifest',
format => 'webapp'
);
}
sub get_counter {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
my @images = LutimModel::Lutim->select('WHERE short = ? AND path IS NOT NULL AND mod_token = ?', ($short, $token));
if (scalar(@images)) {
return $c->render(
json => {
success => true,
counter => $images[0]->counter,
enabled => ($images[0]->enabled) ? true : false
}
);
}
$c->render(
json => {
success => false,
msg => $c->l('Unable to get counter')
}
);
}
sub modify {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
my $url = $c->param('url');
my @images = LutimModel::Lutim->select('WHERE short = ? AND path IS NOT NULL', $short);
if (scalar(@images)) {
my $image = $images[0];
my $msg;
if ($image->mod_token() ne $token || $token eq '') {
$msg = $c->l('The delete token is invalid.');
} else {
$c->app->log->info('[MODIFICATION] someone modify '.$image->filename.' with token method (path: '.$image->path.')');
$image->update(
delete_at_day => ($c->param('delete-day') && ($c->param('delete-day') <= $c->max_delay || $c->max_delay == 0)) ? $c->param('delete-day') : $c->max_delay,
delete_at_first_view => ($c->param('first-view')) ? 1 : 0,
);
$msg = $c->l('The image\'s delay has been successfully modified');
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->true,
msg => $msg
}
);
} else {
$msg .= ' (<a href="'.$url.'">'.$url.'</a>)' unless (!defined($url));
$c->flash(
success => $msg
);
return $c->redirect_to('/');
}
}
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => $msg
}
);
} else {
$c->flash(
msg => $msg
);
return $c->redirect_to('/');
}
} else {
$c->app->log->info('[UNSUCCESSFUL] someone tried to modify '.$short.' but it does\'nt exist.');
# Image never existed
my $msg = $c->l('Unable to find the image %1.', $short);
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => $msg
}
);
} else {
$c->flash(
msg => $msg
);
return $c->redirect_to('/');
}
}
}
sub delete {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
my @images = LutimModel::Lutim->select('WHERE short = ? AND path IS NOT NULL', $short);
if (scalar(@images)) {
my $image = $images[0];
my $msg;
if ($image->mod_token() ne $token || $token eq '') {
$msg = $c->l('The delete token is invalid.');
} elsif ($image->enabled() == 0) {
$msg = $c->l('The image %1 has already been deleted.', $image->filename);
} else {
$c->app->log->info('[DELETION] someone made '.$image->filename.' removed with token method (path: '.$image->path.')');
$c->delete_image($image);
return $c->respond_to(
json => {
json => {
success => true,
msg => $c->l('The image %1 has been successfully deleted', $image->filename)
}
},
any => sub {
$c->flash(
success => $c->l('The image %1 has been successfully deleted', $image->filename)
);
return $c->redirect_to('/');
}
);
}
return $c->respond_to(
json => {
json => {
success => false,
msg => $msg
}
},
any => sub {
$c->flash(
msg => $msg
);
return $c->redirect_to('/');
}
);
} else {
$c->app->log->info('[UNSUCCESSFUL] someone tried to delete '.$short.' but it does\'nt exist.');
# Image never existed
return $c->respond_to(
json => {
json => {
success => false,
msg => $c->l('Unable to find the image %1.', $short)
}
},
any => sub {
shift->render_not_found;
}
);
}
}
sub add {
my $c = shift;
my $upload = $c->param('file');
my $file_url = $c->param('lutim-file-url');
my $keep_exif = $c->param('keep-exif');
if(!defined($c->stash('stop_upload'))) {
if (defined($file_url) && $file_url) {
if (is_http_uri($file_url) || is_https_uri($file_url)) {
# Anti-flood protection
my $ip = $c->ip(1);
while (defined($c->app->{wait_for_it}->{$ip}) && (time - $c->app->{wait_for_it}->{$ip}) <= $c->config->{anti_flood_delay} ) {
sleep($c->config->{anti_flood_delay});
}
my $ua = Mojo::UserAgent->new;
my $tx = $ua->get($file_url => {DNT => 1});
if (my $res = $tx->success) {
$file_url = url_unescape $file_url;
$file_url =~ m#^.*/([^/?]*)\??.*$#;
my $filename = $1;
$filename = 'uploaded.image' unless (defined($filename));
$filename .= '.image' if (index($filename, '.') == -1);
$upload = Mojo::Upload->new(
asset => $res->content->asset,
filename => $filename
);
$c->app->{wait_for_it}->{$ip} = time;
} elsif ($tx->res->is_limit_exceeded) {
my $msg = $c->l('The file exceed the size limit (%1)', $tx->res->max_message_size);
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => {
filename => $file_url,
msg => $msg
}
}
);
} else {
$c->flash(msg => $msg);
$c->flash(filename => $upload->filename);
return $c->redirect_to('/');
}
} else {
my $msg = $c->l('An error occured while downloading the image.');
$c->app->log->warn('[DOWNLOAD ERROR]'.$c->dumper($tx->error));
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => {
filename => $file_url,
msg => $msg
}
}
);
} else {
$c->flash(msg => $msg);
$c->flash(filename => $file_url);
return $c->redirect_to('/');
}
}
} else {
my $msg = $c->l('The URL is not valid.');
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => {
filename => $file_url,
msg => $msg
}
}
);
} else {
$c->flash(msg => $msg);
$c->flash(filename => $file_url);
return $c->redirect_to('/');
}
}
}
my $io_scalar = new IO::Scalar \$upload->slurp();
my $mediatype = mimetype($io_scalar);
my ($ext) = ($upload->filename =~ m/.*\.(.*)$/);
my $ip = $c->ip;
my ($msg, $short, $real_short, $token, $thumb, $limit, $created);
# Check file type
if (index($mediatype, 'image/') >= 0) {
# Create directory if needed
mkdir('files', 0700) unless (-d 'files');
if ($c->req->is_limit_exceeded) {
$msg = $c->l('The file exceed the size limit (%1)', $c->req->max_message_size);
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => $msg
}
);
} else {
$c->flash(msg => $msg);
$c->flash(filename => $upload->filename);
return $c->redirect_to('/');
}
}
if(LutimModel->begin) {
my @records = LutimModel::Lutim->select('WHERE path IS NULL LIMIT 1');
if (scalar(@records)) {
# Save file and create record
my $filename = unidecode($upload->filename);
my $ext = ($filename =~ m/([^.]+)$/)[0];
my $path = 'files/'.$records[0]->short.'.'.$ext;
my ($width, $height);
if ($im_loaded && $mediatype ne 'image/svg+xml' && $mediatype !~ m#image/(x-)?xcf# && $mediatype ne 'image/webp') { # ImageMagick don't work in Debian with svg (for now?)
my $im = Image::Magick->new;
$im->BlobToImage($upload->slurp);
# Automatic rotation from EXIF tag
$im->AutoOrient();
# Update the uploaded file with it's auto-rotated clone
my $asset = Mojo::Asset::Memory->new->add_chunk($im->ImageToBlob());
$upload->asset($asset);
# Create the thumbnail
$width = $im->Get('width');
$height = $im->Get('height');
$im->Resize(geometry=>'x85');
$thumb = 'data:'.$mediatype.';base64,';
if ($mediatype eq 'image/gif') {
$thumb .= b64_encode $im->[0]->ImageToBlob();
} else {
$thumb .= b64_encode $im->ImageToBlob();
}
}
unless ((defined($keep_exif) && $keep_exif) || $mediatype eq 'image/svg+xml' || $mediatype !~ m#image/(x-)?xcf# || $mediatype ne 'image/webp') {
# Remove the EXIF tags
my $data = new IO::Scalar \$upload->slurp();
my $et = new Image::ExifTool;
# Use $data in Image::ExifTool object
$et->ExtractInfo($data);
# Remove all metadata
$et->SetNewValue('*', undef);
# Create a temporary IO::Scalar to write into
my $temp;
my $a = new IO::Scalar \$temp;
$et->WriteInfo($data, $a);
# Update the uploaded file with it's no-tags clone
$data = Mojo::Asset::Memory->new->add_chunk($temp);
$upload->asset($data);
}
my $key;
if ($c->param('crypt') || $c->config->{always_encrypt}) {
($upload, $key) = $c->crypt($upload, $filename);
}
$upload->move_to($path);
$records[0]->update(
path => $path,
filename => $filename,
mediatype => $mediatype,
footprint => digest_file_hex($path, 'SHA-512'),
enabled => 1,
delete_at_day => ($c->param('delete-day') && ($c->param('delete-day') <= $c->max_delay || $c->max_delay == 0)) ? $c->param('delete-day') : $c->max_delay,
delete_at_first_view => ($c->param('first-view')) ? 1 : 0,
created_at => time(),
created_by => $ip,
width => $width,
height => $height
);
# Log image creation
$c->app->log->info('[CREATION] '.$ip.' pushed '.$filename.' (path: '.$path.')');
# Give url to user
$short = $records[0]->short;
$real_short = $short;
if (!defined($records[0]->mod_token)) {
$records[0]->update(
mod_token => $c->shortener($c->config->{token_length})
);
}
$token = $records[0]->mod_token;
$short .= '/'.$key if (defined($key));
$limit = $records[0]->delete_at_day;
$created = $records[0]->created_at;
} else {
# Houston, we have a problem
$msg = $c->l('There is no more available URL. Retry or contact the administrator. %1', $c->config->{contact});
}
}
LutimModel->commit;
} else {
$msg = $c->l('The file %1 is not an image.', $upload->filename);
}
if (defined($c->param('format')) && $c->param('format') eq 'json') {
if (defined($short)) {
$msg = {
filename => $upload->filename,
short => $short,
real_short => $real_short,
token => $token,
ext => $ext || extensions($mediatype),
thumb => $thumb,
del_at_view => ($c->param('first-view')) ? true : false,
limit => $limit,
created_at => $created
};
} else {
$msg = {
filename => $upload->filename,
msg => $msg
};
}
return $c->render(
json => {
success => (defined($short)) ? Mojo::JSON->true : Mojo::JSON->false,
msg => $msg
}
);
} else {
if ((defined($msg))) {
$c->flash(msg => $msg);
$c->flash(filename => $upload->filename);
return $c->redirect_to('/');
} else {
$c->stash(short => $short) if (defined($short));
$c->stash(real_short => $real_short);
$c->stash(token => $token);
$c->stash(ext => $ext || extensions($mediatype));
$c->stash(thumb => $thumb);
$c->stash(filename => $upload->filename);
return $c->render(
template => 'index',
max_file_size => $c->req->max_message_size
);
}
}
} else {
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => {
filename => $upload->filename,
msg => $c->stash('stop_upload')
}
}
);
} else {
$c->flash(msg => $c->stash('stop_upload'));
$c->flash(filename => $upload->filename);
return $c->redirect_to('/');
}
}
}
sub short {
my $c = shift;
my $short = $c->param('short');
my $touit = $c->param('t');
my $key = $c->param('key');
my $thumb = $c->param('thumb');
my $dl = (defined($c->param('dl'))) ? 'attachment' : 'inline';
my @images = LutimModel::Lutim->select('WHERE short = ? AND ENABLED = 1 AND path IS NOT NULL', $short);
if (scalar(@images)) {
if($images[0]->delete_at_day && $images[0]->created_at + $images[0]->delete_at_day * 86400 <= time()) {
# Log deletion
$c->app->log->info('[DELETION] someone tried to view '.$images[0]->filename.' but it has been removed by expiration (path: '.$images[0]->path.')');
# Delete image
$c->delete_image($images[0]);
# Warn user
$c->flash(
msg => $c->l('Unable to find the image: it has been deleted.')
);
return $c->redirect_to('/');
}
my $test;
if (defined($touit)) {
$test = 1;
my $short = $images[0]->short;
$short .= '/'.$key if (defined($key));
my ($width, $height) = (340,340);
if ($images[0]->mediatype eq 'image/gif') {
if (defined($images[0]->width) && defined($images[0]->height)) {
($width, $height) = ($images[0]->width, $images[0]->height);
} elsif ($im_loaded) {
my $upload = $c->decrypt($key, $images[0]->path);
my $im = Image::Magick->new;
$im->BlobToImage($upload->slurp);
$width = $im->Get('width');
$height = $im->Get('height');
$images[0]->update(
width => $width,
height => $height
);
}
}
return $c->render(
template => 'twitter',
layout => undef,
short => $short,
filename => $images[0]->filename,
mimetype => ($c->req->url->to_abs()->scheme eq 'https') ? $images[0]->mediatype : '',
width => $width,
height => $height
);
} else {
# Delete image if needed
if ($images[0]->delete_at_first_view && $images[0]->counter >= 1) {
# Log deletion
$c->app->log->info('[DELETION] someone made '.$images[0]->filename.' removed (path: '.$images[0]->path.')');
# Delete image
$c->delete_image($images[0]);
$c->flash(
msg => $c->l('Unable to find the image: it has been deleted.')
);
return $c->redirect_to('/');
} else {
my $expires = ($images[0]->delete_at_day) ? $images[0]->delete_at_day : 360;
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $images[0]->created_at);
$dt->set_time_zone('GMT');
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
$test = $c->render_file($images[0]->filename, $images[0]->path, $images[0]->mediatype, $dl, $expires, $images[0]->delete_at_first_view, $key, $thumb);
}
}
if ($test != 500) {
# Update counter
$c->on(finish => sub {
# Log access
$c->app->log->info('[VIEW] someone viewed '.$images[0]->filename.' (path: '.$images[0]->path.')');
# Update record
my $counter = $images[0]->counter + 1;
$images[0]->update(counter => $counter);
$images[0]->update(last_access_at => time());
# Delete image if needed
if ($images[0]->delete_at_first_view) {
# Log deletion
$c->app->log->info('[DELETION] someone made '.$images[0]->filename.' removed (path: '.$images[0]->path.')');
# Delete image
$c->delete_image($images[0]);
}
});
}
} else {
@images = LutimModel::Lutim->select('WHERE short = ? AND ENABLED = 0 AND path IS NOT NULL', $short);
if (scalar(@images)) {
# Log access try
$c->app->log->info('[NOT FOUND] someone tried to view '.$short.' but it does\'nt exist anymore.');
# Warn user
$c->flash(
msg => $c->l('Unable to find the image: it has been deleted.')
);
return $c->redirect_to('/');
} else {
# Image never existed
$c->render_not_found;
}
}
}
sub zip {
my $c = shift;
my $imgs = $c->every_param('i');
my $zip = Archive::Zip->new();
# We HAVE to add a png file at the beginning, otherwise the $zip
# could use the mimetype of an SVG file if it's the first file asked.
$zip->addFile('public/img/favicon.png', 'hosted_with_lutim.png');
$zip->addDirectory('images/');
for my $img (@{$imgs}) {
my ($short, $key) = split('/', $img);
if (defined $key) {
$key =~ s/\.[^.]*//;
} else {
$short =~ s/\.[^.]*//;
}
my @images = LutimModel::Lutim->select('WHERE short = ? AND ENABLED = 1 AND path IS NOT NULL', $short);
if (scalar(@images)) {
my $filename = $images[0]->filename;
if($images[0]->delete_at_day && $images[0]->created_at + $images[0]->delete_at_day * 86400 <= time()) {
# Log deletion
$c->app->log->info('[DELETION] someone tried to view '.$images[0]->filename.' but it has been removed by expiration (path: '.$images[0]->path.')');
# Delete image
$c->delete_image($images[0]);
# Warn user
$zip->addString($c->l('Unable to find the image: it has been deleted.'), 'images/'.$filename.'.txt');
next;
}
# Delete image if needed
if ($images[0]->delete_at_first_view && $images[0]->counter >= 1) {
# Log deletion
$c->app->log->info('[DELETION] someone made '.$images[0]->filename.' removed (path: '.$images[0]->path.')');
# Delete image
$c->delete_image($images[0]);
$zip->addString($c->l('Unable to find the image: it has been deleted.'), 'images/'.$filename.'.txt');
next;
} else {
my $expires = ($images[0]->delete_at_day) ? $images[0]->delete_at_day : 360;
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $images[0]->created_at);
$dt->set_time_zone('GMT');
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
my $path = $images[0]->path;
unless ( -f $path && -r $path ) {
$c->app->log->error("Cannot read file [$path]. error [$!]");
$zip->addString($c->l('Unable to find the image: it has been deleted.'), 'images/'.$filename.'.txt');
next;
}
if ($key) {
$zip->addString($c->decrypt($key, $path)->slurp, "images/$filename");
} else {
$zip->addFile($path, "images/$filename");
}
# Log access
$c->app->log->info('[VIEW] someone viewed '.$images[0]->filename.' (path: '.$images[0]->path.')');
# Update counter
$images[0]->update(counter => $images[0]->counter + 1);
# Update record
$images[0]->update(last_access_at => time());
}
} else {
@images = LutimModel::Lutim->select('WHERE short = ? AND ENABLED = 0 AND path IS NOT NULL', $short);
if (scalar(@images)) {
# Log access try
$c->app->log->info('[NOT FOUND] someone tried to view '.$short.' but it does\'nt exist anymore.');
# Warn user
$zip->addString($c->l('Unable to find the image: it has been deleted.'), 'images/'.$images[0]->filename.'.txt');
next;
} else {
$zip->addString($c->l('Image not found.'), 'images/'.$short.'.txt');
next;
}
}
}
my ($fh, $zipfile) = Archive::Zip::tempFile();
unless ($zip->writeToFileNamed($zipfile) == AZ_OK) {
$c->flash(
msg => $c->l('Something went wrong when creating the zip file. Try again later or contact the administrator (%1).', $c->config('contact'))
);
return $c->redirect_to('/');
}
$c->res->content->headers->content_type('application/zip;name=images.zip');
$c->res->content->headers->content_disposition('attachment;filename=images.zip');;
my $asset = Mojo::Asset::File->new(path => $zipfile);
$c->res->content->asset($asset);
$c->res->content->headers->content_length($asset->size);
unlink $zipfile;
return $c->rendered(200);
}
1;

View File

@@ -0,0 +1,76 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Controller::Authent;
use Mojo::Base 'Mojolicious::Controller';
sub index {
my $c = shift;
if ($c->is_user_authenticated) {
$c->redirect_to('index');
} else {
$c->render(template => 'login');
}
}
sub login {
my $c = shift;
my $login = $c->param('login');
my $pwd = $c->param('password');
my $ref = $c->req->headers->referrer;
if($c->authenticate($login, $pwd)) {
$c->respond_to(
json => sub {
my $c = shift;
$c->render(
json => {
success => Mojo::JSON->true,
msg => $c->l('You have been successfully logged in.')
}
);
},
any => sub {
$c->redirect_to($ref);
}
);
} else {
my $msg = $c->l('Please, check your credentials: unable to authenticate.');
$c->respond_to(
json => sub {
my $c = shift;
$c->render(
json => {
success => Mojo::JSON->false,
msg => $msg
}
);
},
any => sub {
$c->stash(msg => $msg);
$c->render(template => 'login')
}
);
}
}
sub log_out {
my $c = shift;
if ($c->is_user_authenticated) {
$c->logout;
}
$c->respond_to(
json => sub {
my $c = shift;
$c->render(
json => {
success => Mojo::JSON->true,
msg => $c->l('You have been successfully logged out.')
}
);
},
any => sub {
$c->render(template => 'logout');
}
);
}
1;

View File

@@ -0,0 +1,864 @@
# vim:set sw=4 ts=4 sts=4 expandtab:
package Lutim::Controller::Image;
use Mojo::Asset::Memory;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::File qw(path);
use Mojo::Util qw(url_escape url_unescape b64_encode encode);
use Mojo::JSON qw(true false);
use Lutim::DB::Image;
use DateTime;
use Digest::file qw(digest_file_hex);
use Text::Unidecode;
use Data::Validate::URI qw(is_http_uri is_https_uri);
use File::MimeInfo::Magic qw(mimetype extensions);
use IO::Scalar;
use Image::ExifTool;
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
use Data::Entropy qw(entropy_source);
use vars qw($im_loaded);
BEGIN {
eval "use Image::Magick";
if ($@) {
warn "You don't have Image::Magick installed so you won't have thumbnails.";
$im_loaded = 0;
} else {
$im_loaded = 1;
}
}
sub home {
my $c = shift;
$c->render(
template => 'index',
max_file_size => $c->req->max_message_size
);
$c->on(finish => sub {
my $c = shift;
$c->app->log->info('[HIT] someone visited site index') unless $c->config('quiet_logs');
}
);
}
sub about {
shift->render(template => 'about');
}
sub change_lang {
my $c = shift;
my $l = $c->param('l');
$c->cookie(lutim_lang => $l, { path => $c->config('prefix') });
if ($c->req->headers->referrer) {
return $c->redirect_to($c->req->headers->referrer);
} else {
return $c->redirect_to('/');
}
}
sub stats {
my $c = shift;
my $img = Lutim::DB::Image->new(app => $c);
$c->render(
template => 'stats',
total => $img->count_not_empty
);
}
sub infos {
my $c = shift;
$c->render(
json => {
broadcast_message => $c->config('broadcast_message'),
image_magick => ($im_loaded) ? true : false,
contact => $c->config('contact'),
max_file_size => $c->config('max_file_size'),
default_delay => $c->config('default_delay'),
max_delay => $c->config('max_delay'),
always_encrypt => ($c->config('always_encrypt')) ? true : false,
upload_enabled => ($c->app->stop_upload()) ? false : true,
}
);
}
sub about_img {
my $c = shift;
my $short = $c->param('short');
my $image = Lutim::DB::Image->new(app => $c->app, short => $short);
if ($image->enabled && $image->path) {
return $c->render(
json => {
success => true,
data => {
width => $image->width,
height => $image->height,
}
}
);
} else {
return $c->render(
json => {
success => false,
msg => $c->l('Unable to find the image %1.', $short)
}
);
}
}
sub webapp {
my $c = shift;
my $headers = Mojo::Headers->new();
$headers->add('Content-Type' => 'application/x-web-app-manifest+json');
$c->res->content->headers($headers);
$c->render(
template => 'manifest',
format => 'webapp'
);
}
sub get_counter {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
my $img = Lutim::DB::Image->new(app => $c->app, short => $short);
if (defined($img->mod_token) && $img->mod_token eq $token) {
return $c->render(
json => {
success => true,
counter => $img->counter,
enabled => ($img->enabled) ? true : false
}
);
}
$c->render(
json => {
success => false,
msg => $c->l('Unable to get counter')
}
);
}
sub modify {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
my $url = $c->param('url');
my $image = Lutim::DB::Image->new(app => $c->app, short => $short);
if ($image->path) {
my $msg;
if ($image->mod_token ne $token || $token eq '') {
$msg = $c->l('The delete token is invalid.');
} else {
$c->app->log->info('[MODIFICATION] someone modify '.$image->filename.' with token method (path: '.$image->path.')') unless $c->config('quiet_logs');
$image->delete_at_day(($c->param('delete-day') && ($c->param('delete-day') <= $c->max_delay || $c->max_delay == 0)) ? $c->param('delete-day') : $c->max_delay);
$image->delete_at_first_view(($c->param('first-view')) ? 1 : 0);
$image->write;
$msg = $c->l('The images delay has been successfully modified');
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->true,
msg => $msg
}
);
} else {
$msg .= ' (<a href="'.$url.'">'.$url.'</a>)' unless (!defined($url));
$c->flash(
success => $msg
);
return $c->redirect_to('/');
}
}
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => $msg
}
);
} else {
$c->flash(
msg => $msg
);
return $c->redirect_to('/');
}
} else {
$c->app->log->info('[UNSUCCESSFUL] someone tried to modify '.$short.' but it doesnt exist.') unless $c->config('quiet_logs');
# Image never existed
my $msg = $c->l('Unable to find the image %1.', $short);
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => $msg
}
);
} else {
$c->flash(
msg => $msg
);
return $c->redirect_to('/');
}
}
}
sub delete {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
my $image = Lutim::DB::Image->new(app => $c->app, short => $short);
if ($image->path) {
my $msg;
if ($image->mod_token ne $token || $token eq '') {
$msg = $c->l('The delete token is invalid.');
} elsif ($image->enabled() == 0) {
$msg = $c->l('The image %1 has already been deleted.', $image->filename);
} else {
$c->app->log->info('[DELETION] someone made '.$image->filename.' removed with token method (path: '.$image->path.')') unless $c->config('quiet_logs');
$c->delete_image($image);
return $c->respond_to(
json => {
json => {
success => true,
msg => $c->l('The image %1 has been successfully deleted', $image->filename)
}
},
any => sub {
$c->flash(
success => $c->l('The image %1 has been successfully deleted', $image->filename)
);
return $c->redirect_to('/');
}
);
}
return $c->respond_to(
json => {
json => {
success => false,
msg => $msg
}
},
any => sub {
$c->flash(
msg => $msg
);
return $c->redirect_to('/');
}
);
} else {
$c->app->log->info('[UNSUCCESSFUL] someone tried to delete '.$short.' but it doesnt exist.') unless $c->config('quiet_logs');
# Image never existed
return $c->respond_to(
json => {
json => {
success => false,
msg => $c->l('Unable to find the image %1.', $short)
}
},
any => sub {
$c->helpers->reply->not_found;
}
);
}
}
sub add {
my $c = shift;
my $upload = $c->param('file');
my $file_url = $c->param('lutim-file-url');
my $keep_exif = $c->param('keep-exif');
my $wm = $c->param('watermark');
if ($c->config('disable_api')) {
my $unauthorized_api = (!defined($c->req->headers->referrer) || Mojo::URL->new($c->req->headers->referrer)->host ne Mojo::URL->new('https://'.$c->req->headers->host)->host);
if ($unauthorized_api) {
my $msg = $c->l('Sorry, the API is disabled');
$c->app->log->info('Blocked API call for '.$c->ip(1));
return $c->respond_to(
json => { json => { success => Mojo::JSON->false, msg => $msg } },
any => sub {
shift->render(
template => 'index',
msg => $msg,
);
}
);
}
}
if(!defined($c->stash('stop_upload'))) {
if (defined($file_url) && $file_url) {
if (is_http_uri($file_url) || is_https_uri($file_url)) {
# Anti-flood protection
my $ip = $c->ip(1);
while (defined($c->app->{wait_for_it}->{$ip}) && (time - $c->app->{wait_for_it}->{$ip}) <= $c->config->{anti_flood_delay} ) {
sleep($c->config->{anti_flood_delay});
}
my $ua = Mojo::UserAgent->new;
my $res = $ua->get($file_url => {DNT => 1})->result;
if ($res->is_success) {
$file_url = url_unescape $file_url;
$file_url =~ m#^.*/([^/?]*)\??.*$#;
my $filename = $1;
$filename = 'uploaded.image' unless (defined($filename));
$filename .= '.image' if (index($filename, '.') == -1);
$upload = Mojo::Upload->new(
asset => $res->content->asset,
filename => $filename
);
$c->app->{wait_for_it}->{$ip} = time;
} elsif ($res->is_limit_exceeded) {
my $msg = $c->l('The file exceed the size limit (%1)', $res->max_message_size);
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => {
filename => $file_url,
msg => $msg
}
}
);
} else {
$c->flash(msg => $msg);
$c->flash(filename => $upload->filename);
return $c->redirect_to('/');
}
} else {
my $msg = $c->l('An error occured while downloading the image.');
$c->app->log->warn('[DOWNLOAD ERROR]'.$c->dumper($res->message));
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => {
filename => $file_url,
msg => $msg
}
}
);
} else {
$c->flash(msg => $msg);
$c->flash(filename => $file_url);
return $c->redirect_to('/');
}
}
} else {
my $msg = $c->l('The URL is not valid.');
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => {
filename => $file_url,
msg => $msg
}
}
);
} else {
$c->flash(msg => $msg);
$c->flash(filename => $file_url);
return $c->redirect_to('/');
}
}
}
my $io_scalar = new IO::Scalar \$upload->slurp();
my $mediatype = mimetype($io_scalar);
my ($ext) = ($upload->filename =~ m/.*\.(.*)$/);
my $ip = $c->ip;
my ($msg, $short, $real_short, $token, $thumb, $limit, $created);
# Check file type
if (index($mediatype, 'image/') >= 0) {
if ($c->req->is_limit_exceeded) {
$msg = $c->l('The file exceed the size limit (%1)', $c->req->max_message_size);
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => $msg
}
);
} else {
$c->flash(msg => $msg);
$c->flash(filename => $upload->filename);
return $c->redirect_to('/');
}
}
my $record = Lutim::DB::Image->new(app => $c->app)->select_empty;
if ($record->short) {
# Save file and create record
my $filename = unidecode($upload->filename);
my $ext = ($filename =~ m/([^.]+)$/)[0];
my $path = path($c->config('upload_dir'), $record->short.'.'.$ext)->to_string;
my ($width, $height);
if ($im_loaded && $mediatype ne 'image/svg+xml' # ImageMagick doesn't work with SVG, xcf or avif files
&& $mediatype !~ m#image/(x-)?xcf#
&& $mediatype ne 'image/avif') {
my $im = Image::Magick->new;
$im->BlobToImage($upload->slurp);
# Automatic rotation from EXIF tag
$im->AutoOrient();
# Get dimensions
$width = $im->Get('width');
$height = $im->Get('height');
# Optionally add watermark
if ($c->config('watermark_path') && (
($wm && $wm ne 'none') ||
$c->config('watermark_enforce') ne 'none'
)) {
my $watermarkim = Image::Magick->new;
$watermarkim->ReadImage($c->config('watermark_path'));
$watermarkim->Evaluate(
operator => 'Multiply',
value => 0.25,
channel => 'Alpha'
);
if ($height <= 80) {
$watermarkim->Resize(geometry => 'x10');
} else {
$watermarkim->Resize(geometry => 'x80');
}
# Add one watermark or repeat it all over the image?
my $tilingw = 1 if ($c->config('watermark_enforce') eq 'tiling' || $wm eq 'tiling');
my $singlew = 1 if ($c->config('watermark_enforce') eq 'single' || $wm eq 'single');
if ($tilingw) {
$im->Composite(
image => $watermarkim,
compose => 'Dissolve',
tile => 'True',
gravity => 'Center'
);
} elsif ($singlew) {
$im->Composite(
image => $watermarkim,
compose => 'Dissolve',
tile => 'False',
x => '20',
y => '20',
gravity => $c->config('watermark_placement')
);
}
}
# Update the uploaded file with it's auto-rotated/watermarked clone
my $asset = Mojo::Asset::Memory->new->add_chunk($im->ImageToBlob());
$upload->asset($asset);
# Create the thumbnail
$im->Resize(geometry => 'x85');
$thumb = 'data:'.$mediatype.';base64,';
if ($mediatype eq 'image/gif') {
$thumb .= b64_encode $im->[0]->ImageToBlob();
} else {
$thumb .= b64_encode $im->ImageToBlob();
}
}
unless (defined($keep_exif) && $keep_exif) {
# Exiftool cant process SVG or xcf files
if ($mediatype ne 'image/svg+xml'
&& $mediatype !~ m#image/(x-)?xcf#) {
# Remove the EXIF tags
my $data = new IO::Scalar \$upload->slurp();
my $et = Image::ExifTool->new;
# Remove all metadata
$et->SetNewValue('*');
# Create a temporary IO::Scalar to write into
my $temp;
my $a = new IO::Scalar \$temp;
$et->WriteInfo($data, $a);
# Update the uploaded file with it's no-tags clone
$data = Mojo::Asset::Memory->new->add_chunk($temp);
$upload->asset($data);
}
}
my ($key, $iv);
if ($c->param('crypt') || $c->config('always_encrypt')) {
($upload, $key, $iv) = $c->crypt($upload, $filename);
}
$upload->move_to($path);
$record->path($path)
->filename($filename)
->mediatype($mediatype)
->footprint(digest_file_hex($path, 'SHA-512'))
->enabled(1)
->delete_at_day(($c->param('delete-day') && ($c->param('delete-day') <= $c->max_delay || $c->max_delay == 0)) ? $c->param('delete-day') : $c->max_delay)
->delete_at_first_view(($c->param('first-view'))? 1 : 0)
->created_at(time())
->created_by($ip)
->width($width)
->height($height)
->iv($iv)
->write;
# Log image creation
$c->app->log->info('[CREATION] '.$ip.' pushed '.$filename.' (path: '.$path.')') unless $c->config('quiet_logs');
# Give url to user
$short = $record->short;
$real_short = $short;
if (!defined($record->mod_token)) {
$record->mod_token($c->shortener($c->config->{token_length}))->write;
}
$token = $record->mod_token;
$short .= '/'.$key if (defined($key));
$limit = $record->delete_at_day;
$created = $record->created_at;
} else {
# Houston, we have a problem
$msg = $c->l('There is no more available URL. Retry or contact the administrator. %1', $c->config->{contact});
}
} else {
$msg = $c->l('The file %1 is not an image.', $upload->filename);
}
if (defined($c->param('format')) && $c->param('format') eq 'json') {
if (defined($short)) {
$msg = {
filename => $upload->filename,
short => $short,
real_short => $real_short,
token => $token,
ext => $ext || extensions($mediatype),
thumb => $thumb,
del_at_view => ($c->param('first-view')) ? true : false,
limit => $limit,
created_at => $created
};
} else {
$msg = {
filename => $upload->filename,
msg => $msg
};
}
return $c->render(
json => {
success => (defined($short)) ? Mojo::JSON->true : Mojo::JSON->false,
msg => $msg
}
);
} else {
if ((defined($msg))) {
$c->flash(msg => $msg);
$c->flash(filename => $upload->filename);
return $c->redirect_to('/');
} else {
$c->stash(short => $short) if (defined($short));
$c->stash(real_short => $real_short);
$c->stash(token => $token);
$c->stash(ext => $ext || extensions($mediatype));
$c->stash(thumb => $thumb);
$c->stash(filename => $upload->filename);
return $c->render(
template => 'index',
max_file_size => $c->req->max_message_size
);
}
}
} else {
if (defined($c->param('format')) && $c->param('format') eq 'json') {
return $c->render(
json => {
success => Mojo::JSON->false,
msg => {
filename => $upload->filename,
msg => $c->stash('stop_upload')
}
}
);
} else {
$c->flash(msg => $c->stash('stop_upload'));
$c->flash(filename => $upload->filename);
return $c->redirect_to('/');
}
}
}
sub short {
my $c = shift;
my $short = $c->param('short');
my $touit = $c->param('t');
my $key = $c->param('key');
my $thumb;
$thumb = '' if defined $c->param('thumb');
$thumb = $c->param('width') if defined $c->param('width');
my $dl = (defined($c->param('dl'))) ? 'attachment' : 'inline';
my $image = Lutim::DB::Image->new(app => $c->app, short => $short);
if ($image->enabled && $image->path) {
if($image->delete_at_day && $image->created_at + $image->delete_at_day * 86400 <= time()) {
# Log deletion
$c->app->log->info('[DELETION] someone tried to view '.$image->filename.' but it has been removed by expiration (path: '.$image->path.')') unless $c->config('quiet_logs');
# Delete image
$c->delete_image($image);
# Warn user
$c->flash(
msg => $c->l('Unable to find the image: it has been deleted.')
);
return $c->redirect_to('/');
}
my $test;
if (defined($touit) && $image->mediatype !~ m/svg/) {
$test = 1;
my $short = $image->short;
$short .= '/'.$key if (defined($key));
my ($width, $height) = (340,340);
if ($image->mediatype eq 'image/gif') {
if (defined($image->width) && defined($image->height)) {
($width, $height) = ($image->width, $image->height);
} elsif ($im_loaded && $image->mediatype !~ m/xcf|avif/) {
my $upload = $c->decrypt($key, $image->path, $image->iv);
my $im = Image::Magick->new;
$im->BlobToImage($upload->slurp);
$width = $im->Get('width');
$height = $im->Get('height');
$image->width($width)
->height($height)
->write;
}
}
return $c->render(
template => 'share',
layout => undef,
short => $short,
filename => $image->filename,
mimetype => $image->mediatype,
width => $width,
height => $height
);
} else {
# Delete image if needed
if ($image->delete_at_first_view && $image->counter >= 1) {
# Log deletion
$c->app->log->info('[DELETION] someone made '.$image->filename.' removed (path: '.$image->path.')') unless $c->config('quiet_logs');
# Delete image
$c->delete_image($image);
$c->flash(
msg => $c->l('Unable to find the image: it has been deleted.')
);
return $c->redirect_to('/');
} else {
$test = $c->render_file($im_loaded, $image, $dl, $key, $thumb);
}
}
if ($test != 500) {
# Update counter
$c->on(finish => sub {
# Log access
$c->app->log->info('[VIEW] someone viewed '.$image->filename.' (path: '.$image->path.')') unless $c->config('quiet_logs');
# Update record
unless ($c->config('disable_img_stats')) {
if ($c->config('minion')->{enabled}) {
$c->app->minion->enqueue(accessed => [$image->short, time]);
} else {
$image->accessed(time);
}
}
# Delete image if needed
if ($image->delete_at_first_view) {
# Log deletion
$c->app->log->info('[DELETION] someone made '.$image->filename.' removed (path: '.$image->path.')') unless $c->config('quiet_logs');
# Delete image
$c->delete_image($image);
}
});
} else {
$c->app->log->error('[ERROR] Cant render '.$image->short);
}
} elsif ($image->path && !$image->enabled) {
# Log access try
$c->app->log->info('[NOT FOUND] someone tried to view '.$short.' but it doesnt exist anymore.') unless $c->config('quiet_logs');
# Warn user
$c->flash(
msg => $c->l('Unable to find the image: it has been deleted.')
);
return $c->redirect_to('/');
} else {
# Image never existed
$c->helpers->reply->not_found;
}
}
sub zip {
my $c = shift;
my $imgs = $c->every_param('i');
my $img_nb = scalar(@{$imgs});
my $max_zip = $c->config('max_files_in_zip');
if ($img_nb <= $max_zip) {
my $zip = Archive::Zip->new();
# We HAVE to add a png file at the beginning, otherwise the $zip
# could use the mimetype of an SVG file if it's the first file asked.
$zip->addFile('themes/default/public/img/favicon.png', 'hosted_with_lutim.png');
$zip->addDirectory('images/');
for my $img (@{$imgs}) {
my ($short, $key) = split('/', $img);
if (defined $key) {
$key =~ s/\.[^.]*//;
} else {
$short =~ s/\.[^.]*//;
}
my $image = Lutim::DB::Image->new(app => $c->app, short => $short);
if ($image->enabled && $image->path) {
my $filename = $image->filename;
if($image->delete_at_day && $image->created_at + $image->delete_at_day * 86400 <= time()) {
# Log deletion
$c->app->log->info('[DELETION] someone tried to view '.$image->filename.' but it has been removed by expiration (path: '.$image->path.')') unless $c->config('quiet_logs');
# Delete image
$c->delete_image($image);
# Warn user
$zip->addString(encode('UTF-8', $c->l('Unable to find the image: it has been deleted.')), 'images/'.$filename.'.txt');
next;
}
# Delete image if needed
if ($image->delete_at_first_view && $image->counter >= 1) {
# Log deletion
$c->app->log->info('[DELETION] someone made '.$image->filename.' removed (path: '.$image->path.')') unless $c->config('quiet_logs');
# Delete image
$c->delete_image($image);
$zip->addString(encode('UTF-8', $c->l('Unable to find the image: it has been deleted.')), 'images/'.$filename.'.txt');
next;
} else {
my $expires = ($image->delete_at_day) ? $image->delete_at_day : 360;
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $image->created_at);
$dt->set_time_zone('GMT');
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
my $path = $image->path;
unless ( -f $path && -r $path ) {
$c->app->log->error("Cannot read file [$path]. error [$!]");
$zip->addString(encode('UTF-8', $c->l('Unable to find the image: it has been deleted.')), 'images/'.$filename.'.txt');
next;
}
if ($key) {
$zip->addString($c->decrypt($key, $path, $image->iv), "images/$filename");
} else {
$zip->addFile($path, "images/$filename");
}
# Log access
$c->app->log->info('[VIEW] someone viewed '.$image->filename.' (path: '.$image->path.')') unless $c->config('quiet_logs');
# Update counter and record
unless ($c->config('disable_img_stats')) {
if ($c->config('minion')->{enabled}) {
$c->app->minion->enqueue(accessed => [$image->short, time]);
} else {
$image->accessed(time);
}
}
}
} elsif ($image->path && !$image->enabled) {
# Log access try
$c->app->log->info('[NOT FOUND] someone tried to view '.$short.' but it doesnt exist anymore.') unless $c->config('quiet_logs');
# Warn user
$zip->addString(encode('UTF-8', $c->l('Unable to find the image: it has been deleted.')), 'images/'.$image->filename.'.txt');
next;
} else {
$zip->addString(encode('UTF-8', $c->l('Image not found.')), 'images/'.$short.'.txt');
next;
}
}
my ($fh, $zipfile) = Archive::Zip::tempFile();
unless ($zip->writeToFileNamed($zipfile) == AZ_OK) {
$c->flash(
msg => $c->l('Something went wrong when creating the zip file. Try again later or contact the administrator (%1).', $c->config('contact'))
);
return $c->redirect_to('/');
}
$c->res->content->headers->content_type('application/zip;name=images.zip');
$c->res->content->headers->content_disposition('attachment;filename=images.zip');;
my $asset = Mojo::Asset::File->new(path => $zipfile);
$c->res->content->asset($asset);
$c->res->content->headers->content_length($asset->size);
unlink $zipfile;
return $c->rendered(200);
} else {
my $i = -1;
my @urls = ();
my @esc_imgs = map { my $e = $_; $e = url_escape($e); $e =~ s#%2F#/#g; $e } @{$imgs};
while (++$i < $img_nb) {
my $stop = ($i + $max_zip - 1 < $img_nb) ? $i + $max_zip - 1 : $img_nb - 1;
push @urls, $c->url_for('/zip')->to_abs->to_string.'?i='.join('&i=', @esc_imgs[$i..$stop]);
$i = $stop;
}
$c->render(
template => 'zip',
urls => \@urls
);
}
}
sub random {
my $c = shift;
my $imgs = $c->every_param('i');
my $img_nb = scalar(@{$imgs});
if ($img_nb) {
$c->redirect_to($c->prefix.$imgs->[entropy_source->get_int($img_nb)]);
} else {
$c->render_not_found;
}
}
1;

364
lib/Lutim/DB/Image.pm Normal file
View File

@@ -0,0 +1,364 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::DB::Image;
use Mojo::Base -base;
has 'short';
has 'path';
has 'footprint';
has 'enabled';
has 'mediatype';
has 'filename';
has 'counter' => 0;
has 'delete_at_first_view';
has 'delete_at_day';
has 'created_at';
has 'created_by';
has 'last_access_at';
has 'mod_token';
has 'width';
has 'height';
has 'iv';
has 'app';
=head1 NAME
Lutim::DB::Image - DB abstraction layer for Lutim images
=head1 Contributing
When creating a new database accessor, make sure that it provides the following subroutines.
After that, modify this file and modify the C<new> subroutine to allow to use your accessor.
Have a look at Lutim::DB::Image::SQLite's code: it's simple and may be more understandable that this doc.
=head1 Attributes
=over 1
=item B<short> : random string
=item B<path> : string, path to the image, relative to lutim's installation directory
=item B<footprint> : string, sha512 checksum of the image
=item B<enabled> : boolean, is the image accessible?
=item B<mediatype> : string, mimetype of the image
=item B<filename> : string
=item B<counter> : integer
=item B<delete_at_first_view> : boolean
=item B<delete_at_day> : integer, number of days from image upload to deletion
=item B<created_at> : unix timestamp
=item B<created_by> : unix timestamp
=item B<last_access_at> : unix timestamp
=item B<mod_token> : random string
=item B<width> : integer
=item B<height> : integer
=item B<iv> : initialization vector for the file encryption
=item B<app> : a mojolicious object
=back
=head1 Sub routines
=head2 new
=over 1
=item B<Usage> : C<$c = Lutim::DB::Image-E<gt>new(app =E<gt> $self);>
=item B<Arguments> : any of the attribute above
=item B<Purpose> : construct a new db accessor object. If the C<short> attribute is provided, it have to load the informations from the database.
=item B<Returns> : the db accessor object
=item B<Info> : the app argument is used by Lutim::DB::Image to choose which db accessor will be used, you don't need to use it in new(), but you can use it to access helpers or configuration settings in the other subroutines
=back
=cut
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
if (ref($c) eq 'Lutim::DB::Image') {
my $dbtype = $c->app->config('dbtype');
if ($dbtype eq 'sqlite') {
use Lutim::DB::Image::SQLite;
$c = Lutim::DB::Image::SQLite->new(@_);
} elsif ($dbtype eq 'postgresql') {
use Lutim::DB::Image::Pg;
$c = Lutim::DB::Image::Pg->new(@_);
}
}
return $c;
}
sub to_hash {
my $c = shift;
return {
short => $c->short,
path => $c->path,
footprint => $c->footprint,
enabled => $c->enabled,
mediatype => $c->mediatype,
filename => $c->filename,
counter => $c->counter,
delete_at_first_view => $c->delete_at_first_view,
delete_at_day => $c->delete_at_day,
created_at => $c->created_at,
created_by => $c->created_by,
last_access_at => $c->last_access_at,
mod_token => $c->mod_token,
width => $c->width,
height => $c->height,
iv => $c->iv
};
}
=head2 accessed
=over 1
=item B<Usage> : C<$c-E<gt>accessed($time)>
=item B<Arguments> : an unix timestamp
=item B<Purpose> : increments the counter attribute by one, set the last_access_at attribute to $time and update the database
=item B<Returns> : the db accessor object
=back
=head2 count_delete_at_day_endis
=over 1
=item B<Usage> : C<$c-E<gt>count_delete_at_day_endis($delete_at_day, $enabled[, $time])>
=item B<Arguments> : two mandatory parameters: one integer, the delete_at_day attribute, a boolean (0 or 1), the enabled attribute
an optional parameter: an unix timestamp
=item B<Purpose> : count how many images there are with the given delete_at_day attribute, and enabled or disabled, depending on the given enabled attribute
if the optional parameter is given, count only images according to the given mandatory parameters that were created before the timestamp
=item B<Returns> : integer
=back
=head2 count_created_before
=over 1
=item B<Usage> : C<$c-E<gt>count_created_before($time)>
=item B<Arguments> : an unix timestamp
=item B<Purpose> : count how many images have been created before the given timestamp
=item B<Returns> : integer
=back
=head2 select_created_after
=over 1
=item B<Usage> : C<$c-E<gt>select_created_after($time)>
=item B<Arguments> : an unix timestamp
=item B<Purpose> : select images created after the given timestamp
=item B<Returns> : a Mojo::Collection object containing the images created after the given timestamp
=back
=head2 select_empty
=over 1
=item B<Usage> : C<$c-E<gt>select_empty>
=item B<Arguments> : none
=item B<Purpose> : select a ready-to-use empty record
=item B<Returns> : a db accessor object
=back
=head2 write
=over 1
=item B<Usage> : C<$c-E<gt>write>
=item B<Arguments> : none
=item B<Purpose> : create or update a record in the database, with the values of the object's attributes
=item B<Returns> : the db accessor object
=back
=head2 count_short
=over 1
=item B<Usage> : C<$c-E<gt>count_short($short)>
=item B<Arguments> : a random string, unique image identifier in the database
=item B<Purpose> : checks that an identifier isn't already used
=item B<Returns> : integer, number of records having this identifier (should be 0 or 1)
=back
=head2 count_empty
=over 1
=item B<Usage> : C<$c-E<gt>count_empty>
=item B<Arguments> : none
=item B<Purpose> : counts the number of records whose path is null
=item B<Returns> : integer
=back
=head2 count_not_empty
=over 1
=item B<Usage> : C<$c-E<gt>count_not_empty>
=item B<Arguments> : none
=item B<Purpose> : counts the number of records whose path is not null
=item B<Returns> : integer
=back
=head2 clean_ips_until
=over 1
=item B<Usage> : C<$c-E<gt>clean_ips_until($time)>
=item B<Arguments> : unix timestamp
=item B<Purpose> : remove the image's sender information on images created before the given timestamp
=item B<Returns> : the db accessor object
=back
=head2 get_no_longer_viewed_files
=over 1
=item B<Usage> : C<$c-E<gt>get_no_longer_viewed_files($time)>
=item B<Arguments> : unix timestamp
=item B<Purpose> : get images no longer viewed after the given timestamp
=item B<Returns> : a Mojo::Collection object containing the no longer viewed images as Lutim::DB::Image objects
=back
=head2 get_images_to_clean
=over 1
=item B<Usage> : C<$c-E<gt>get_images_to_clean>
=item B<Arguments> : none
=item B<Purpose> : get images that are expired but not marked as it
=item B<Returns> : a Mojo::Collection object containing the images to clean as Lutim::DB::Image objects
=back
=head2 get_50_oldest
=over 1
=item B<Usage> : C<$c-E<gt>get_50_oldest>
=item B<Arguments> : none
=item B<Purpose> : get the 50 oldest enabled images
=item B<Returns> : a Mojo::Collection object containing the 50 oldest enabled images as Lutim::DB::Image objects
=back
=head2 disable
=over 1
=item B<Usage> : C<$c-E<gt>disable>
=item B<Arguments> : none
=item B<Purpose> : change the attribute C<enabled> to false and update the database record
=item B<Returns> : the db accessor object
=back
=head2 search_created_by
=over 1
=item B<Usage> : C<$c-E<gt>search_created_by($ip)>
=item B<Arguments> : an IP address
=item B<Purpose> : get enabled images that have been uploaded by this IP address (database query: LIKE '$ip%', results may include images uploaded by similar IP addresses)
=item B<Returns> : a Mojo::Collection object containing the matching images as Lutim::DB::Image objects
=back
=head2 search_exact_created_by
=over 1
=item B<Usage> : C<$c-E<gt>search_exact_created_by($ip)>
=item B<Arguments> : an IP address
=item B<Purpose> : get enabled images that have been uploaded by this IP address
=item B<Returns> : a Mojo::Collection object containing the matching images as Lutim::DB::Image objects
=back
=cut
1;

276
lib/Lutim/DB/Image/Pg.pm Normal file
View File

@@ -0,0 +1,276 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::DB::Image::Pg;
use Mojo::Base 'Lutim::DB::Image';
use Mojo::Collection 'c';
has 'record' => 0;
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if ($c->short);
return $c;
}
sub accessed {
my $c = shift;
my $time = shift;
my $h = $c->app->pg->db->query('UPDATE lutim SET counter = counter + 1, last_access_at = ? WHERE short = ? RETURNING counter, last_access_at', $time, $c->short)->hashes->first;
$c->counter($h->{counter});
$c->last_access_at($h->{last_access_at});
return $c;
}
sub count_delete_at_day_endis {
my $c = shift;
my $day = shift;
my $enabled = shift;
my $created = shift;
if (defined $created) {
return $c->app->pg->db->query('SELECT count(short) FROM lutim WHERE path IS NOT NULL AND delete_at_day = ? AND enabled = ? AND created_at < ?', $day, $enabled, $created)->hashes->first->{count};
} else {
return $c->app->pg->db->query('SELECT count(short) FROM lutim WHERE path IS NOT NULL AND delete_at_day = ? AND enabled = ?', $day, $enabled)->hashes->first->{count};
}
}
sub count_created_before {
my $c = shift;
my $time = shift;
return $c->app->pg->db->query('SELECT count(short) FROM lutim WHERE path IS NOT NULL AND created_at < ?', $time)->hashes->first->{count};
}
sub select_created_after {
my $c = shift;
my $time = shift;
my @images;
my $records = $c->app->pg->db->query('SELECT * FROM lutim WHERE path IS NOT NULL AND created_at >= ?', $time)->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub select_empty {
my $c = shift;
my $record = $c->app->pg->db->query('SELECT * FROM lutim WHERE path IS NULL')->hashes->shuffle->first;
$c->app->pg->db->query('UPDATE lutim SET path = ? WHERE short = ?', 'used', $record->{short});
$c = $c->_slurp($record);
return $c;
}
sub write {
my $c = shift;
my $provisioning = shift;
if ($c->record) {
$c->app->pg->db->query('UPDATE lutim SET counter = ?, created_at = ?, created_by = ?, delete_at_day = ?, delete_at_first_view = ?, enabled = ?, filename = ?, footprint = ?, height = ?, last_access_at = ?, mediatype = ?, mod_token = ?, path = ?, short = ?, width = ?, iv = ? WHERE short = ?', $c->counter, $c->created_at, $c->created_by, $c->delete_at_day, $c->delete_at_first_view, $c->enabled, $c->filename, $c->footprint, $c->height, $c->last_access_at, $c->mediatype, $c->mod_token, $c->path, $c->short, $c->width, $c->iv, $c->short);
} else {
my $query = 'INSERT INTO lutim (counter, created_at, created_by, delete_at_day, delete_at_first_view, enabled, filename, footprint, height, last_access_at, mediatype, mod_token, path, short, width, iv) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$query .= ' ON CONFLICT DO NOTHING' if $provisioning;
$c->app->pg->db->query($query, $c->counter, $c->created_at, $c->created_by, $c->delete_at_day, $c->delete_at_first_view, $c->enabled, $c->filename, $c->footprint, $c->height, $c->last_access_at, $c->mediatype, $c->mod_token, $c->path, $c->short, $c->width, $c->iv);
$c->record(1);
}
return $c;
}
sub count_short {
my $c = shift;
my $short = shift;
return $c->app->pg->db->query('SELECT count(short) FROM lutim WHERE short = ?', $short)->hashes->first->{count};
}
sub count_empty {
my $c = shift;
return $c->app->pg->db->query('SELECT count(short) FROM lutim WHERE path IS NULL')->hashes->first->{count};
}
sub count_not_empty {
my $c = shift;
return $c->app->pg->db->query('SELECT count(short) FROM lutim WHERE path IS NOT NULL')->hashes->first->{count};
}
sub clean_ips_until {
my $c = shift;
my $time = shift;
$c->app->pg->db->query('UPDATE lutim SET created_by = NULL WHERE path IS NOT NULL AND created_at < ?', $time);
return $c;
}
sub get_no_longer_viewed_files {
my $c = shift;
my $time = shift;
my @images;
my $records = $c->app->pg->db->query('SELECT * FROM lutim WHERE enabled = 1 AND last_access_at < ?', $time)->{hashes};
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->record(1);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub get_images_to_clean {
my $c = shift;
my @images;
my $records = $c->app->pg->db->query('SELECT * FROM lutim WHERE enabled = 1 AND (delete_at_day * 86400) < (? - created_at) AND delete_at_day != 0', time())->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub get_50_oldest {
my $c = shift;
my @images;
my $records = $c->app->pg->db->query('SELECT * FROM lutim WHERE path IS NOT NULL AND enabled = 1 ORDER BY created_at ASC LIMIT 50')->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub disable {
my $c = shift;
$c->app->pg->db->query('UPDATE lutim SET enabled = 0 WHERE short = ?', $c->short);
$c->enabled(0);
return $c;
}
sub search_created_by {
my $c = shift;
my $ip = shift;
my @images;
my $records = $c->app->pg->db->select('lutim', undef, { enabled => 1, created_by => { -like => $ip.'%' } })->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub search_exact_created_by {
my $c = shift;
my $ip = shift;
my @images;
my $records = $c->app->pg->db->select('lutim', undef, { enabled => 1, created_by => $ip })->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub _slurp {
my $c = shift;
my $r = shift;
my $image;
if (defined $r) {
$image = $r;
} else {
my $images = $c->app->pg->db->query('SELECT * FROM lutim WHERE short = ?', $c->short)->hashes;
if ($images->size) {
$image = $images->first;
}
}
if ($image) {
$c->short($image->{short});
$c->path($image->{path});
$c->footprint($image->{footprint});
$c->enabled($image->{enabled});
$c->mediatype($image->{mediatype});
$c->filename($image->{filename});
$c->counter($image->{counter});
$c->delete_at_first_view($image->{delete_at_first_view});
$c->delete_at_day($image->{delete_at_day});
$c->created_at($image->{created_at});
$c->created_by($image->{created_by});
$c->last_access_at($image->{last_access_at});
$c->mod_token($image->{mod_token});
$c->width($image->{width});
$c->height($image->{height});
$c->iv($image->{iv});
$c->record(1);
}
return $c;
}
1;

View File

@@ -0,0 +1,277 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::DB::Image::SQLite;
use Mojo::Base 'Lutim::DB::Image';
use Mojo::Collection 'c';
has 'record' => 0;
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if ($c->short);
return $c;
}
sub accessed {
my $c = shift;
my $time = shift;
$c->app->sqlite->db->query('UPDATE lutim SET counter = counter + 1, last_access_at = ? WHERE short = ?', $time, $c->short);
my $h = $c->app->sqlite->db->query('SELECT counter, last_access_at FROM lutim WHERE short = ?', $c->short)->hashes->first;
$c->counter($h->{counter});
$c->last_access_at($h->{last_access_at});
return $c;
}
sub count_delete_at_day_endis {
my $c = shift;
my $day = shift;
my $enabled = shift;
my $created = shift;
if (defined $created) {
return $c->app->sqlite->db->query('SELECT count(short) AS count FROM lutim WHERE path IS NOT NULL AND delete_at_day = ? AND enabled = ? AND created_at < ?', $day, $enabled, $created)->hashes->first->{count};
} else {
return $c->app->sqlite->db->query('SELECT count(short) AS count FROM lutim WHERE path IS NOT NULL AND delete_at_day = ? AND enabled = ?', $day, $enabled)->hashes->first->{count};
}
}
sub count_created_before {
my $c = shift;
my $time = shift;
return $c->app->sqlite->db->query('SELECT count(short) AS count FROM lutim WHERE path IS NOT NULL AND created_at < ?', $time)->hashes->first->{count};
}
sub select_created_after {
my $c = shift;
my $time = shift;
my @images;
my $records = $c->app->sqlite->db->query('SELECT * FROM lutim WHERE path IS NOT NULL AND created_at >= ?', $time)->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub select_empty {
my $c = shift;
my $record = $c->app->sqlite->db->query('SELECT * FROM lutim WHERE path IS NULL')->hashes->shuffle->first;
$c->app->sqlite->db->query('UPDATE lutim SET path = ? WHERE short = ?', 'used', $record->{short});
$c = $c->_slurp($record);
return $c;
}
sub write {
my $c = shift;
my $provisioning = shift;
if ($c->record) {
$c->app->sqlite->db->query('UPDATE lutim SET counter = ?, created_at = ?, created_by = ?, delete_at_day = ?, delete_at_first_view = ?, enabled = ?, filename = ?, footprint = ?, height = ?, last_access_at = ?, mediatype = ?, mod_token = ?, path = ?, short = ?, width = ?, iv = ? WHERE short = ?', $c->counter, $c->created_at, $c->created_by, $c->delete_at_day, $c->delete_at_first_view, $c->enabled, $c->filename, $c->footprint, $c->height, $c->last_access_at, $c->mediatype, $c->mod_token, $c->path, $c->short, $c->width, $c->iv, $c->short);
} else {
my $query = 'INSERT INTO lutim (counter, created_at, created_by, delete_at_day, delete_at_first_view, enabled, filename, footprint, height, last_access_at, mediatype, mod_token, path, short, width, iv) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$query .= ' ON CONFLICT DO NOTHING' if $provisioning;
$c->app->sqlite->db->query($query, $c->counter, $c->created_at, $c->created_by, $c->delete_at_day, $c->delete_at_first_view, $c->enabled, $c->filename, $c->footprint, $c->height, $c->last_access_at, $c->mediatype, $c->mod_token, $c->path, $c->short, $c->width, $c->iv);
$c->record(1);
}
return $c;
}
sub count_short {
my $c = shift;
my $short = shift;
return $c->app->sqlite->db->query('SELECT count(short) AS count FROM lutim WHERE short = ?', $short)->hashes->first->{count};
}
sub count_empty {
my $c = shift;
return $c->app->sqlite->db->query('SELECT count(short) AS count FROM lutim WHERE path IS NULL')->hashes->first->{count};
}
sub count_not_empty {
my $c = shift;
return $c->app->sqlite->db->query('SELECT count(short) AS count FROM lutim WHERE path IS NOT NULL')->hashes->first->{count};
}
sub clean_ips_until {
my $c = shift;
my $time = shift;
$c->app->sqlite->db->query('UPDATE lutim SET created_by = NULL WHERE path IS NOT NULL AND created_at < ?', $time);
return $c;
}
sub get_no_longer_viewed_files {
my $c = shift;
my $time = shift;
my @images;
my $records = $c->app->sqlite->db->query('SELECT * FROM lutim WHERE enabled = 1 AND last_access_at < ?', $time)->{hashes};
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->record(1);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub get_images_to_clean {
my $c = shift;
my @images;
my $records = $c->app->sqlite->db->query('SELECT * FROM lutim WHERE enabled = 1 AND (delete_at_day * 86400) < (? - created_at) AND delete_at_day != 0', time())->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub get_50_oldest {
my $c = shift;
my @images;
my $records = $c->app->sqlite->db->query('SELECT * FROM lutim WHERE path IS NOT NULL AND enabled = 1 ORDER BY created_at ASC LIMIT 50')->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub disable {
my $c = shift;
$c->app->sqlite->db->query('UPDATE lutim SET enabled = 0 WHERE short = ?', $c->short);
$c->enabled(0);
return $c;
}
sub search_created_by {
my $c = shift;
my $ip = shift;
my @images;
my $records = $c->app->sqlite->db->select('lutim', undef, { enabled => 1, created_by => { -like => $ip.'%' } })->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub search_exact_created_by {
my $c = shift;
my $ip = shift;
my @images;
my $records = $c->app->sqlite->db->select('lutim', undef, { enabled => 1, created_by => $ip })->hashes;
$records->each(
sub {
my ($e, $num) = @_;
my $i = Lutim::DB::Image->new(app => $c->app);
$i->_slurp($e);
push @images, $i;
}
);
return c(@images);
}
sub _slurp {
my $c = shift;
my $r = shift;
my $image;
if (defined $r) {
$image = $r;
} else {
my $images = $c->app->sqlite->db->query('SELECT * FROM lutim WHERE short = ?', $c->short)->hashes;
if ($images->size) {
$image = $images->first;
}
}
if ($image) {
$c->short($image->{short});
$c->path($image->{path});
$c->footprint($image->{footprint});
$c->enabled($image->{enabled});
$c->mediatype($image->{mediatype});
$c->filename($image->{filename});
$c->counter($image->{counter});
$c->delete_at_first_view($image->{delete_at_first_view});
$c->delete_at_day($image->{delete_at_day});
$c->created_at($image->{created_at});
$c->created_by($image->{created_by});
$c->last_access_at($image->{last_access_at});
$c->mod_token($image->{mod_token});
$c->width($image->{width});
$c->height($image->{height});
$c->iv($image->{iv});
$c->record(1);
}
return $c;
}
1;

View File

@@ -0,0 +1,49 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::DefaultConfig;
require Exporter;
@ISA = qw(Exporter);
@EXPORT_OK = qw($default_config);
our $default_config = {
provisioning => 100,
provis_step => 5,
length => 8,
always_encrypt => 0,
anti_flood_delay => 5,
max_file_size => 10*1024*1024,
https => 0,
proposed_delays => '0,1,7,30,365',
default_delay => 0,
max_delay => 0,
token_length => 24,
crypto_key_length => 8,
thumbnail_size => 100,
watermark_path => '',
watermark_placement => 'SouthEast',
watermark_default => 'none',
watermark_enforce => 'none',
theme => 'default',
disable_api => 0,
upload_dir => 'files',
dbtype => 'sqlite',
db_path => 'lutim.db',
max_files_in_zip => 15,
prefix => '/',
minion => {
enabled => 0,
dbtype => 'sqlite',
db_path => 'minion.db'
},
session_duration => 3600,
cache_max_size => 0,
memcached_servers => [],
quiet_logs => 0,
disable_img_stats => 0,
x_frame_options => 'DENY',
x_content_type_options => 'nosniff',
x_xss_protection => '1; mode=block',
stats_day_num => 365,
keep_ip_during => 365,
policy_when_full => 'warn',
};
1;

View File

@@ -1,479 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Thor77 <thor77@thor77.org>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Lutim\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: 2015-09-18 13:34+0000\n"
"Last-Translator: Thor77 <thor77@thor77.org>\n"
"Language-Team: German (http://www.transifex.com/fiat-tux/lutim/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. ($delay)
#. (config('max_delay')
#: templates/partial/lutim.js.ep:238
#: templates/partial/lutim.js.ep:247
#: templates/partial/lutim.js.ep:248
msgid "%1 days"
msgstr "%1 Tage"
#. ($total)
#: templates/stats.html.ep:2
msgid "%1 sent images on this instance from beginning."
msgstr "%1 Bilder wurden bisher über diese Instanz versendet."
#: templates/index.html.ep:190
msgid "-or-"
msgstr "-oder-"
#: templates/index.html.ep:5
msgid "1 year"
msgstr "1 Jahr"
#: templates/index.html.ep:4
#: templates/partial/lutim.js.ep:247
msgid "24 hours"
msgstr "24 Stunden"
#: templates/myfiles.html.ep:79
msgid ": Error while trying to get the counter."
msgstr ":Fehler beim Abrufen des Zählers."
#: lib/Lutim/Controller.pm:270
msgid "An error occured while downloading the image."
msgstr "Beim Herunterladen des Bildes ist ein Fehler aufgetreten."
#: templates/about.html.ep:40
#: templates/myfiles.html.ep:27
#: templates/stats.html.ep:13
msgid "Back to homepage"
msgstr "Zurück zur Hauptseite"
#: templates/index.html.ep:193
#: templates/index.html.ep:194
msgid "Click to open the file browser"
msgstr "Klicken um den Dateibrowser zu öffnen"
#: templates/about.html.ep:30
msgid "Contributors"
msgstr "Mitwirkende"
#: templates/partial/lutim.js.ep:313
#: templates/partial/lutim.js.ep:361
#: templates/partial/lutim.js.ep:435
msgid "Copy all view links to clipboard"
msgstr "Alle Links zum Anschauen in die Zwischenablage kopieren"
#: templates/index.html.ep:18
#: templates/index.html.ep:36
#: templates/index.html.ep:69
#: templates/index.html.ep:77
#: templates/index.html.ep:85
#: templates/index.html.ep:93
#: templates/partial/lutim.js.ep:180
#: templates/partial/lutim.js.ep:192
#: templates/partial/lutim.js.ep:206
#: templates/partial/lutim.js.ep:220
msgid "Copy to clipboard"
msgstr "In die Zwischenablage kopieren"
#: templates/myfiles.html.ep:15
msgid "Counter"
msgstr "Zähler"
#: templates/index.html.ep:115
#: templates/index.html.ep:147
#: templates/index.html.ep:178
#: templates/myfiles.html.ep:16
#: templates/partial/lutim.js.ep:259
msgid "Delete at first view?"
msgstr "Nach erstem Aufruf löschen?"
#: templates/index.html.ep:98
#: templates/myfiles.html.ep:19
#: templates/partial/common.js.ep:36
msgid "Deletion link"
msgstr "Link zum Löschen"
#: templates/gallery.html.ep:6
#: templates/gallery.html.ep:63
msgid "Download all images"
msgstr "Laden Sie alle Bilder"
#: templates/index.html.ep:81
#: templates/index.html.ep:83
#: templates/partial/lutim.js.ep:198
#: templates/partial/lutim.js.ep:202
msgid "Download link"
msgstr "Link zum Herunterladen"
#: templates/index.html.ep:28
#: templates/index.html.ep:31
msgid "Download zip link"
msgstr "Link zum Archivbilder"
#: templates/index.html.ep:189
msgid "Drag & drop images here"
msgstr "Bilder hierher ziehen"
#: templates/about.html.ep:7
msgid "Drag and drop an image in the appropriate area or use the traditional way to send files and Lutim will provide you four URLs. One to view the image, an other to directly download it, one you can use on social networks and a last to delete the image when you want."
msgstr "Ziehe Bilder in den dafür vorgesehenen Bereich und Lutim wird vier URLs generieren. Eine zum Anschauen, eine zum direkten Herunterladen, eine zum Nutzen in sozialen Netzwerken und eine letzte um das Bild zu löschen."
#: templates/index.html.ep:150
#: templates/index.html.ep:181
msgid "Encrypt the image (Lutim does not keep the key)."
msgstr "Verschlüssle das Bild (Lutim behält den Key nicht)"
#: templates/partial/lutim.js.ep:74
msgid "Error while trying to modify the image."
msgstr "Beim bearbeiten des Bildes ist ein Fehler aufgetreten."
#: templates/stats.html.ep:9
msgid "Evolution of total files"
msgstr "Entwicklung der Anzahl an Dateien"
#: templates/myfiles.html.ep:18
msgid "Expires at"
msgstr "Läuft ab am"
#: templates/myfiles.html.ep:13
msgid "File name"
msgstr "Dateiname"
#: templates/about.html.ep:24
msgid "For more details, see the <a href=\"https://framagit.org/luc/lutim\">homepage of the project</a>."
msgstr "Besuche für mehr Details die <a href=\"https://framagit.org/luc/lutim\">Homepage des Projekts</a>."
#: templates/layouts/default.html.ep:50
msgid "Fork me!"
msgstr "Fork me!"
#: templates/index.html.ep:10
#: templates/index.html.ep:13
msgid "Gallery link"
msgstr "Link zur Galerie"
#: templates/partial/lutim.js.ep:127
#: templates/partial/lutim.js.ep:145
msgid "Hit Enter, then Ctrl+C to copy the short link"
msgstr "Drücke Enter und dann STRG+C um den Kurz-Link zu kopieren."
#: templates/layouts/default.html.ep:45
msgid "Homepage"
msgstr "Webseite"
#: templates/about.html.ep:20
msgid "How do you pronounce Lutim?"
msgstr "Wie spricht man Lutim aus?"
#: templates/about.html.ep:6
msgid "How does it work?"
msgstr "Wie funktionert das?"
#: templates/about.html.ep:18
msgid "How to report an image?"
msgstr "Wie kann ich ein Bild melden?"
#: templates/about.html.ep:14
msgid "If the files are deleted if you ask it while posting it, their SHA512 footprint are retained."
msgstr "Wenn du versuchst, ein Bild während dem Hochladen zu löschen, wird die SHA512-Summe des Bildes behalten."
#: templates/index.html.ep:163
#: templates/index.html.ep:203
msgid "Image URL"
msgstr "Bild-URL"
#: lib/Lutim/Controller.pm:711
msgid "Image not found."
msgstr "Bild nicht gefunden"
#: templates/layouts/default.html.ep:49
msgid "Informations"
msgstr "Informationen"
#: templates/layouts/default.html.ep:55
msgid "Install webapp"
msgstr "Installiere die Webapp"
#: templates/about.html.ep:11
msgid "Is it really anonymous?"
msgstr "Ist es wirklich anonym?"
#: templates/about.html.ep:9
msgid "Is it really free (as in free beer)?"
msgstr "Is es wirklich kostenlos?"
#: templates/about.html.ep:21
msgid "Juste like you pronounce the French word <a href=\"https://fr.wikipedia.org/wiki/Lutin\">lutin</a> (/ly.tɛ̃/)."
msgstr "Genauso wie das französische Wort <a href=\"https://fr.wikipedia.org/wiki/Lutin\">lutin</a> (/ly.tɛ̃/)."
#: templates/index.html.ep:153
#: templates/index.html.ep:184
msgid "Keep EXIF tags"
msgstr "Behalte EXIF-Daten"
#: templates/index.html.ep:118
#: templates/index.html.ep:166
#: templates/index.html.ep:206
#: templates/partial/lutim.js.ep:263
msgid "Let's go!"
msgstr "Los gehts!"
#: templates/layouts/default.html.ep:48
msgid "License:"
msgstr "Lizenz:"
#: templates/index.html.ep:89
#: templates/index.html.ep:91
#: templates/partial/lutim.js.ep:212
#: templates/partial/lutim.js.ep:216
msgid "Link for share on social networks"
msgstr "Links zum teilen auf sozialen Netzwerken"
#: templates/about.html.ep:4
msgid "Lutim is a free (as in free beer) and anonymous image hosting service. It's also the name of the free (as in free speech) software which provides this service."
msgstr ""
"Lutim ist ein freier und anonymer Bilder-Upload-Service.\n"
"Es ist auch der Name der freien Software, die diesen Service bietet."
#: templates/about.html.ep:25
msgid "Main developers"
msgstr "Haupt-Entwickler"
#: templates/index.html.ep:73
#: templates/index.html.ep:75
#: templates/partial/lutim.js.ep:186
#: templates/partial/lutim.js.ep:189
msgid "Markdown syntax"
msgstr "Markdown Syntax"
#: templates/layouts/default.html.ep:54
#: templates/myfiles.html.ep:2
msgid "My images"
msgstr "Meine Bilder"
#: templates/myfiles.html.ep:39
msgid "No limit"
msgstr "Keine Begrenzung"
#: templates/index.html.ep:165
#: templates/index.html.ep:198
msgid "Only images are allowed"
msgstr "Es sind nur Bilder erlaubt"
#: templates/myfiles.html.ep:6
msgid "Only the images sent with this browser will be listed here. The informations are stored in localStorage: if you delete your localStorage data, you'll loose this informations."
msgstr "Nur die Bilder, die über diesen Browser versendet wurden, werden hier angezeigt. Die Informationen werden im localStorage gespeichert, wenn du diesen leerst, sind die Informationen verloren."
#: templates/about.html.ep:16
msgid "Only the uploader! (well, only if he's the only owner of the images' rights before the upload)"
msgstr "Nur der Hochladende (natürlich nur, wenn er vorher auch Rechteinhaber des Bildes war)"
#. (config('contact')
#: templates/about.html.ep:19
msgid "Please contact the administrator: %1"
msgstr "Kontaktiere den Administrator: %1"
#: templates/gallery.html.ep:51
msgid "Please wait"
msgstr "Warten Sie mal"
#: templates/index.html.ep:158
msgid "Send an image"
msgstr "Sende ein Bild"
#: templates/partial/lutim.js.ep:53
msgid "Share it!"
msgstr "Teile es!"
#: templates/layouts/default.html.ep:51
msgid "Share on Twitter"
msgstr "Teile es auf Twitter"
#: templates/index.html.ep:133
#: templates/partial/lutim.js.ep:274
msgid "Something bad happened"
msgstr "Es ist ein Fehler aufgetreten"
#. ($c->config('contact')
#: lib/Lutim/Controller.pm:719
msgid "Something went wrong when creating the zip file. Try again later or contact the administrator (%1)."
msgstr "Es ist ein Fehler aufgetreten. Versuche es erneut oder kontaktiere den Administrator (%1)."
#: templates/about.html.ep:13
msgid "The IP address of the image's sender is retained for a delay which depends of the administrator's choice (for the official instance, which is located in France, it's one year)."
msgstr "Die IP-Adresse des Nutzers wird für eine bestimmte Zeit gespeichert. Diese kann der Administrator frei wählen (für die offizielle Instanz, die in Frankreich gehostet ist, liegt diese Zeit bei einem Jahr)"
#: templates/about.html.ep:23
msgid "The Lutim software is a <a href=\"http://en.wikipedia.org/wiki/Free_software\">free software</a>, which allows you to download and install it on you own server. Have a look at the <a href=\"https://www.gnu.org/licenses/agpl-3.0.html\">AGPL</a> to see what you can do."
msgstr "Lutim ist <a href=\"https://de.wikipedia.org/wiki/Freie_Software\">freie Software</a>, was dir erlaubt sie herunterzuladen und sie auf deinem eigenem Server zu installieren. Schau dir die <a href=\"https://www.gnu.org/licenses/agpl-3.0.html\">AGPL</a> an um deine Recht zu sehen."
#: lib/Lutim/Controller.pm:289
msgid "The URL is not valid."
msgstr "Die URL ist nicht gültig."
#: lib/Lutim/Controller.pm:101
#: lib/Lutim/Controller.pm:170
msgid "The delete token is invalid."
msgstr "Das Token zum Löschen ist ungültig."
#. ($upload->filename)
#: lib/Lutim/Controller.pm:433
msgid "The file %1 is not an image."
msgstr "Die Datei %1 ist kein Bild."
#. ($max_file_size)
#. ($tx->res->max_message_size)
#. ($c->req->max_message_size)
#: lib/Lutim/Controller.pm:253
#: lib/Lutim/Controller.pm:322
#: templates/partial/lutim.js.ep:337
msgid "The file exceed the size limit (%1)"
msgstr "Die Datei überschreitet die Größenbeschränkung (%1)"
#: templates/stats.html.ep:11
msgid "The graph's datas are not updated in real-time."
msgstr "Die Daten des Graphs werden nicht in Echtzeit aktualisiert."
#. ($image->filename)
#: lib/Lutim/Controller.pm:172
msgid "The image %1 has already been deleted."
msgstr "Das Bild %1 wurde schon gelöscht."
#. ($image->filename)
#: lib/Lutim/Controller.pm:181
#: lib/Lutim/Controller.pm:186
msgid "The image %1 has been successfully deleted"
msgstr "Das Bild %1 wurde erfolgreich gelöscht."
#: lib/Lutim/Controller.pm:109
msgid "The image's delay has been successfully modified"
msgstr "Die Zeit bis zum Löschen des Bildes wurde erfolgreich geändert."
#: templates/index.html.ep:45
msgid "The images are encrypted on the server (Lutim does not keep the key)."
msgstr "Die Bilder werden auf dem Server verschlüsselt (Lutim behält den Key nicht)"
#: templates/about.html.ep:5
msgid "The images you post on Lutim can be stored indefinitely or be deleted at first view or after a delay selected from those proposed."
msgstr "Die Bilder, die du auf Lutim hochlädst, können entweder nie, nach dem ersten Aufruf oder nach einer bestimmten Zeit gelöscht werden."
#. ($c->config->{contact})
#: lib/Lutim/Controller.pm:428
msgid "There is no more available URL. Retry or contact the administrator. %1"
msgstr "Es sind keine URLs mehr verfügbar. Versuche es erneut oder kontaktiere den Administrator. %1"
#: templates/index.html.ep:60
#: templates/partial/lutim.js.ep:46
msgid "Tweet it!"
msgstr "Twittere es!"
#. ($short)
#: lib/Lutim/Controller.pm:143
#: lib/Lutim/Controller.pm:215
msgid "Unable to find the image %1."
msgstr "Konnte das Bild %1 nicht finden."
#: lib/Lutim.pm:66
#: lib/Lutim/Controller.pm:518
#: lib/Lutim/Controller.pm:564
#: lib/Lutim/Controller.pm:608
#: lib/Lutim/Controller.pm:648
#: lib/Lutim/Controller.pm:660
#: lib/Lutim/Controller.pm:671
#: lib/Lutim/Controller.pm:708
msgid "Unable to find the image: it has been deleted."
msgstr "Dieses Bild wurde gelöscht."
#: lib/Lutim/Controller.pm:85
msgid "Unable to get counter"
msgstr "Konnte den Zähler nicht abrufen"
#: templates/about.html.ep:17
msgid "Unlike many image sharing services, you don't give us rights on uploaded images."
msgstr "Im Gegensatz zu anderen Bild-Hosting-Diensten, überträgst du uns nicht die Rechte an hochgeladenen Bildern."
#: templates/index.html.ep:162
#: templates/index.html.ep:201
msgid "Upload an image with its URL"
msgstr "Lade ein Bild über seine URL hoch"
#: templates/myfiles.html.ep:17
msgid "Uploaded at"
msgstr "Hochgeladen am"
#: templates/stats.html.ep:6
msgid "Uploaded files by days"
msgstr "Hochgeladene Bilder pro Tag"
#. ($config->{contact})
#: lib/Lutim.pm:170
msgid "Uploading is currently disabled, please try later or contact the administrator (%1)."
msgstr "Hochladen ist momentan deaktiviert. Versuche es später erneut oder kontaktiere den Administrator (%1)."
#: templates/index.html.ep:65
#: templates/index.html.ep:67
#: templates/myfiles.html.ep:14
#: templates/partial/lutim.js.ep:172
#: templates/partial/lutim.js.ep:176
msgid "View link"
msgstr "Link ansehen"
#: templates/about.html.ep:22
msgid "What about the software which provides the service?"
msgstr "Welche Software stellt diesen Dienst bereit?"
#: templates/about.html.ep:3
msgid "What is Lutim?"
msgstr "Was ist Lutim?"
#: templates/about.html.ep:15
msgid "Who owns rights on images uploaded on Lutim?"
msgstr "Wer hat die Rechte an auf Lutim hochgeladenen Bildern?"
#: templates/about.html.ep:12
msgid "Yes, it is! On the other side, for legal reasons, your IP address will be stored when you send an image. Don't panic, it is normally the case of all sites on which you send files!"
msgstr "Ja, ist es! Auf der anderen Seite wird deine IP-Adresse, wegen rechtlichen Gründen, beim hochladen gespeichert. Keine Panik, das ist normalerweise der Fall für alle Seiten, an die du Dateien sendest!"
#. (url_for('/')
#: templates/about.html.ep:10
msgid "Yes, it is! On the other side, if you want to support the developer, you can do it via <a href=\"https://flattr.com/submit/auto?user_id=_SKy_&amp;url=%1&amp;title=Lutim&amp;category=software\">Flattr</a> or with <a href=\"bitcoin:1JCEtmx9pyzWfitMQj2pKAk8GNgyix7RmA?label=lutim\">BitCoin</a>."
msgstr "Ja, ist es! Auf der anderen Seite kannst du den Entwickler via <a href=\"https://flattr.com/submit/auto?user_id=_SKy_&amp;url=%1&amp;title=Lutim&amp;category=software\">Flattr</a> oder <a href=\"bitcoin:1JCEtmx9pyzWfitMQj2pKAk8GNgyix7RmA?label=lutim\">Bitcoin</a> unterstützen."
#: templates/about.html.ep:8
msgid "You can, optionally, request that the image(s) posted on Lutim to be deleted at first view (or download) or after the delay selected from those proposed."
msgstr "Du kannst Bilder, die du auf Lutim hochlädst, entweder nach dem ernsten Ansehen (oder Herunterladen) oder nach einem der vorgeschlagenen Zeiten löschen lassen."
#: templates/about.html.ep:27
msgid "and on"
msgstr "und auf"
#: templates/about.html.ep:27
msgid "core developer"
msgstr "Haupt-Entwickler"
#: templates/index.html.ep:3
msgid "no time limit"
msgstr "keine Zeit-Begrenzung"
#: templates/about.html.ep:27
msgid "on"
msgstr "auf"
#: templates/about.html.ep:36
msgid "spanish translation"
msgstr "spanische Übersetzung"
#: templates/about.html.ep:28
msgid "webapp developer"
msgstr "Webapp-Entwickler"

View File

@@ -1,475 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: Lutim\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: 2015-09-10 23:28+0000\n"
"Last-Translator: Luc Didry <luc@didry.org>\n"
"Language-Team: English (http://www.transifex.com/fiat-tux/lutim/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. ($delay)
#. (config('max_delay')
#: templates/partial/lutim.js.ep:238
#: templates/partial/lutim.js.ep:247
#: templates/partial/lutim.js.ep:248
msgid "%1 days"
msgstr ""
#. ($total)
#: templates/stats.html.ep:2
msgid "%1 sent images on this instance from beginning."
msgstr "%1 sent images on this instance from beginning."
#: templates/index.html.ep:190
msgid "-or-"
msgstr "-or-"
#: templates/index.html.ep:5
msgid "1 year"
msgstr "1 year"
#: templates/index.html.ep:4
#: templates/partial/lutim.js.ep:247
msgid "24 hours"
msgstr "24 hours"
#: templates/myfiles.html.ep:79
msgid ": Error while trying to get the counter."
msgstr ""
#: lib/Lutim/Controller.pm:270
msgid "An error occured while downloading the image."
msgstr "An error occured while downloading the image."
#: templates/about.html.ep:40
#: templates/myfiles.html.ep:27
#: templates/stats.html.ep:13
msgid "Back to homepage"
msgstr "Back to homepage"
#: templates/index.html.ep:193
#: templates/index.html.ep:194
msgid "Click to open the file browser"
msgstr "Click to open the file browser"
#: templates/about.html.ep:30
msgid "Contributors"
msgstr "Contributors"
#: templates/partial/lutim.js.ep:313
#: templates/partial/lutim.js.ep:361
#: templates/partial/lutim.js.ep:435
msgid "Copy all view links to clipboard"
msgstr ""
#: templates/index.html.ep:18
#: templates/index.html.ep:36
#: templates/index.html.ep:69
#: templates/index.html.ep:77
#: templates/index.html.ep:85
#: templates/index.html.ep:93
#: templates/partial/lutim.js.ep:180
#: templates/partial/lutim.js.ep:192
#: templates/partial/lutim.js.ep:206
#: templates/partial/lutim.js.ep:220
msgid "Copy to clipboard"
msgstr "Copy to clipboard"
#: templates/myfiles.html.ep:15
msgid "Counter"
msgstr ""
#: templates/index.html.ep:115
#: templates/index.html.ep:147
#: templates/index.html.ep:178
#: templates/myfiles.html.ep:16
#: templates/partial/lutim.js.ep:259
msgid "Delete at first view?"
msgstr "Delete at first view?"
#: templates/index.html.ep:98
#: templates/myfiles.html.ep:19
#: templates/partial/common.js.ep:36
msgid "Deletion link"
msgstr "Deletion link"
#: templates/gallery.html.ep:6
#: templates/gallery.html.ep:63
msgid "Download all images"
msgstr ""
#: templates/index.html.ep:81
#: templates/index.html.ep:83
#: templates/partial/lutim.js.ep:198
#: templates/partial/lutim.js.ep:202
msgid "Download link"
msgstr "Download link"
#: templates/index.html.ep:28
#: templates/index.html.ep:31
msgid "Download zip link"
msgstr ""
#: templates/index.html.ep:189
msgid "Drag & drop images here"
msgstr "Drag & drop images here"
#: templates/about.html.ep:7
msgid "Drag and drop an image in the appropriate area or use the traditional way to send files and Lutim will provide you four URLs. One to view the image, an other to directly download it, one you can use on social networks and a last to delete the image when you want."
msgstr "Drag and drop an image in the appropriate area or use the traditional way to send files and Lutim will provide you four URLs. One to view the image, an other to directly download it, one you can use on social networks and a last to delete the image when you want."
#: templates/index.html.ep:150
#: templates/index.html.ep:181
msgid "Encrypt the image (Lutim does not keep the key)."
msgstr "Encrypt the image (Lutim does not keep the key)."
#: templates/partial/lutim.js.ep:74
msgid "Error while trying to modify the image."
msgstr ""
#: templates/stats.html.ep:9
msgid "Evolution of total files"
msgstr "Evolution of total files"
#: templates/myfiles.html.ep:18
msgid "Expires at"
msgstr ""
#: templates/myfiles.html.ep:13
msgid "File name"
msgstr ""
#: templates/about.html.ep:24
msgid "For more details, see the <a href=\"https://framagit.org/luc/lutim\">homepage of the project</a>."
msgstr "For more details, see the <a href=\"https://framagit.org/luc/lutim\">homepage of the project</a>."
#: templates/layouts/default.html.ep:50
msgid "Fork me!"
msgstr "Fork me!"
#: templates/index.html.ep:10
#: templates/index.html.ep:13
msgid "Gallery link"
msgstr ""
#: templates/partial/lutim.js.ep:127
#: templates/partial/lutim.js.ep:145
msgid "Hit Enter, then Ctrl+C to copy the short link"
msgstr ""
#: templates/layouts/default.html.ep:45
msgid "Homepage"
msgstr "Homepage"
#: templates/about.html.ep:20
msgid "How do you pronounce Lutim?"
msgstr "How do you pronounce Lutim?"
#: templates/about.html.ep:6
msgid "How does it work?"
msgstr "How does it work?"
#: templates/about.html.ep:18
msgid "How to report an image?"
msgstr "How to report an image?"
#: templates/about.html.ep:14
msgid "If the files are deleted if you ask it while posting it, their SHA512 footprint are retained."
msgstr "If the files are deleted if you ask it while posting it, their SHA512 footprint are retained."
#: templates/index.html.ep:163
#: templates/index.html.ep:203
msgid "Image URL"
msgstr "Image URL"
#: lib/Lutim/Controller.pm:711
msgid "Image not found."
msgstr ""
#: templates/layouts/default.html.ep:49
msgid "Informations"
msgstr "Informations"
#: templates/layouts/default.html.ep:55
msgid "Install webapp"
msgstr "Install webapp"
#: templates/about.html.ep:11
msgid "Is it really anonymous?"
msgstr "Is it really anonymous?"
#: templates/about.html.ep:9
msgid "Is it really free (as in free beer)?"
msgstr "Is it really free (as in free beer)?"
#: templates/about.html.ep:21
msgid "Juste like you pronounce the French word <a href=\"https://fr.wikipedia.org/wiki/Lutin\">lutin</a> (/ly.tɛ̃/)."
msgstr "Juste like you pronounce the French word <a href=\"https://fr.wikipedia.org/wiki/Lutin\">lutin</a> (/ly.tɛ̃/)."
#: templates/index.html.ep:153
#: templates/index.html.ep:184
msgid "Keep EXIF tags"
msgstr "Keep EXIF tags"
#: templates/index.html.ep:118
#: templates/index.html.ep:166
#: templates/index.html.ep:206
#: templates/partial/lutim.js.ep:263
msgid "Let's go!"
msgstr "Let's go!"
#: templates/layouts/default.html.ep:48
msgid "License:"
msgstr "License:"
#: templates/index.html.ep:89
#: templates/index.html.ep:91
#: templates/partial/lutim.js.ep:212
#: templates/partial/lutim.js.ep:216
msgid "Link for share on social networks"
msgstr "Link for share on social networks"
#: templates/about.html.ep:4
msgid "Lutim is a free (as in free beer) and anonymous image hosting service. It's also the name of the free (as in free speech) software which provides this service."
msgstr "Lutim is a free (as in free beer) and anonymous image hosting service. It's also the name of the free (as in free speech) software which provides this service."
#: templates/about.html.ep:25
msgid "Main developers"
msgstr "Main developers"
#: templates/index.html.ep:73
#: templates/index.html.ep:75
#: templates/partial/lutim.js.ep:186
#: templates/partial/lutim.js.ep:189
msgid "Markdown syntax"
msgstr "Markdown syntax"
#: templates/layouts/default.html.ep:54
#: templates/myfiles.html.ep:2
msgid "My images"
msgstr ""
#: templates/myfiles.html.ep:39
msgid "No limit"
msgstr ""
#: templates/index.html.ep:165
#: templates/index.html.ep:198
msgid "Only images are allowed"
msgstr "Only images are allowed"
#: templates/myfiles.html.ep:6
msgid "Only the images sent with this browser will be listed here. The informations are stored in localStorage: if you delete your localStorage data, you'll loose this informations."
msgstr ""
#: templates/about.html.ep:16
msgid "Only the uploader! (well, only if he's the only owner of the images' rights before the upload)"
msgstr "Only the uploader! (well, only if he's the only owner of the images' rights before the upload)"
#. (config('contact')
#: templates/about.html.ep:19
msgid "Please contact the administrator: %1"
msgstr "Please contact the administrator: %1"
#: templates/gallery.html.ep:51
msgid "Please wait"
msgstr ""
#: templates/index.html.ep:158
msgid "Send an image"
msgstr "Send an image"
#: templates/partial/lutim.js.ep:53
msgid "Share it!"
msgstr ""
#: templates/layouts/default.html.ep:51
msgid "Share on Twitter"
msgstr "Share on Twitter"
#: templates/index.html.ep:133
#: templates/partial/lutim.js.ep:274
msgid "Something bad happened"
msgstr "Something bad happened"
#. ($c->config('contact')
#: lib/Lutim/Controller.pm:719
msgid "Something went wrong when creating the zip file. Try again later or contact the administrator (%1)."
msgstr ""
#: templates/about.html.ep:13
msgid "The IP address of the image's sender is retained for a delay which depends of the administrator's choice (for the official instance, which is located in France, it's one year)."
msgstr "The IP address of the image's sender is retained for a delay which depends of the administrator's choice (for the official instance, which is located in France, it's one year)."
#: templates/about.html.ep:23
msgid "The Lutim software is a <a href=\"http://en.wikipedia.org/wiki/Free_software\">free software</a>, which allows you to download and install it on you own server. Have a look at the <a href=\"https://www.gnu.org/licenses/agpl-3.0.html\">AGPL</a> to see what you can do."
msgstr "The Lutim software is a <a href=\"http://en.wikipedia.org/wiki/Free_software\">free software</a>, which allows you to download and install it on you own server. Have a look at the <a href=\"https://www.gnu.org/licenses/agpl-3.0.html\">AGPL</a> to see what you can do."
#: lib/Lutim/Controller.pm:289
msgid "The URL is not valid."
msgstr "The URL is not valid."
#: lib/Lutim/Controller.pm:101
#: lib/Lutim/Controller.pm:170
msgid "The delete token is invalid."
msgstr "The delete token is invalid."
#. ($upload->filename)
#: lib/Lutim/Controller.pm:433
msgid "The file %1 is not an image."
msgstr "The file %1 is not an image."
#. ($max_file_size)
#. ($tx->res->max_message_size)
#. ($c->req->max_message_size)
#: lib/Lutim/Controller.pm:253
#: lib/Lutim/Controller.pm:322
#: templates/partial/lutim.js.ep:337
msgid "The file exceed the size limit (%1)"
msgstr "The file exceed the size limit (%1)"
#: templates/stats.html.ep:11
msgid "The graph's datas are not updated in real-time."
msgstr "The graph's datas are not updated in real-time."
#. ($image->filename)
#: lib/Lutim/Controller.pm:172
msgid "The image %1 has already been deleted."
msgstr "The image %1 has already been deleted."
#. ($image->filename)
#: lib/Lutim/Controller.pm:181
#: lib/Lutim/Controller.pm:186
msgid "The image %1 has been successfully deleted"
msgstr "The image %1 has been successfully deleted"
#: lib/Lutim/Controller.pm:109
msgid "The image's delay has been successfully modified"
msgstr "The image's delay has been successfully modified"
#: templates/index.html.ep:45
msgid "The images are encrypted on the server (Lutim does not keep the key)."
msgstr "The images are encrypted on the server (Lutim does not keep the key)."
#: templates/about.html.ep:5
msgid "The images you post on Lutim can be stored indefinitely or be deleted at first view or after a delay selected from those proposed."
msgstr "The images you post on Lutim can be stored indefinitely or be deleted at first view or after a delay selected from those proposed."
#. ($c->config->{contact})
#: lib/Lutim/Controller.pm:428
msgid "There is no more available URL. Retry or contact the administrator. %1"
msgstr "There is no more available URL. Retry or contact the administrator. %1"
#: templates/index.html.ep:60
#: templates/partial/lutim.js.ep:46
msgid "Tweet it!"
msgstr "Tweet it!"
#. ($short)
#: lib/Lutim/Controller.pm:143
#: lib/Lutim/Controller.pm:215
msgid "Unable to find the image %1."
msgstr "Unable to find the image %1."
#: lib/Lutim.pm:66
#: lib/Lutim/Controller.pm:518
#: lib/Lutim/Controller.pm:564
#: lib/Lutim/Controller.pm:608
#: lib/Lutim/Controller.pm:648
#: lib/Lutim/Controller.pm:660
#: lib/Lutim/Controller.pm:671
#: lib/Lutim/Controller.pm:708
msgid "Unable to find the image: it has been deleted."
msgstr "Unable to find the image: it has been deleted."
#: lib/Lutim/Controller.pm:85
msgid "Unable to get counter"
msgstr ""
#: templates/about.html.ep:17
msgid "Unlike many image sharing services, you don't give us rights on uploaded images."
msgstr "Unlike many image sharing services, you don't give us rights on uploaded images."
#: templates/index.html.ep:162
#: templates/index.html.ep:201
msgid "Upload an image with its URL"
msgstr "Upload an image with its URL"
#: templates/myfiles.html.ep:17
msgid "Uploaded at"
msgstr ""
#: templates/stats.html.ep:6
msgid "Uploaded files by days"
msgstr "Uploaded files by days"
#. ($config->{contact})
#: lib/Lutim.pm:170
msgid "Uploading is currently disabled, please try later or contact the administrator (%1)."
msgstr "Uploading is currently disabled, please try later or contact the administrator (%1)."
#: templates/index.html.ep:65
#: templates/index.html.ep:67
#: templates/myfiles.html.ep:14
#: templates/partial/lutim.js.ep:172
#: templates/partial/lutim.js.ep:176
msgid "View link"
msgstr "View link"
#: templates/about.html.ep:22
msgid "What about the software which provides the service?"
msgstr "What about the software which provides the service?"
#: templates/about.html.ep:3
msgid "What is Lutim?"
msgstr "What is Lutim?"
#: templates/about.html.ep:15
msgid "Who owns rights on images uploaded on Lutim?"
msgstr "Who owns rights on images uploaded on Lutim?"
#: templates/about.html.ep:12
msgid "Yes, it is! On the other side, for legal reasons, your IP address will be stored when you send an image. Don't panic, it is normally the case of all sites on which you send files!"
msgstr "Yes, it is! On the other side, for legal reasons, your IP address will be stored when you send an image. Don't panic, it is normally the case of all sites on which you send files!"
#. (url_for('/')
#: templates/about.html.ep:10
msgid "Yes, it is! On the other side, if you want to support the developer, you can do it via <a href=\"https://flattr.com/submit/auto?user_id=_SKy_&amp;url=%1&amp;title=Lutim&amp;category=software\">Flattr</a> or with <a href=\"bitcoin:1JCEtmx9pyzWfitMQj2pKAk8GNgyix7RmA?label=lutim\">BitCoin</a>."
msgstr "Yes, it is! On the other side, if you want to support the developer, you can do it via <a href=\"https://flattr.com/submit/auto?user_id=_SKy_&amp;url=%1&amp;title=Lutim&amp;category=software\">Flattr</a> or with <a href=\"bitcoin:1JCEtmx9pyzWfitMQj2pKAk8GNgyix7RmA?label=lutim\">BitCoin</a>."
#: templates/about.html.ep:8
msgid "You can, optionally, request that the image(s) posted on Lutim to be deleted at first view (or download) or after the delay selected from those proposed."
msgstr "You can, optionally, request that the image(s) posted on Lutim to be deleted at first view (or download) or after the delay selected from those proposed."
#: templates/about.html.ep:27
msgid "and on"
msgstr "and on"
#: templates/about.html.ep:27
msgid "core developer"
msgstr "core developer"
#: templates/index.html.ep:3
msgid "no time limit"
msgstr "no time limit"
#: templates/about.html.ep:27
msgid "on"
msgstr "on"
#: templates/about.html.ep:36
msgid "spanish translation"
msgstr "spanish translation"
#: templates/about.html.ep:28
msgid "webapp developer"
msgstr "webapp developer"

View File

@@ -1,477 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Luc Didry <luc@didry.org>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Lutim\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: 2015-09-17 22:00+0000\n"
"Last-Translator: Luc Didry <luc@didry.org>\n"
"Language-Team: Spanish (http://www.transifex.com/fiat-tux/lutim/language/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. ($delay)
#. (config('max_delay')
#: templates/partial/lutim.js.ep:238
#: templates/partial/lutim.js.ep:247
#: templates/partial/lutim.js.ep:248
msgid "%1 days"
msgstr "%1 días"
#. ($total)
#: templates/stats.html.ep:2
msgid "%1 sent images on this instance from beginning."
msgstr "%1 imágenes enviadas a esta instancia desde el inicio."
#: templates/index.html.ep:190
msgid "-or-"
msgstr "-o-"
#: templates/index.html.ep:5
msgid "1 year"
msgstr "1 año"
#: templates/index.html.ep:4
#: templates/partial/lutim.js.ep:247
msgid "24 hours"
msgstr "24 horas"
#: templates/myfiles.html.ep:79
msgid ": Error while trying to get the counter."
msgstr ": Error al intentar obtener el contador."
#: lib/Lutim/Controller.pm:270
msgid "An error occured while downloading the image."
msgstr "Error al intentar modificar la imagen."
#: templates/about.html.ep:40
#: templates/myfiles.html.ep:27
#: templates/stats.html.ep:13
msgid "Back to homepage"
msgstr "Volver a la página inicial"
#: templates/index.html.ep:193
#: templates/index.html.ep:194
msgid "Click to open the file browser"
msgstr "Clic para abrir el explorador de archivos"
#: templates/about.html.ep:30
msgid "Contributors"
msgstr "Contribuidores"
#: templates/partial/lutim.js.ep:313
#: templates/partial/lutim.js.ep:361
#: templates/partial/lutim.js.ep:435
msgid "Copy all view links to clipboard"
msgstr "Copiar todos los enlaces de visualización al portapapeles"
#: templates/index.html.ep:18
#: templates/index.html.ep:36
#: templates/index.html.ep:69
#: templates/index.html.ep:77
#: templates/index.html.ep:85
#: templates/index.html.ep:93
#: templates/partial/lutim.js.ep:180
#: templates/partial/lutim.js.ep:192
#: templates/partial/lutim.js.ep:206
#: templates/partial/lutim.js.ep:220
msgid "Copy to clipboard"
msgstr "Copiar al portapapeles"
#: templates/myfiles.html.ep:15
msgid "Counter"
msgstr "Contador"
#: templates/index.html.ep:115
#: templates/index.html.ep:147
#: templates/index.html.ep:178
#: templates/myfiles.html.ep:16
#: templates/partial/lutim.js.ep:259
msgid "Delete at first view?"
msgstr "¿Borrar en la primera vista?"
#: templates/index.html.ep:98
#: templates/myfiles.html.ep:19
#: templates/partial/common.js.ep:36
msgid "Deletion link"
msgstr "Enlace para borrar"
#: templates/gallery.html.ep:6
#: templates/gallery.html.ep:63
msgid "Download all images"
msgstr "Descargar todas las imágenes"
#: templates/index.html.ep:81
#: templates/index.html.ep:83
#: templates/partial/lutim.js.ep:198
#: templates/partial/lutim.js.ep:202
msgid "Download link"
msgstr "Enlace de descarga"
#: templates/index.html.ep:28
#: templates/index.html.ep:31
msgid "Download zip link"
msgstr "Enlace de descarga del archivo de las imágenes"
#: templates/index.html.ep:189
msgid "Drag & drop images here"
msgstr "Arrastre y suelte imágenes aquí"
#: templates/about.html.ep:7
msgid "Drag and drop an image in the appropriate area or use the traditional way to send files and Lutim will provide you four URLs. One to view the image, an other to directly download it, one you can use on social networks and a last to delete the image when you want."
msgstr "Arrastre y suelte una imagen en el área apropiada, o use el método tradicional para enviar ficheros, y Lutim proporcionará cuatro URLs. Una para ver la imagen, otra para descargarla directamente, una que upede usar en redes sociales, y una última para borrar la imagen cuando lo desee."
#: templates/index.html.ep:150
#: templates/index.html.ep:181
msgid "Encrypt the image (Lutim does not keep the key)."
msgstr "Las imágenes se cifran en el servidor (Lutim no guarda la clave)."
#: templates/partial/lutim.js.ep:74
msgid "Error while trying to modify the image."
msgstr "Error al intentar modificar la imagen."
#: templates/stats.html.ep:9
msgid "Evolution of total files"
msgstr "Evolución de archivos en total"
#: templates/myfiles.html.ep:18
msgid "Expires at"
msgstr "Expira"
#: templates/myfiles.html.ep:13
msgid "File name"
msgstr "Nombre de archivo"
#: templates/about.html.ep:24
msgid "For more details, see the <a href=\"https://framagit.org/luc/lutim\">homepage of the project</a>."
msgstr "Para más detalles, vea la <a href=\"https://framagit.org/luc/lutim\">página del proyecto</a>."
#: templates/layouts/default.html.ep:50
msgid "Fork me!"
msgstr "¡Clóname!"
#: templates/index.html.ep:10
#: templates/index.html.ep:13
msgid "Gallery link"
msgstr "Enlace a la galería"
#: templates/partial/lutim.js.ep:127
#: templates/partial/lutim.js.ep:145
msgid "Hit Enter, then Ctrl+C to copy the short link"
msgstr "Presione Ingresar, entonces Ctrl + C para copiar el enlace"
#: templates/layouts/default.html.ep:45
msgid "Homepage"
msgstr "Página inicial"
#: templates/about.html.ep:20
msgid "How do you pronounce Lutim?"
msgstr "¿Cómo se pronuncia Lutim?"
#: templates/about.html.ep:6
msgid "How does it work?"
msgstr "¿Cómo funciona?"
#: templates/about.html.ep:18
msgid "How to report an image?"
msgstr "¿Cómo informar sobre una imagen?"
#: templates/about.html.ep:14
msgid "If the files are deleted if you ask it while posting it, their SHA512 footprint are retained."
msgstr "Si los ficheros se borran por haberlo solicitado al enviarlos, se retiene su huella digital SHA512."
#: templates/index.html.ep:163
#: templates/index.html.ep:203
msgid "Image URL"
msgstr "URL de la imagen"
#: lib/Lutim/Controller.pm:711
msgid "Image not found."
msgstr "Imagen no encontrada."
#: templates/layouts/default.html.ep:49
msgid "Informations"
msgstr "Informaciones"
#: templates/layouts/default.html.ep:55
msgid "Install webapp"
msgstr "Instalar webapp"
#: templates/about.html.ep:11
msgid "Is it really anonymous?"
msgstr "¿Es realmente anónimo?"
#: templates/about.html.ep:9
msgid "Is it really free (as in free beer)?"
msgstr "¿Es realmente gratis?"
#: templates/about.html.ep:21
msgid "Juste like you pronounce the French word <a href=\"https://fr.wikipedia.org/wiki/Lutin\">lutin</a> (/ly.tɛ̃/)."
msgstr "Tal y como se pronuncia la palabra francesa <a href=\"https://fr.wikipedia.org/wiki/Lutin\">lutin</a> (/ly.tɛ̃/)."
#: templates/index.html.ep:153
#: templates/index.html.ep:184
msgid "Keep EXIF tags"
msgstr "Mantener las etiquetas EXIF"
#: templates/index.html.ep:118
#: templates/index.html.ep:166
#: templates/index.html.ep:206
#: templates/partial/lutim.js.ep:263
msgid "Let's go!"
msgstr "¡Vamos allá!"
#: templates/layouts/default.html.ep:48
msgid "License:"
msgstr "Licencia:"
#: templates/index.html.ep:89
#: templates/index.html.ep:91
#: templates/partial/lutim.js.ep:212
#: templates/partial/lutim.js.ep:216
msgid "Link for share on social networks"
msgstr "Enlace para compartir en redes sociales"
#: templates/about.html.ep:4
msgid "Lutim is a free (as in free beer) and anonymous image hosting service. It's also the name of the free (as in free speech) software which provides this service."
msgstr "Lutim es un servicio de alojamiento de imágenes anónimo y gratuito. También es el nombre del software libre que proporciona este servicio."
#: templates/about.html.ep:25
msgid "Main developers"
msgstr "Desarrolladores principales"
#: templates/index.html.ep:73
#: templates/index.html.ep:75
#: templates/partial/lutim.js.ep:186
#: templates/partial/lutim.js.ep:189
msgid "Markdown syntax"
msgstr "Sintaxis de Markdown"
#: templates/layouts/default.html.ep:54
#: templates/myfiles.html.ep:2
msgid "My images"
msgstr "Mis Imágenes"
#: templates/myfiles.html.ep:39
msgid "No limit"
msgstr "Sin fecha de caducidad"
#: templates/index.html.ep:165
#: templates/index.html.ep:198
msgid "Only images are allowed"
msgstr "Sólo se admiten imágenes"
#: templates/myfiles.html.ep:6
msgid "Only the images sent with this browser will be listed here. The informations are stored in localStorage: if you delete your localStorage data, you'll loose this informations."
msgstr ""
#: templates/about.html.ep:16
msgid "Only the uploader! (well, only if he's the only owner of the images' rights before the upload)"
msgstr "¡Sólo el usuario! (bueno, sólo si él/ela es el único titular de los derechos de las imágenes antes de subirlas)"
#. (config('contact')
#: templates/about.html.ep:19
msgid "Please contact the administrator: %1"
msgstr "Por favor, contacte con el administrador: %1"
#: templates/gallery.html.ep:51
msgid "Please wait"
msgstr "Por favor espera"
#: templates/index.html.ep:158
msgid "Send an image"
msgstr "Enviar una imagen"
#: templates/partial/lutim.js.ep:53
msgid "Share it!"
msgstr "¡Compártelo!"
#: templates/layouts/default.html.ep:51
msgid "Share on Twitter"
msgstr "Compartir en Twitter"
#: templates/index.html.ep:133
#: templates/partial/lutim.js.ep:274
msgid "Something bad happened"
msgstr "Algo malo ha pasado"
#. ($c->config('contact')
#: lib/Lutim/Controller.pm:719
msgid "Something went wrong when creating the zip file. Try again later or contact the administrator (%1)."
msgstr "Algo malo ha pasado. Inténtelo de nuevo más tarde o contacte con el administrador (%1)."
#: templates/about.html.ep:13
msgid "The IP address of the image's sender is retained for a delay which depends of the administrator's choice (for the official instance, which is located in France, it's one year)."
msgstr "La dirección IP del remitente de la imagen se retiene durante un tiempo, que depende de lo que elija el administrador (para la instancia oficial, que está localizada en Francia, es un año)."
#: templates/about.html.ep:23
msgid "The Lutim software is a <a href=\"http://en.wikipedia.org/wiki/Free_software\">free software</a>, which allows you to download and install it on you own server. Have a look at the <a href=\"https://www.gnu.org/licenses/agpl-3.0.html\">AGPL</a> to see what you can do."
msgstr "El software Lutim es <a href=\"http://es.wikipedia.org/wiki/Software_libre\">software libre</a>, lo que le permite descargarlo e instalarlo en su propio servidor. Eche un vistazo a la licencia <a href=\"https://www.gnu.org/licenses/agpl-3.0.html\">AGPL</a> para ver qué puede hacer."
#: lib/Lutim/Controller.pm:289
msgid "The URL is not valid."
msgstr "La URL no es válida."
#: lib/Lutim/Controller.pm:101
#: lib/Lutim/Controller.pm:170
msgid "The delete token is invalid."
msgstr "El código de borrado no es válido."
#. ($upload->filename)
#: lib/Lutim/Controller.pm:433
msgid "The file %1 is not an image."
msgstr "El archivo %1 no es una imagen."
#. ($max_file_size)
#. ($tx->res->max_message_size)
#. ($c->req->max_message_size)
#: lib/Lutim/Controller.pm:253
#: lib/Lutim/Controller.pm:322
#: templates/partial/lutim.js.ep:337
msgid "The file exceed the size limit (%1)"
msgstr "El archivo supera el límite de tamaño (%1)"
#: templates/stats.html.ep:11
msgid "The graph's datas are not updated in real-time."
msgstr "Los datos del gráfico no se actualizan en tiempo real."
#. ($image->filename)
#: lib/Lutim/Controller.pm:172
msgid "The image %1 has already been deleted."
msgstr "La imagen %1 ya se ha borrado."
#. ($image->filename)
#: lib/Lutim/Controller.pm:181
#: lib/Lutim/Controller.pm:186
msgid "The image %1 has been successfully deleted"
msgstr "La imagen %1 se ha borrado correctamente"
#: lib/Lutim/Controller.pm:109
msgid "The image's delay has been successfully modified"
msgstr "Se ha modificado correctamente el tiempo de la imagen"
#: templates/index.html.ep:45
msgid "The images are encrypted on the server (Lutim does not keep the key)."
msgstr "Las imágenes se cifran en el servidor (Lutim no guarda la clave)."
#: templates/about.html.ep:5
msgid "The images you post on Lutim can be stored indefinitely or be deleted at first view or after a delay selected from those proposed."
msgstr "Puede, opcionalmente, solicitar que la imagen publicada en Lutim se elimine con la primera vista (o descarga) o tras un tiempo seleccionado de entre varios propuestos."
#. ($c->config->{contact})
#: lib/Lutim/Controller.pm:428
msgid "There is no more available URL. Retry or contact the administrator. %1"
msgstr "No más URL disponibles. Inténtelo de nuevo o contacte con el administrador. %1"
#: templates/index.html.ep:60
#: templates/partial/lutim.js.ep:46
msgid "Tweet it!"
msgstr "¡Tuitéalo!"
#. ($short)
#: lib/Lutim/Controller.pm:143
#: lib/Lutim/Controller.pm:215
msgid "Unable to find the image %1."
msgstr "No se ha podido encontrar la imagen %1."
#: lib/Lutim.pm:66
#: lib/Lutim/Controller.pm:518
#: lib/Lutim/Controller.pm:564
#: lib/Lutim/Controller.pm:608
#: lib/Lutim/Controller.pm:648
#: lib/Lutim/Controller.pm:660
#: lib/Lutim/Controller.pm:671
#: lib/Lutim/Controller.pm:708
msgid "Unable to find the image: it has been deleted."
msgstr "No se ha podido encontrar la imagen: ha sido borrada."
#: lib/Lutim/Controller.pm:85
msgid "Unable to get counter"
msgstr "Imposible recuperar el contador"
#: templates/about.html.ep:17
msgid "Unlike many image sharing services, you don't give us rights on uploaded images."
msgstr "A diferencia de muchos servicios de compartición de imágenes, usted no cede los derechos de las imágenes que sube."
#: templates/index.html.ep:162
#: templates/index.html.ep:201
msgid "Upload an image with its URL"
msgstr "Subir una imagen con la URL"
#: templates/myfiles.html.ep:17
msgid "Uploaded at"
msgstr "Enviado el"
#: templates/stats.html.ep:6
msgid "Uploaded files by days"
msgstr "Archivos enviados por día"
#. ($config->{contact})
#: lib/Lutim.pm:170
msgid "Uploading is currently disabled, please try later or contact the administrator (%1)."
msgstr "La carga está deshabilitada en estos momentos, por favor inténtelo más tarde o contacte con el administrador (%1)."
#: templates/index.html.ep:65
#: templates/index.html.ep:67
#: templates/myfiles.html.ep:14
#: templates/partial/lutim.js.ep:172
#: templates/partial/lutim.js.ep:176
msgid "View link"
msgstr "Enlace de visualización"
#: templates/about.html.ep:22
msgid "What about the software which provides the service?"
msgstr "¿Y qué hay sobre el software que proporciona el servicio?"
#: templates/about.html.ep:3
msgid "What is Lutim?"
msgstr "¿Qué es Lutim?"
#: templates/about.html.ep:15
msgid "Who owns rights on images uploaded on Lutim?"
msgstr "¿Quién posee los derechos de las imágenes que se suben a Lutim?"
#: templates/about.html.ep:12
msgid "Yes, it is! On the other side, for legal reasons, your IP address will be stored when you send an image. Don't panic, it is normally the case of all sites on which you send files!"
msgstr "¡Sí, lo es! Por otro lado, por razones legales, se almacena la dirección IP cuando se envía una imagen. Que no cunda el pánico, ¡es el caso habitual para todos los sitios a los cuales se envían ficheros!"
#. (url_for('/')
#: templates/about.html.ep:10
msgid "Yes, it is! On the other side, if you want to support the developer, you can do it via <a href=\"https://flattr.com/submit/auto?user_id=_SKy_&amp;url=%1&amp;title=Lutim&amp;category=software\">Flattr</a> or with <a href=\"bitcoin:1JCEtmx9pyzWfitMQj2pKAk8GNgyix7RmA?label=lutim\">BitCoin</a>."
msgstr "¡Sí, lo es! Por otro lado, si quiere ayudar a apoyar al desarrollador, puede hacerlo vía <a href=\"https://flattr.com/submit/auto?user_id=_SKy_&amp;url=%1&amp;title=Lutim&amp;category=software\">Flattr</a> o con <a href=\"bitcoin:1JCEtmx9pyzWfitMQj2pKAk8GNgyix7RmA?label=lutim\">BitCoin</a>."
#: templates/about.html.ep:8
msgid "You can, optionally, request that the image(s) posted on Lutim to be deleted at first view (or download) or after the delay selected from those proposed."
msgstr "Puede, opcionalmente, solicitar que la imagen publicada en Lutim se elimine con la primera vista (o descarga) o tras un tiempo seleccionado de entre varios propuestos."
#: templates/about.html.ep:27
msgid "and on"
msgstr "y en"
#: templates/about.html.ep:27
msgid "core developer"
msgstr "desarrollador principal"
#: templates/index.html.ep:3
msgid "no time limit"
msgstr "Sin tiempo límite"
#: templates/about.html.ep:27
msgid "on"
msgstr "en"
#: templates/about.html.ep:36
msgid "spanish translation"
msgstr "traducción al español"
#: templates/about.html.ep:28
msgid "webapp developer"
msgstr "desarrollador de la webapp"

View File

@@ -1,479 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Luc Didry <luc@didry.org>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Lutim\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: 2015-09-17 22:02+0000\n"
"Last-Translator: Luc Didry <luc@didry.org>\n"
"Language-Team: French (http://www.transifex.com/fiat-tux/lutim/language/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. ($delay)
#. (config('max_delay')
#: templates/partial/lutim.js.ep:238
#: templates/partial/lutim.js.ep:247
#: templates/partial/lutim.js.ep:248
msgid "%1 days"
msgstr "%1 jours"
#. ($total)
#: templates/stats.html.ep:2
msgid "%1 sent images on this instance from beginning."
msgstr "%1 images envoyées sur cette instance depuis le début."
#: templates/index.html.ep:190
msgid "-or-"
msgstr "-ou-"
#: templates/index.html.ep:5
msgid "1 year"
msgstr "1 an"
#: templates/index.html.ep:4
#: templates/partial/lutim.js.ep:247
msgid "24 hours"
msgstr "24 heures"
#: templates/myfiles.html.ep:79
msgid ": Error while trying to get the counter."
msgstr " : Erreur en essayant de récupérer le compteur."
#: lib/Lutim/Controller.pm:270
msgid "An error occured while downloading the image."
msgstr "Une erreur est survenue lors du téléchargement de l'image."
#: templates/about.html.ep:40
#: templates/myfiles.html.ep:27
#: templates/stats.html.ep:13
msgid "Back to homepage"
msgstr "Retour à la page d'accueil"
#: templates/index.html.ep:193
#: templates/index.html.ep:194
msgid "Click to open the file browser"
msgstr "Cliquez pour utiliser le navigateur de fichier"
#: templates/about.html.ep:30
msgid "Contributors"
msgstr "Contributeurs"
#: templates/partial/lutim.js.ep:313
#: templates/partial/lutim.js.ep:361
#: templates/partial/lutim.js.ep:435
msgid "Copy all view links to clipboard"
msgstr "Copier tous les liens de visualisation dans le presse-papier"
#: templates/index.html.ep:18
#: templates/index.html.ep:36
#: templates/index.html.ep:69
#: templates/index.html.ep:77
#: templates/index.html.ep:85
#: templates/index.html.ep:93
#: templates/partial/lutim.js.ep:180
#: templates/partial/lutim.js.ep:192
#: templates/partial/lutim.js.ep:206
#: templates/partial/lutim.js.ep:220
msgid "Copy to clipboard"
msgstr "Copier dans le presse-papier"
#: templates/myfiles.html.ep:15
msgid "Counter"
msgstr "Compteur"
#: templates/index.html.ep:115
#: templates/index.html.ep:147
#: templates/index.html.ep:178
#: templates/myfiles.html.ep:16
#: templates/partial/lutim.js.ep:259
msgid "Delete at first view?"
msgstr "Supprimer au premier accès ?"
#: templates/index.html.ep:98
#: templates/myfiles.html.ep:19
#: templates/partial/common.js.ep:36
msgid "Deletion link"
msgstr "Lien de suppression"
#: templates/gallery.html.ep:6
#: templates/gallery.html.ep:63
msgid "Download all images"
msgstr "Télécharger toutes les images"
#: templates/index.html.ep:81
#: templates/index.html.ep:83
#: templates/partial/lutim.js.ep:198
#: templates/partial/lutim.js.ep:202
msgid "Download link"
msgstr "Lien de téléchargement"
#: templates/index.html.ep:28
#: templates/index.html.ep:31
msgid "Download zip link"
msgstr "Lien de téléchargement de l'archive des images"
#: templates/index.html.ep:189
msgid "Drag & drop images here"
msgstr "Déposez vos images ici"
#: templates/about.html.ep:7
msgid "Drag and drop an image in the appropriate area or use the traditional way to send files and Lutim will provide you four URLs. One to view the image, an other to directly download it, one you can use on social networks and a last to delete the image when you want."
msgstr "Faites glisser des images dans la zone prévue à cet effet ou sélectionnez un fichier de façon classique et Lutim vous fournira quatre URLs en retour. Une pour afficher limage, une autre pour la télécharger directement, une pour l'utiliser sur les réseaux sociaux et une dernière pour supprimer votre image quand vous le souhaitez"
#: templates/index.html.ep:150
#: templates/index.html.ep:181
msgid "Encrypt the image (Lutim does not keep the key)."
msgstr "Chiffrer l'image (Lutim ne stocke pas la clé)."
#: templates/partial/lutim.js.ep:74
msgid "Error while trying to modify the image."
msgstr "Une erreur est survenue en essayant de modifier l'image."
#: templates/stats.html.ep:9
msgid "Evolution of total files"
msgstr "Évolution du nombre total de fichiers"
#: templates/myfiles.html.ep:18
msgid "Expires at"
msgstr "Expire le"
#: templates/myfiles.html.ep:13
msgid "File name"
msgstr "Nom du fichier"
#: templates/about.html.ep:24
msgid "For more details, see the <a href=\"https://framagit.org/luc/lutim\">homepage of the project</a>."
msgstr "Pour plus de détails, consultez la page <a href=\"https://framagit.org/luc/lutim\">Github</a> du projet."
#: templates/layouts/default.html.ep:50
msgid "Fork me!"
msgstr "Créez un fork !"
#: templates/index.html.ep:10
#: templates/index.html.ep:13
msgid "Gallery link"
msgstr "Lien vers la galerie"
#: templates/partial/lutim.js.ep:127
#: templates/partial/lutim.js.ep:145
msgid "Hit Enter, then Ctrl+C to copy the short link"
msgstr "Appuyez sur la touche Entrée puis faites Ctrl+C pour copier le lien"
#: templates/layouts/default.html.ep:45
msgid "Homepage"
msgstr "Accueil"
#: templates/about.html.ep:20
msgid "How do you pronounce Lutim?"
msgstr "Comment doit-on prononcer Lutim ?"
#: templates/about.html.ep:6
msgid "How does it work?"
msgstr "Comment ça marche ?"
#: templates/about.html.ep:18
msgid "How to report an image?"
msgstr "Comment peut-on faire pour signaler une image ?"
#: templates/about.html.ep:14
msgid "If the files are deleted if you ask it while posting it, their SHA512 footprint are retained."
msgstr "Si les fichiers sont bien supprimés si vous en avez exprimé le choix, leur empreinte SHA512 est toutefois conservée."
#: templates/index.html.ep:163
#: templates/index.html.ep:203
msgid "Image URL"
msgstr "URL de l'image"
#: lib/Lutim/Controller.pm:711
msgid "Image not found."
msgstr "Image non trouvée."
#: templates/layouts/default.html.ep:49
msgid "Informations"
msgstr "Informations"
#: templates/layouts/default.html.ep:55
msgid "Install webapp"
msgstr "Installer la webapp"
#: templates/about.html.ep:11
msgid "Is it really anonymous?"
msgstr "Cest vraiment anonyme ?"
#: templates/about.html.ep:9
msgid "Is it really free (as in free beer)?"
msgstr "Cest vraiment gratuit ?"
#: templates/about.html.ep:21
msgid "Juste like you pronounce the French word <a href=\"https://fr.wikipedia.org/wiki/Lutin\">lutin</a> (/ly.tɛ̃/)."
msgstr "Comme on prononce <a href=\"https://fr.wikipedia.org/wiki/Lutin\">lutin</a> (/ly.tɛ̃/)."
#: templates/index.html.ep:153
#: templates/index.html.ep:184
msgid "Keep EXIF tags"
msgstr "Conserver les données EXIF"
#: templates/index.html.ep:118
#: templates/index.html.ep:166
#: templates/index.html.ep:206
#: templates/partial/lutim.js.ep:263
msgid "Let's go!"
msgstr "Allons-y !"
#: templates/layouts/default.html.ep:48
msgid "License:"
msgstr "Licence :"
#: templates/index.html.ep:89
#: templates/index.html.ep:91
#: templates/partial/lutim.js.ep:212
#: templates/partial/lutim.js.ep:216
msgid "Link for share on social networks"
msgstr "Lien pour partager sur les réseaux sociaux"
#: templates/about.html.ep:4
msgid "Lutim is a free (as in free beer) and anonymous image hosting service. It's also the name of the free (as in free speech) software which provides this service."
msgstr "Lutim est un service gratuit et anonyme dhébergement dimages. Il sagit aussi du nom du logiciel (libre) qui fournit ce service."
#: templates/about.html.ep:25
msgid "Main developers"
msgstr "Développeurs de l'application"
#: templates/index.html.ep:73
#: templates/index.html.ep:75
#: templates/partial/lutim.js.ep:186
#: templates/partial/lutim.js.ep:189
msgid "Markdown syntax"
msgstr "Syntaxe Markdown"
#: templates/layouts/default.html.ep:54
#: templates/myfiles.html.ep:2
msgid "My images"
msgstr "Mes images"
#: templates/myfiles.html.ep:39
msgid "No limit"
msgstr "Pas de date d'expiration"
#: templates/index.html.ep:165
#: templates/index.html.ep:198
msgid "Only images are allowed"
msgstr "Seules les images sont acceptées"
#: templates/myfiles.html.ep:6
msgid "Only the images sent with this browser will be listed here. The informations are stored in localStorage: if you delete your localStorage data, you'll loose this informations."
msgstr "Seules les images envoyées avec ce navigateur seront listées ici. Les informations sont stockées en localStorage : si vous supprimez vos données localStorage, vous perdrez ces informations."
#: templates/about.html.ep:16
msgid "Only the uploader! (well, only if he's the only owner of the images' rights before the upload)"
msgstr "Seulement l'envoyeur ! (enfin, seulement s'il possède des droits exclusifs sur les images avant de les envoyer)"
#. (config('contact')
#: templates/about.html.ep:19
msgid "Please contact the administrator: %1"
msgstr "Veuillez contacter ladministrateur : %1"
#: templates/gallery.html.ep:51
msgid "Please wait"
msgstr "Veuillez patienter"
#: templates/index.html.ep:158
msgid "Send an image"
msgstr "Envoyer une image"
#: templates/partial/lutim.js.ep:53
msgid "Share it!"
msgstr "Partagez !"
#: templates/layouts/default.html.ep:51
msgid "Share on Twitter"
msgstr "Partager sur Twitter"
#: templates/index.html.ep:133
#: templates/partial/lutim.js.ep:274
msgid "Something bad happened"
msgstr "Un problème est survenu"
#. ($c->config('contact')
#: lib/Lutim/Controller.pm:719
msgid "Something went wrong when creating the zip file. Try again later or contact the administrator (%1)."
msgstr "Quelque chose s'est mal passé lors de la création de l'archive. Veuillez réessayer plus tard ou contactez l'administrateur (%1)."
#: templates/about.html.ep:13
msgid "The IP address of the image's sender is retained for a delay which depends of the administrator's choice (for the official instance, which is located in France, it's one year)."
msgstr ""
"LIP de la personne ayant déposé limage est stockée pendant un délai dépendant de l'administrateur de l'instance (pour l'instance officielle, dont le serveur est en France, c'est un délai\n"
" d'un an)."
#: templates/about.html.ep:23
msgid "The Lutim software is a <a href=\"http://en.wikipedia.org/wiki/Free_software\">free software</a>, which allows you to download and install it on you own server. Have a look at the <a href=\"https://www.gnu.org/licenses/agpl-3.0.html\">AGPL</a> to see what you can do."
msgstr "Le logiciel Lutim est un <a href=\"https://fr.wikipedia.org/wiki/Logiciel_libre\">logiciel libre</a>, ce qui vous permet de le télécharger et de linstaller sur votre propre serveur. Jetez un coup dœil à l<a href=\"https://www.gnu.org/licenses/agpl-3.0.html\">AGPL</a> pour voir quels sont vos droits"
#: lib/Lutim/Controller.pm:289
msgid "The URL is not valid."
msgstr "L'URL n'est pas valide."
#: lib/Lutim/Controller.pm:101
#: lib/Lutim/Controller.pm:170
msgid "The delete token is invalid."
msgstr "Le jeton de suppression est invalide."
#. ($upload->filename)
#: lib/Lutim/Controller.pm:433
msgid "The file %1 is not an image."
msgstr "Le fichier %1 n'est pas une image."
#. ($max_file_size)
#. ($tx->res->max_message_size)
#. ($c->req->max_message_size)
#: lib/Lutim/Controller.pm:253
#: lib/Lutim/Controller.pm:322
#: templates/partial/lutim.js.ep:337
msgid "The file exceed the size limit (%1)"
msgstr "Le fichier dépasse la limite de taille (%1)"
#: templates/stats.html.ep:11
msgid "The graph's datas are not updated in real-time."
msgstr "Les données du graphique ne sont pas mises à jour en temps réél."
#. ($image->filename)
#: lib/Lutim/Controller.pm:172
msgid "The image %1 has already been deleted."
msgstr "L'image %1 a déjà été supprimée."
#. ($image->filename)
#: lib/Lutim/Controller.pm:181
#: lib/Lutim/Controller.pm:186
msgid "The image %1 has been successfully deleted"
msgstr "L'image %1 a été supprimée avec succès."
#: lib/Lutim/Controller.pm:109
msgid "The image's delay has been successfully modified"
msgstr "Le délai de l'image a été modifié avec succès."
#: templates/index.html.ep:45
msgid "The images are encrypted on the server (Lutim does not keep the key)."
msgstr "Les images sont chiffrées sur le serveur (Lutim ne stocke pas la clé)."
#: templates/about.html.ep:5
msgid "The images you post on Lutim can be stored indefinitely or be deleted at first view or after a delay selected from those proposed."
msgstr "Les images déposées sur Lutim peuvent être stockées indéfiniment, ou seffacer dès le premier affichage ou au bout du délai choisi parmi ceux proposés."
#. ($c->config->{contact})
#: lib/Lutim/Controller.pm:428
msgid "There is no more available URL. Retry or contact the administrator. %1"
msgstr "Il n'y a plus d'URL disponible. Veuillez réessayer ou contacter l'administrateur. %1."
#: templates/index.html.ep:60
#: templates/partial/lutim.js.ep:46
msgid "Tweet it!"
msgstr "Tweetez !"
#. ($short)
#: lib/Lutim/Controller.pm:143
#: lib/Lutim/Controller.pm:215
msgid "Unable to find the image %1."
msgstr "Impossible de trouver l'image %1."
#: lib/Lutim.pm:66
#: lib/Lutim/Controller.pm:518
#: lib/Lutim/Controller.pm:564
#: lib/Lutim/Controller.pm:608
#: lib/Lutim/Controller.pm:648
#: lib/Lutim/Controller.pm:660
#: lib/Lutim/Controller.pm:671
#: lib/Lutim/Controller.pm:708
msgid "Unable to find the image: it has been deleted."
msgstr "Impossible de trouver l'image : elle a été supprimée."
#: lib/Lutim/Controller.pm:85
msgid "Unable to get counter"
msgstr "Impossible de récupérer le compteur"
#: templates/about.html.ep:17
msgid "Unlike many image sharing services, you don't give us rights on uploaded images."
msgstr "Au contraire de la majorité des services de partages d'image, vous ne nous cédez aucun droit sur les images envoyées."
#: templates/index.html.ep:162
#: templates/index.html.ep:201
msgid "Upload an image with its URL"
msgstr "Déposer une image par son URL"
#: templates/myfiles.html.ep:17
msgid "Uploaded at"
msgstr "Envoyé le"
#: templates/stats.html.ep:6
msgid "Uploaded files by days"
msgstr "Fichiers envoyés, par jour"
#. ($config->{contact})
#: lib/Lutim.pm:170
msgid "Uploading is currently disabled, please try later or contact the administrator (%1)."
msgstr "L'envoi d'images est actuellement désactivé, veuillez réessayer plus tard ou contacter l'administrateur (%1)."
#: templates/index.html.ep:65
#: templates/index.html.ep:67
#: templates/myfiles.html.ep:14
#: templates/partial/lutim.js.ep:172
#: templates/partial/lutim.js.ep:176
msgid "View link"
msgstr "Lien d'affichage"
#: templates/about.html.ep:22
msgid "What about the software which provides the service?"
msgstr "Et à propos du logiciel qui fournit le service ?"
#: templates/about.html.ep:3
msgid "What is Lutim?"
msgstr "Quest-ce que Lutim ?"
#: templates/about.html.ep:15
msgid "Who owns rights on images uploaded on Lutim?"
msgstr "Qui possède des droits sur les images envoyées sur Lutim ?"
#: templates/about.html.ep:12
msgid "Yes, it is! On the other side, for legal reasons, your IP address will be stored when you send an image. Don't panic, it is normally the case of all sites on which you send files!"
msgstr "Oui, ça lest ! Par contre, pour des raisons légales, votre adresse IP sera enregistrée lorsque vous enverrez une image. Ne vous affolez pas, cest de toute façon normalement le cas de tous les sites sur lesquels vous envoyez des fichiers !"
#. (url_for('/')
#: templates/about.html.ep:10
msgid "Yes, it is! On the other side, if you want to support the developer, you can do it via <a href=\"https://flattr.com/submit/auto?user_id=_SKy_&amp;url=%1&amp;title=Lutim&amp;category=software\">Flattr</a> or with <a href=\"bitcoin:1JCEtmx9pyzWfitMQj2pKAk8GNgyix7RmA?label=lutim\">BitCoin</a>."
msgstr "Oui, ça lest ! Par contre, si vous avez envie de soutenir le développeur, vous pouvez faire un microdon avec <a href=\"https://flattr.com/submit/auto?user_id=_SKy_&amp;url=%1&amp;title=Lutim&amp;category=software\">Flattr</a> ou en <a href=\"bitcoin:1JCEtmx9pyzWfitMQj2pKAk8GNgyix7RmA?label=lutim\">BitCoin</a>."
#: templates/about.html.ep:8
msgid "You can, optionally, request that the image(s) posted on Lutim to be deleted at first view (or download) or after the delay selected from those proposed."
msgstr "Vous pouvez, de façon facultative, demander à ce que la ou les images déposées sur Lutim soient supprimées après leur premier affichage (ou téléchargement) ou au bout d'un délai choisi parmi ceux proposés."
#: templates/about.html.ep:27
msgid "and on"
msgstr "et sur"
#: templates/about.html.ep:27
msgid "core developer"
msgstr "développeur principal"
#: templates/index.html.ep:3
msgid "no time limit"
msgstr "Pas de limitation de durée"
#: templates/about.html.ep:27
msgid "on"
msgstr "sur"
#: templates/about.html.ep:36
msgid "spanish translation"
msgstr "traduction espagnole"
#: templates/about.html.ep:28
msgid "webapp developer"
msgstr "développeur de la webapp"

View File

@@ -0,0 +1,50 @@
package Lutim::Plugin::Headers;
use Mojo::Base 'Mojolicious::Plugin';
sub register {
my ($self, $app) = @_;
# Assets Cache headers
$app->plugin('StaticCache' => { even_in_dev => 1 });
# Add CSP Header
if (!defined($app->config('csp')) || (defined($app->config('csp')) && $app->config('csp') ne '')) {
my $directives = {
'default-src' => "'none'",
'script-src' => "'self' 'unsafe-eval'",
'style-src' => "'self' 'unsafe-inline'",
'connect-src' => "'self'",
'img-src' => "'self' data:",
'font-src' => "'self'",
'form-action' => "'self'",
'base-uri' => "'self'",
};
my $frame_ancestors = '';
$frame_ancestors = "'none'" if $app->config('x_frame_options') eq 'DENY';
$frame_ancestors = "'self'" if $app->config('x_frame_options') eq 'SAMEORIGIN';
if ($app->config('x_frame_options') =~ m#^ALLOW-FROM#) {
$frame_ancestors = $app->config('x_frame_options');
$frame_ancestors =~ s#ALLOW-FROM +##;
}
$directives->{'frame-ancestors'} = $frame_ancestors if $frame_ancestors;
$app->plugin('CSPHeader',
csp => $app->config('csp'),
directives => $directives
);
}
# Add other headers
$app->hook(
before_dispatch => sub {
my $c = shift;
$c->res->headers->header('X-Frame-Options' => $app->config('x_frame_options')) if $app->config('x_frame_options');
$c->res->headers->header('X-Content-Type-Options' => $app->config('x_content_type_options')) if $app->config('x_content_type_options');
$c->res->headers->header('X-XSS-Protection' => $app->config('x_xss_protection')) if $app->config('x_xss_protection');
}
);
}
1;

361
lib/Lutim/Plugin/Helpers.pm Normal file
View File

@@ -0,0 +1,361 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Plugin::Helpers;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Util qw(quote);
use Mojo::File;
use Crypt::CBC;
use Data::Entropy qw(entropy_source);
use DateTime;
use Mojo::Util qw(decode);
use ISO::639_1;
use Digest::MD5 'md5';
sub register {
my ($self, $app) = @_;
if ($app->config('dbtype') eq 'postgresql') {
require Mojo::Pg;
$app->plugin('PgURLHelper');
$app->helper(pg => \&_pg);
# Database migration
my $migrations = Mojo::Pg::Migrations->new(pg => $app->pg);
if ($app->mode eq 'development' && $ENV{LUTIM_DEBUG}) {
$migrations->from_file('utilities/migrations/postgresql.sql')->migrate(0)->migrate(3);
} else {
$migrations->from_file('utilities/migrations/postgresql.sql')->migrate(3);
}
} elsif ($app->config('dbtype') eq 'sqlite') {
# SQLite database migration if needed
require Mojo::SQLite;
$app->helper(sqlite => \&_sqlite);
my $sql = Mojo::SQLite->new('sqlite:'.$app->config('db_path'));
my $migrations = $sql->migrations;
if ($app->mode eq 'development' && $ENV{LUTIM_DEBUG}) {
$migrations->from_file('utilities/migrations/sqlite.sql')->migrate(0)->migrate(2);
} else {
$migrations->from_file('utilities/migrations/sqlite.sql')->migrate(2);
}
}
$app->helper(render_file => \&_render_file);
$app->helper(ip => \&_ip);
$app->helper(provisioning => \&_provisioning);
$app->helper(shortener => \&_shortener);
$app->helper(stop_upload => \&_stop_upload);
$app->helper(max_delay => \&_max_delay);
$app->helper(default_delay => \&_default_delay);
$app->helper(is_selected => \&_is_selected);
$app->helper(is_wm_selected => \&_is_wm_selected);
$app->helper(crypt => \&_crypt);
$app->helper(decrypt => \&_decrypt);
$app->helper(delete_image => \&_delete_image);
$app->helper(iso639_native_name => \&_iso639_native_name);
$app->helper(prefix => \&_prefix);
}
sub _pg {
my $c = shift;
state $pg = Mojo::Pg->new($c->app->pg_url($c->app->config('pgdb')));
return $pg;
}
sub _sqlite {
my $c = shift;
state $sqlite = Mojo::SQLite->new('sqlite:'.$c->app->config('db_path'));
return $sqlite;
}
sub _render_file {
my $c = shift;
my ($im_loaded, $img, $dl, $key, $thumb) = @_;
my ($filename, $path, $iv, $mediatype, $no_cache) = ($img->filename, $img->path, $img->iv, $img->mediatype, $img->delete_at_first_view);
my $expires = ($img->delete_at_day) ? $img->delete_at_day : 360;
my $dt = DateTime->from_epoch( epoch => $expires * 86400 + $img->created_at);
$dt->set_time_zone('GMT');
$expires = $dt->strftime("%a, %d %b %Y %H:%M:%S GMT");
$dl = 'attachment' if ($mediatype =~ m/svg/);
$filename = quote($filename);
unless (-f $path && -r $path) {
$c->app->log->error("Cannot read file [$path]. error [$!]");
$c->flash(
msg => $c->l('Unable to find the image: it has been deleted.')
);
return 500;
}
$mediatype =~ s/x-//;
my $headers = Mojo::Headers->new();
if ($no_cache || defined($thumb)) {
$headers->add('Cache-Control' => 'no-cache, no-store, max-age=0, must-revalidate');
} else {
$headers->add('Expires' => $expires);
}
$headers->add('Content-Type' => $mediatype.';name='.$filename);
$headers->add('Content-Disposition' => $dl.';filename='.$filename);
$c->res->content->headers($headers);
my $cache;
if ($c->config('cache_max_size') != 0 || scalar(@{$c->config('memcached_servers')})) {
$cache = $c->chi('lutim_images_cache')->compute($img->short, undef, sub {
if ($key) {
return {
asset => $c->decrypt($key, $path, $iv),
key => $key
};
} else {
return {
asset => Mojo::File->new($path)->slurp,
};
}
});
if ($key && $key ne $cache->{key}) {
my $tmp = $c->decrypt($key, $path, $iv);
$cache->{asset} = $tmp;
$c->chi('lutim_images_cache')->replace(
$img->short,
{
asset => $tmp,
key => $key
},
);
}
} else {
if ($key) {
$cache = {
asset => $c->decrypt($key, $path, $iv),
};
} else {
$cache = {
asset => Mojo::File->new($path)->slurp,
};
}
}
# Extend expiration time
my $asset = Mojo::Asset::Memory->new;
$asset->add_chunk($cache->{asset});
if (defined $thumb && $im_loaded && $mediatype ne 'image/svg+xml' && $mediatype !~ m#image/(x-)?xcf# && $mediatype ne 'image/avif') { # ImageMagick don't work in Debian with svg (for now?)
my $im = Image::Magick->new;
$im->BlobToImage($asset->slurp);
# Create the thumbnail
if ($thumb eq '') {
$im->Resize(geometry => 'x'.$c->config('thumbnail_size'));
} else {
$im->Resize(geometry => $thumb);
}
# Replace the asset with the thumbnail
$asset = Mojo::Asset::Memory->new->add_chunk($im->ImageToBlob());
}
$c->res->content->asset($asset);
$headers->add('Content-Length' => $asset->size);
return $c->rendered(200);
}
sub _ip {
my $c = shift;
my $ip_only = shift || 0;
my $proxy = $c->req->headers->header('X-Forwarded-For');
my $ip = ($proxy) ? $proxy : $c->tx->remote_address;
my $remote_port = (defined($c->req->headers->header('X-Remote-Port'))) ? $c->req->headers->header('X-Remote-Port') : $c->tx->remote_port;
return ($ip_only) ? $ip : "$ip remote port:$remote_port";
}
sub _provisioning {
my $c = shift;
# Create some short patterns for provisioning
my $img = Lutim::DB::Image->new(app => $c->app);
if ($img->count_empty < $c->app->config('provisioning')) {
for (my $i = 0; $i < $c->app->config('provis_step'); $i++) {
my $short;
do {
$short = $c->shortener($c->app->config('length'));
} while ($img->count_short($short) || $short eq 'about' || $short eq 'stats' || $short eq 'd' || $short eq 'm' || $short eq 'gallery' || $short eq 'zip' || $short eq 'infos');
$img->short($short)
->counter(0)
->enabled(1)
->delete_at_first_view(0)
->delete_at_day(0)
->mod_token($c->shortener($c->app->config('token_length')))
->write('provisioning');
$img = Lutim::DB::Image->new(app => $c->app);
}
}
}
sub _shortener {
my $c = shift;
my $length = shift;
my @chars = ('a'..'z','A'..'Z','0'..'9');
my $result = '';
foreach (1..$length) {
$result .= $chars[entropy_source->get_int(scalar(@chars))];
}
return $result;
}
sub _stop_upload {
my $c = shift;
if (-f 'stop-upload' || -f 'stop-upload.manual') {
$c->stash(
stop_upload => $c->l('Uploading is currently disabled, please try later or contact the administrator (%1).', $c->app->config('contact'))
);
return 1;
}
return 0;
}
sub _max_delay {
my $c = shift;
return $c->app->config('max_delay') if ($c->app->config('max_delay') >= 0);
warn "max_delay set to a negative value. Default to 0.";
return 0;
}
sub _default_delay {
my $c = shift;
return $c->app->config('default_delay') if ($c->app->config('default_delay') >= 0);
warn "default_delay set to a negative value. Default to 0.";
return 0;
}
sub _is_selected {
my $c = shift;
my $num = shift;
return ($num == $c->default_delay) ? 'selected="selected"' : '';
}
sub _is_wm_selected {
my $c = shift;
my $wm = shift;
return ($wm eq $c->config('watermark_default')) ? 'selected="selected"' : '';
}
sub _key_from_key {
my $key = shift;
# Key size for Blowfish is 56
my $ks = 56;
my $material = md5($key);
while (length($material) < $ks) {
$material .= md5($material);
}
return substr($material,0,$ks);
}
sub _crypt {
my $c = shift;
my $upload = shift;
my $filename = shift;
my $key = $c->shortener($c->config('crypto_key_length'));
my $iv = $c->shortener(8);
my $cipher = Crypt::CBC->new(
-key => _key_from_key($key),
-cipher => 'Blowfish',
-header => 'none',
-literal_key => 1,
-pbkdf => 'pbkdf2',
-iv => $iv
);
$cipher->start('encrypting');
my $crypt_asset = Mojo::Asset::File->new;
$crypt_asset->add_chunk($cipher->crypt($upload->slurp));
$crypt_asset->add_chunk($cipher->finish);
my $crypt_upload = Mojo::Upload->new;
$crypt_upload->filename($filename);
$crypt_upload->asset($crypt_asset);
return ($crypt_upload, $key, $iv);
}
sub _decrypt {
my $c = shift;
my $key = _key_from_key(shift);
my $file = shift;
my $iv = shift;
$iv = 'dupajasi' unless $iv;
my $cipher = Crypt::CBC->new(
-key => $key,
-cipher => 'Blowfish',
-header => 'none',
-literal_key => 1,
-pbkdf => 'pbkdf2',
-iv => $iv
);
$cipher->start('decrypting');
my $decrypt_asset = Mojo::Asset::File->new;
open(my $f, "<",$file) or die "Unable to read encrypted file: $!";
binmode $f;
while (read($f, my $buffer, 1024)) {
$decrypt_asset->add_chunk($cipher->crypt($buffer));
}
$decrypt_asset->add_chunk($cipher->finish) ;
return $decrypt_asset->slurp;
}
sub _delete_image {
my $c = shift;
my $img = shift;
if ($c->config('cache_max_size') != 0 || scalar(@{$c->config('memcached_servers')})) {
$c->chi('lutim_images_cache')->remove($img->short);
}
unlink $img->path or warn "Could not unlink ".$img->path.": $!";
$img->disable();
}
sub _iso639_native_name {
my $c = shift;
return ucfirst(decode 'UTF-8', get_iso639_1(shift)->{nativeName});
}
sub _prefix {
my $c = shift;
my $prefix = $c->url_for('/')->to_abs;
# Forced domain
$prefix->host($c->config('fixed_domain')) if (defined($c->config('fixed_domain')) && $c->config('fixed_domain') ne '');
# Hack for prefix (subdir) handling
$prefix .= '/' unless ($prefix =~ m#/$#);
return $prefix;
}
1;

38
lib/Lutim/Plugin/Lang.pm Normal file
View File

@@ -0,0 +1,38 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lutim::Plugin::Lang;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Collection;
use Mojo::File;
sub register {
my ($self, $app) = @_;
$app->helper(available_langs => \&_available_langs);
$app->hook(
before_dispatch => sub {
my $c = shift;
$c->languages($c->cookie('lutim_lang')) if $c->cookie('lutim_lang');
}
);
}
sub _available_langs {
my $c = shift;
state $langs = Mojo::Collection->new(
glob($c->app->home->rel_file('themes/'.$c->config('theme').'/lib/Lutim/I18N/*po')),
glob($c->app->home->rel_file('themes/default/lib/Lutim/I18N/*po'))
)->map(
sub {
Mojo::File->new($_)->basename('.po');
}
)->uniq->sort(
sub {
$c->l($a) cmp $c->l($b)
}
)->to_array;
}
1;

View File

@@ -1,46 +0,0 @@
package LutimModel;
use Mojolicious;
use FindBin qw($Bin);
use File::Spec qw(catfile);
BEGIN {
my $m = Mojolicious->new;
our $config = $m->plugin('Config' =>
{
file => File::Spec->catfile($Bin, '..' ,'lutim.conf'),
default => {
db_path => 'lutim.db'
}
}
);
}
# Create database
use ORLite {
file => $config->{db_path},
unicode => 1,
create => sub {
my $dbh = shift;
$dbh->do(
'CREATE TABLE lutim (
short TEXT PRIMARY KEY,
path TEXT,
footprint TEXT,
enabled INTEGER,
mediatype TEXT,
filename TEXT,
counter INTEGER,
delete_at_first_view INTEGER,
delete_at_day INTEGER,
created_at INTEGER,
created_by TEXT,
last_access_at INTEGER,
mod_token TEXT,
width INTEGER,
height INTEGER)'
);
return 1;
}
};
1;

View File

@@ -1,7 +1,10 @@
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Mounter;
use Mojo::Base 'Mojolicious';
use Mojo::File;
use FindBin qw($Bin);
use File::Spec qw(catfile);
use Lutim::DefaultConfig qw($default_config);
# This method will run once at server start
sub startup {
@@ -9,19 +12,39 @@ sub startup {
push @{$self->commands->namespaces}, 'Lutim::Command';
my $config = $self->plugin('Config' =>
{
file => File::Spec->catfile($Bin, '..' ,'lutim.conf'),
default => {
prefix => '/'
}
my $cfile = Mojo::File->new($Bin, '..' , 'lutim.conf');
if (defined $ENV{MOJO_CONFIG}) {
$cfile = Mojo::File->new($ENV{MOJO_CONFIG});
unless (-e $cfile->to_abs) {
$cfile = Mojo::File->new($Bin, '..', $ENV{MOJO_CONFIG});
}
);
}
my $config = $self->plugin('Config', {
file => $cfile,
default => $default_config
});
$config->{prefix} = $config->{url_sub_dir} if (defined($config->{url_sub_dir}) && $config->{prefix} eq '/');
$self->app->log->warn('"url_sub_dir" configuration option is deprecated. Use "prefix" instead. "url_sub_dir" will be removed in the future') if (defined($config->{url_sub_dir}));
# Themes handling
shift @{$self->static->paths};
if ($config->{theme} ne 'default') {
my $theme = $self->home->rel_file('themes/'.$config->{theme});
push @{$self->static->paths}, $theme.'/public' if -d $theme.'/public';
}
push @{$self->static->paths}, $self->home->rel_file('themes/default/public');
# Static assets gzipping
$self->plugin('GzipStatic');
# Headers
$self->plugin('Lutim::Plugin::Headers');
# Helpers
$self->plugin('Lutim::Plugin::Helpers');
$self->plugin('Mount' => {$config->{prefix} => File::Spec->catfile($Bin, '..', 'script', 'application')});
}

View File

@@ -24,6 +24,10 @@
# mandatory
secrets => ['fdjsofjoihrei'],
# choose a theme. See the available themes in `themes` directory
# optional, default is 'default'
#theme => 'default',
# length of the images random URL
# optional, default is 8
#length => 8,
@@ -45,11 +49,6 @@
# optional, default is 5
#anti_flood_delay => 5,
# twitter account which will appear on twitter cards
# see https://dev.twitter.com/docs/cards/validation/validator to register your Lutim instance on twitter
# optional, default is @framasky
#tweet_card_via => '@framasky',
# max image size, in octets
# you can write it 10*1024*1024
# optional, default is 10485760
@@ -61,7 +60,7 @@
#piwik_img => 'https://piwik.example.org/piwik.php?idsite=1&amp;rec=1',
# if you want to include something in the right of the screen, put it here
# here's an exemple to put the logo of your hoster
# here's an example to put the logo of your hoster
# optional, no default
#hosted_by => 'My super hoster <img src="http://hoster.example.com" alt="Hoster logo">',
@@ -85,6 +84,10 @@
# optional, default is 0 (no limit)
#default_delay => 0,
# comma-separated values proposed for delays
# optional, default is '0,1,7,30,365'
#proposed_delays => '0,1,7,30,365',
# number of days after which the images will be deleted, even if they were uploaded with "no delay" (or value superior to max_delay)
# a warning message will be displayed on homepage
# optional, default is 0 (no limit)
@@ -94,6 +97,29 @@
# optional, default is 0
#always_encrypt => 0,
# you can allow to use a watermark on the uploaded images (or enforce its use)
# define a path to the watermark image (provide an image with alpha channel)
# you can define the path relative to lutim directory or set an absolute path
# to disable the usage of a watermark, leave it blank or commented
# optional, no default
#watermark_path => '',
# the watermark can be a tiling one or a single one
# when using a small one, you can choose where to place it
# valid values are 'Center', 'North', 'NorthEast', 'East', 'SouthEast', 'South', 'SouthWest', 'West' and 'NorthWest' (case insensitive)
# optional, default is 'SouthEast'
#watermark_placement => 'SouthEast',
# choose which watermark (tiling, single or none) should be used by default
# valid values are 'tiling', 'single' or 'none' (case insensitive)
# optional, default is 'none'
#watermark_default => 'none',
# choose which watermark (tiling, single or none) should be enforced (users will always have a watermark and wont be able to disable it)
# valid values are 'tiling', 'single' or 'none' (case insensitive)
# optional, default is 'none'
#watermark_enforce => 'none',
# length of the image's delete token
# optional, default is 24
#token_length => 24,
@@ -104,12 +130,94 @@
# optional, defaut is /
#prefix => '/',
# if set to 1, Lutim will try to prevent its use without using the web interface
# optional, default is 0
#disable_api => 0,
# Define a path to the upload directory, where the uploaded images will be stored.
# You can define it relative to lutim directory or set an absolute path.
# The path is stored in database for each uploaded file, so youll need to do some
# SQL commands if you move the images in an other directory (if you keep the old directory
# where it was, you have nothing to do).
# Remember that it has to be in a directory writable by Lutim user
# optional, default is 'files'
#upload_dir => 'files',
# choose what database you want to use
# valid choices are sqlite and postgresql (all lowercase)
# optional, default is sqlite
#dbtype => 'sqlite',
# SQLite ONLY - only used if dbtype is set to sqlite
# define a path to the SQLite database
# you can define it relative to lutim directory or set an absolute path
# remember that it has to be in a directory writable by Lutim user
# optional, default is lutim.db
#db_path => 'lutim.db',
# PostgreSQL ONLY - only used if dbtype is set to postgresql
# these are the credentials to access the PostgreSQL database
# mandatory if you choosed postgresql as dbtype
#pgdb => {
# database => 'lutim',
# host => 'localhost',
# #user => 'DBUSER',
# #pwd => 'DBPASSWORD'
#},
# use Minion instead of directly increase counters
# need to launch a minion worker service if enabled
# optional, Minion is disabled by default
#minion => {
# enabled => 0,
# # Which Minion backend to use?
# # valid values are sqlite and postgresql (all lowercase)
# # mandatory if Minion is enabled, default is sqlite
# dbtype => 'sqlite',
# # SQLite ONLY - only used if if you choose sqlite as Minion backend, define the path to the minion database
# # you can define it relative to lutim directory or set an absolute path
# # remember that it has to be in a directory writable by Lutim user
# # optional, default is minion.db
# db_path => 'minion.db',
# # PostgreSQL ONLY - only used if you choose postgresql as Minion backend
# # these are the credentials to access the Minion's PostgreSQL database
# # mandatory if you choosed postgresql as Minion backend, no default
# pgdb => {
# database => 'lutim_minion',
# host => 'localhost',
# #user => 'DBUSER',
# #pwd => 'DBPASSWORD'
# }
#},
# set `ldap` if you want that only authenticated users can shorten URLs
# please note that everybody can still use shortend URLs
# optional, no default
#ldap => {
# uri => 'ldaps://ldap.example.org', # server URI
# user_tree => 'ou=users,dc=example,dc=org', # search base DN
# bind_dn => 'uid=ldap_user,ou=users,dc=example,dc=org', # search bind DN
# bind_pwd => 'secr3t', # search bind password
# user_attr => 'uid', # user attribute (uid, mail, sAMAccountName, etc.)
# user_filter => '(!(uid=ldap_user))', # user filter (to exclude some users, etc.)
#},
# set `htpasswd` if you want to use an htpasswd file instead of ldap
# create the file with `htpasswd -c lutim.passwd user`, update it with `htpasswd lutim.passwd user2`
# make sure that lutim can read the file!
# optional, no default
#htpasswd => 'lutim.passwd',
# if you've set ldap or htpasswd above, the session will last `session_duration` seconds before
# the user needs to reauthenticate
# optional, default is 3600
#session_duration => 3600,
# disable counters of images
# set to 1 to disable counters
# optional, counters are enabled by default
#disable_img_stats => 0,
# define the height of the thumbnails generated at users' will
# this is not the height of the thumbnails send after upload,
# we're talking about thumbnails generated when someone asked for
@@ -118,6 +226,74 @@
# optional, default is 100 (pixels)
#thumbnail_size => 100,
# maximum number of files that can be downloaded as a single zip archive
# if too many files are asked, it results a timeout, so Lutim split the zip URL
# in multiple URLs, each with max_file_size images.
# timeout behavior depends heavily on your server ressources (CPU) and if images
# are encrypted
# optional, default is 15
#max_files_in_zip => 15,
# maximum size (in MB) of memory allowed for the image cache
# Lutim has a built-in memory-based image cache to accelerate responses to often-viewed images.
# This setting makes the cache remove oldest viewed image if the cache size is over it.
# WARNING: a cache is created for each hypnotoad worker, which by default is twice the number of
# CPUs you have. See http://mojolicious.org/perldoc/Mojo/Server/Hypnotoad#workers for details
# So, if you have 4 workers and set cache_max_size to 100, the real maximum size of RAM used for
# cache is 400MB.
# If set to 0, the cache is disabled
# optional, default is 0
#cache_max_size => 0,
# array of memcached servers to cache URL in order to accelerate responses to often-viewed URL.
# If set to [], the use of memcached is disabled.
# If you use memcached, the internal cache (see cache_max_size option above) will not be used.
# Please see https://framagit.org/fiat-tux/hat-softwares/lutim/-/wikis/memcached to know how to configure your memcached
# servers.
# exemple of valid value: ['127.0.0.1:11211']
# optional, default is []
#memcached_servers => [],
# enable or disable Lutim built-in logs
# set to 1 to disable logs
# optional, default is 0
#quiet_logs => 0,
# Content-Security-Policy header that will be sent by Lutim
# Set to '' to disable CSP header
# https://content-security-policy.com/ provides a good documentation about CSP.
# https://report-uri.com/home/generate provides a tool to generate a CSP header.
# optional, default is "base-uri 'self'; connect-src 'self'; default-src 'none'; font-src 'self'; form-action 'self'; img-src 'self' data:; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
# NB: unsafe-inline for script-src and style-src are here only because morris,
# the graph library used in the stats page requires it
# the default value is good for `default` theme
#csp => "base-uri 'self'; connect-src 'self'; default-src 'none'; font-src 'self'; form-action 'self'; img-src 'self' data:; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'",
# X-Frame-Options header that will be sent by Lutim
# Valid values are: 'DENY', 'SAMEORIGIN', 'ALLOW-FROM https://example.com/'
# Set to '' to disable X-Frame-Options header
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
# Please note that this will add a "frame-ancestors" directive to the CSP header (see above) accordingly
# to the chosen setting (See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors)
# optional, default is 'DENY'
#x_frame_options => 'DENY',
# X-Content-Type-Options that will be sent by Lutim
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
# Set to '' to disable X-Content-Type-Options header
# optional, default is 'nosniff'
#x_content_type_options => 'nosniff',
# X-XSS-Protection that will be sent by Lutim
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
# Set to '' to disable X-XSS-Protection header
# optional, default is '1; mode=block'
#x_xss_protection => '1; mode=block',
# if set, the uploaded images will use this domain
# optional
#fixed_domain => 'example.org',
##########################
# Lutim cron jobs settings
##########################

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
@font-face {
font-family: 'Henny_Penny';
font-style: normal;
font-weight: 400;
src: local('Henny Penny'), local('HennyPenny-Regular'), url(../font/hennypenny.ttf) format('truetype');
}

View File

@@ -1,62 +0,0 @@
@media (max-width: 767px) {
body {
padding-top: 5px;
padding-bottom: 5px;
}
}
@media (min-width: 768px) {
body {
padding-top: 40px;
padding-bottom: 40px;
}
}
.container {
padding: 15px;
margin: 0 auto;
}
.jsonly {
display: none;
}
.thumbnail {
margin-right: 8px;
}
.hennypenny {
font-family: 'Henny_Penny', cursive;
font-size: 42px;
}
.logo {
margin-right: 10px;
}
label.always-encrypt {
display: none;
}
.link_nocol,
.link_nocol:hover{
color: #000000;
text-decoration: none;
}
#install-app img {
height: 22px;
}
#install-app {
display: none;
}
.form-control[readonly] {
background-color: #FFFFFF;
}
.alert .form-group {
margin-bottom: 5px;
}
#copy-all {
margin-bottom: 8px;
}
#gallery-url {
margin-bottom: 10px;
}

View File

@@ -1,2 +0,0 @@
.morris-hover{position:absolute;z-index:1000;}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255, 255, 255, 0.8);border:solid 2px rgba(230, 230, 230, 0.8);font-family:sans-serif;font-size:12px;text-align:center;}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0;}
.morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0;}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 909 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

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