Browse Source

Changes

Every changes for lolisareinthe.club.
Kiru 10 months ago
parent
commit
f58aee93ef
88 changed files with 2852 additions and 2277 deletions
  1. 14
    5
      .gitignore
  2. 0
    1
      .mailmap
  3. 0
    16
      .travis.yml
  4. 0
    319
      COPYING.CC-BY-3.0
  5. 19
    0
      Dockerfile
  6. 15
    15
      LICENSE
  7. 0
    97
      Makefile
  8. 54
    164
      README.md
  9. 179
    0
      controllers/albumsController.js
  10. 91
    0
      controllers/authController.js
  11. 34
    0
      controllers/tokenController.js
  12. 302
    0
      controllers/uploadController.js
  13. 67
    0
      controllers/utilsController.js
  14. 53
    0
      database/db.js
  15. 13
    0
      database/migration.js
  16. 0
    26
      dist.json
  17. 58
    0
      lolisafe.js
  18. 0
    20
      mysql_schema.sql
  19. 38
    20
      package.json
  20. 60
    0
      pages/album.html
  21. 87
    0
      pages/auth.html
  22. 100
    0
      pages/dashboard.html
  23. 47
    0
      pages/error/404.html
  24. 47
    0
      pages/error/500.html
  25. 108
    0
      pages/faq.html
  26. 93
    0
      pages/home.html
  27. 210
    0
      public/css/style.css
  28. BIN
      public/images/fb_share.png
  29. BIN
      public/images/icons/android-chrome-192x192.png
  30. BIN
      public/images/icons/android-chrome-384x384.png
  31. BIN
      public/images/icons/apple-touch-icon.png
  32. 9
    0
      public/images/icons/browserconfig.xml
  33. BIN
      public/images/icons/favicon-16x16.png
  34. BIN
      public/images/icons/favicon-32x32.png
  35. BIN
      public/images/icons/favicon.ico
  36. 18
    0
      public/images/icons/manifest.json
  37. BIN
      public/images/icons/mstile-150x150.png
  38. 47
    0
      public/images/icons/safari-pinned-tab.svg
  39. BIN
      public/images/logo.png
  40. BIN
      public/images/logo_big.png
  41. BIN
      public/images/logo_smol.png
  42. BIN
      public/images/logo_square.png
  43. 56
    0
      public/js/auth.js
  44. 624
    0
      public/js/dashboard.js
  45. 212
    0
      public/js/home.js
  46. 30
    0
      real-ip-from-cf
  47. 56
    0
      routes/album.js
  48. 37
    0
      routes/api.js
  49. 0
    16
      sqlite_schema.sql
  50. 0
    397
      static/css/pomf.css
  51. BIN
      static/img/10.png
  52. BIN
      static/img/2.png
  53. BIN
      static/img/3.png
  54. BIN
      static/img/4.png
  55. BIN
      static/img/5.png
  56. BIN
      static/img/6.png
  57. BIN
      static/img/7.png
  58. BIN
      static/img/8.png
  59. BIN
      static/img/9.png
  60. BIN
      static/img/bg.png
  61. BIN
      static/img/bitcoin.png
  62. BIN
      static/img/donate.png
  63. BIN
      static/img/favicon.ico
  64. BIN
      static/img/flattr.png
  65. BIN
      static/img/glyphicons-512-copy.png
  66. BIN
      static/img/paypal.png
  67. 0
    266
      static/js/app.js
  68. 0
    3
      static/php/.gitignore
  69. 0
    3
      static/php/.gitmodules
  70. 0
    6
      static/php/.mailmap
  71. 0
    16
      static/php/.travis.yml
  72. 0
    21
      static/php/LICENSE
  73. 0
    241
      static/php/classes/Response.class.php
  74. 0
    63
      static/php/classes/UploadException.class.php
  75. 0
    32
      static/php/classes/UploadedFile.class.php
  76. 0
    19
      static/php/grill.php
  77. 0
    30
      static/php/includes/database.inc.php
  78. 0
    118
      static/php/includes/settings.inc.php.example
  79. 0
    236
      static/php/upload.php
  80. 0
    3
      templates/banners.swig
  81. 0
    23
      templates/banners/donations.swig
  82. 0
    27
      templates/faq.swig
  83. 0
    11
      templates/index.swig
  84. 0
    17
      templates/layout.swig
  85. 0
    9
      templates/nav.swig
  86. 0
    31
      templates/tools.swig
  87. 0
    6
      templates/upload_form.swig
  88. 74
    0
      views/album.handlebars

+ 14
- 5
.gitignore View File

@@ -1,5 +1,14 @@
1
-node_modules
2
-dist
3
-dist.zip
4
-build
5
-static/php/includes/settings.inc.php
1
+.DS_Store
2
+!.gitkeep
3
+node_modules/
4
+uploads/
5
+logs/
6
+database/db
7
+config.js
8
+start.json
9
+npm-debug.log
10
+pages/custom/**
11
+migrate.js
12
+yarn.lock
13
+package-lock.json
14
+.vscode/

+ 0
- 1
.mailmap View File

@@ -1 +0,0 @@
1
-Kiru <kiru@waifu.club>

+ 0
- 16
.travis.yml View File

@@ -1,16 +0,0 @@
1
-language: php
2
-php:
3
-  - '5.4'
4
-  - '5.5'
5
-  - '5.6'
6
-  - '7.0'
7
-  - hhvm
8
-
9
-install:
10
-- source ~/.nvm/nvm.sh 
11
-- nvm ls-remote
12
-- nvm install stable
13
-- nvm use stable
14
-script:
15
-- make 
16
-

+ 0
- 319
COPYING.CC-BY-3.0 View File

@@ -1,319 +0,0 @@
1
-Creative Commons Legal Code
2
-
3
-Attribution 3.0 Unported
4
-
5
-    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
-    LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
7
-    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
-    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
-    REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
10
-    DAMAGES RESULTING FROM ITS USE.
11
-
12
-License
13
-
14
-THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
15
-COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
16
-COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
17
-AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
18
-
19
-BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
20
-TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
21
-BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
22
-CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
23
-CONDITIONS.
24
-
25
-1. Definitions
26
-
27
- a. "Adaptation" means a work based upon the Work, or upon the Work and
28
-    other pre-existing works, such as a translation, adaptation,
29
-    derivative work, arrangement of music or other alterations of a
30
-    literary or artistic work, or phonogram or performance and includes
31
-    cinematographic adaptations or any other form in which the Work may be
32
-    recast, transformed, or adapted including in any form recognizably
33
-    derived from the original, except that a work that constitutes a
34
-    Collection will not be considered an Adaptation for the purpose of
35
-    this License. For the avoidance of doubt, where the Work is a musical
36
-    work, performance or phonogram, the synchronization of the Work in
37
-    timed-relation with a moving image ("synching") will be considered an
38
-    Adaptation for the purpose of this License.
39
- b. "Collection" means a collection of literary or artistic works, such as
40
-    encyclopedias and anthologies, or performances, phonograms or
41
-    broadcasts, or other works or subject matter other than works listed
42
-    in Section 1(f) below, which, by reason of the selection and
43
-    arrangement of their contents, constitute intellectual creations, in
44
-    which the Work is included in its entirety in unmodified form along
45
-    with one or more other contributions, each constituting separate and
46
-    independent works in themselves, which together are assembled into a
47
-    collective whole. A work that constitutes a Collection will not be
48
-    considered an Adaptation (as defined above) for the purposes of this
49
-    License.
50
- c. "Distribute" means to make available to the public the original and
51
-    copies of the Work or Adaptation, as appropriate, through sale or
52
-    other transfer of ownership.
53
- d. "Licensor" means the individual, individuals, entity or entities that
54
-    offer(s) the Work under the terms of this License.
55
- e. "Original Author" means, in the case of a literary or artistic work,
56
-    the individual, individuals, entity or entities who created the Work
57
-    or if no individual or entity can be identified, the publisher; and in
58
-    addition (i) in the case of a performance the actors, singers,
59
-    musicians, dancers, and other persons who act, sing, deliver, declaim,
60
-    play in, interpret or otherwise perform literary or artistic works or
61
-    expressions of folklore; (ii) in the case of a phonogram the producer
62
-    being the person or legal entity who first fixes the sounds of a
63
-    performance or other sounds; and, (iii) in the case of broadcasts, the
64
-    organization that transmits the broadcast.
65
- f. "Work" means the literary and/or artistic work offered under the terms
66
-    of this License including without limitation any production in the
67
-    literary, scientific and artistic domain, whatever may be the mode or
68
-    form of its expression including digital form, such as a book,
69
-    pamphlet and other writing; a lecture, address, sermon or other work
70
-    of the same nature; a dramatic or dramatico-musical work; a
71
-    choreographic work or entertainment in dumb show; a musical
72
-    composition with or without words; a cinematographic work to which are
73
-    assimilated works expressed by a process analogous to cinematography;
74
-    a work of drawing, painting, architecture, sculpture, engraving or
75
-    lithography; a photographic work to which are assimilated works
76
-    expressed by a process analogous to photography; a work of applied
77
-    art; an illustration, map, plan, sketch or three-dimensional work
78
-    relative to geography, topography, architecture or science; a
79
-    performance; a broadcast; a phonogram; a compilation of data to the
80
-    extent it is protected as a copyrightable work; or a work performed by
81
-    a variety or circus performer to the extent it is not otherwise
82
-    considered a literary or artistic work.
83
- g. "You" means an individual or entity exercising rights under this
84
-    License who has not previously violated the terms of this License with
85
-    respect to the Work, or who has received express permission from the
86
-    Licensor to exercise rights under this License despite a previous
87
-    violation.
88
- h. "Publicly Perform" means to perform public recitations of the Work and
89
-    to communicate to the public those public recitations, by any means or
90
-    process, including by wire or wireless means or public digital
91
-    performances; to make available to the public Works in such a way that
92
-    members of the public may access these Works from a place and at a
93
-    place individually chosen by them; to perform the Work to the public
94
-    by any means or process and the communication to the public of the
95
-    performances of the Work, including by public digital performance; to
96
-    broadcast and rebroadcast the Work by any means including signs,
97
-    sounds or images.
98
- i. "Reproduce" means to make copies of the Work by any means including
99
-    without limitation by sound or visual recordings and the right of
100
-    fixation and reproducing fixations of the Work, including storage of a
101
-    protected performance or phonogram in digital form or other electronic
102
-    medium.
103
-
104
-2. Fair Dealing Rights. Nothing in this License is intended to reduce,
105
-limit, or restrict any uses free from copyright or rights arising from
106
-limitations or exceptions that are provided for in connection with the
107
-copyright protection under copyright law or other applicable laws.
108
-
109
-3. License Grant. Subject to the terms and conditions of this License,
110
-Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
111
-perpetual (for the duration of the applicable copyright) license to
112
-exercise the rights in the Work as stated below:
113
-
114
- a. to Reproduce the Work, to incorporate the Work into one or more
115
-    Collections, and to Reproduce the Work as incorporated in the
116
-    Collections;
117
- b. to create and Reproduce Adaptations provided that any such Adaptation,
118
-    including any translation in any medium, takes reasonable steps to
119
-    clearly label, demarcate or otherwise identify that changes were made
120
-    to the original Work. For example, a translation could be marked "The
121
-    original work was translated from English to Spanish," or a
122
-    modification could indicate "The original work has been modified.";
123
- c. to Distribute and Publicly Perform the Work including as incorporated
124
-    in Collections; and,
125
- d. to Distribute and Publicly Perform Adaptations.
126
- e. For the avoidance of doubt:
127
-
128
-     i. Non-waivable Compulsory License Schemes. In those jurisdictions in
129
-        which the right to collect royalties through any statutory or
130
-        compulsory licensing scheme cannot be waived, the Licensor
131
-        reserves the exclusive right to collect such royalties for any
132
-        exercise by You of the rights granted under this License;
133
-    ii. Waivable Compulsory License Schemes. In those jurisdictions in
134
-        which the right to collect royalties through any statutory or
135
-        compulsory licensing scheme can be waived, the Licensor waives the
136
-        exclusive right to collect such royalties for any exercise by You
137
-        of the rights granted under this License; and,
138
-   iii. Voluntary License Schemes. The Licensor waives the right to
139
-        collect royalties, whether individually or, in the event that the
140
-        Licensor is a member of a collecting society that administers
141
-        voluntary licensing schemes, via that society, from any exercise
142
-        by You of the rights granted under this License.
143
-
144
-The above rights may be exercised in all media and formats whether now
145
-known or hereafter devised. The above rights include the right to make
146
-such modifications as are technically necessary to exercise the rights in
147
-other media and formats. Subject to Section 8(f), all rights not expressly
148
-granted by Licensor are hereby reserved.
149
-
150
-4. Restrictions. The license granted in Section 3 above is expressly made
151
-subject to and limited by the following restrictions:
152
-
153
- a. You may Distribute or Publicly Perform the Work only under the terms
154
-    of this License. You must include a copy of, or the Uniform Resource
155
-    Identifier (URI) for, this License with every copy of the Work You
156
-    Distribute or Publicly Perform. You may not offer or impose any terms
157
-    on the Work that restrict the terms of this License or the ability of
158
-    the recipient of the Work to exercise the rights granted to that
159
-    recipient under the terms of the License. You may not sublicense the
160
-    Work. You must keep intact all notices that refer to this License and
161
-    to the disclaimer of warranties with every copy of the Work You
162
-    Distribute or Publicly Perform. When You Distribute or Publicly
163
-    Perform the Work, You may not impose any effective technological
164
-    measures on the Work that restrict the ability of a recipient of the
165
-    Work from You to exercise the rights granted to that recipient under
166
-    the terms of the License. This Section 4(a) applies to the Work as
167
-    incorporated in a Collection, but this does not require the Collection
168
-    apart from the Work itself to be made subject to the terms of this
169
-    License. If You create a Collection, upon notice from any Licensor You
170
-    must, to the extent practicable, remove from the Collection any credit
171
-    as required by Section 4(b), as requested. If You create an
172
-    Adaptation, upon notice from any Licensor You must, to the extent
173
-    practicable, remove from the Adaptation any credit as required by
174
-    Section 4(b), as requested.
175
- b. If You Distribute, or Publicly Perform the Work or any Adaptations or
176
-    Collections, You must, unless a request has been made pursuant to
177
-    Section 4(a), keep intact all copyright notices for the Work and
178
-    provide, reasonable to the medium or means You are utilizing: (i) the
179
-    name of the Original Author (or pseudonym, if applicable) if supplied,
180
-    and/or if the Original Author and/or Licensor designate another party
181
-    or parties (e.g., a sponsor institute, publishing entity, journal) for
182
-    attribution ("Attribution Parties") in Licensor's copyright notice,
183
-    terms of service or by other reasonable means, the name of such party
184
-    or parties; (ii) the title of the Work if supplied; (iii) to the
185
-    extent reasonably practicable, the URI, if any, that Licensor
186
-    specifies to be associated with the Work, unless such URI does not
187
-    refer to the copyright notice or licensing information for the Work;
188
-    and (iv) , consistent with Section 3(b), in the case of an Adaptation,
189
-    a credit identifying the use of the Work in the Adaptation (e.g.,
190
-    "French translation of the Work by Original Author," or "Screenplay
191
-    based on original Work by Original Author"). The credit required by
192
-    this Section 4 (b) may be implemented in any reasonable manner;
193
-    provided, however, that in the case of a Adaptation or Collection, at
194
-    a minimum such credit will appear, if a credit for all contributing
195
-    authors of the Adaptation or Collection appears, then as part of these
196
-    credits and in a manner at least as prominent as the credits for the
197
-    other contributing authors. For the avoidance of doubt, You may only
198
-    use the credit required by this Section for the purpose of attribution
199
-    in the manner set out above and, by exercising Your rights under this
200
-    License, You may not implicitly or explicitly assert or imply any
201
-    connection with, sponsorship or endorsement by the Original Author,
202
-    Licensor and/or Attribution Parties, as appropriate, of You or Your
203
-    use of the Work, without the separate, express prior written
204
-    permission of the Original Author, Licensor and/or Attribution
205
-    Parties.
206
- c. Except as otherwise agreed in writing by the Licensor or as may be
207
-    otherwise permitted by applicable law, if You Reproduce, Distribute or
208
-    Publicly Perform the Work either by itself or as part of any
209
-    Adaptations or Collections, You must not distort, mutilate, modify or
210
-    take other derogatory action in relation to the Work which would be
211
-    prejudicial to the Original Author's honor or reputation. Licensor
212
-    agrees that in those jurisdictions (e.g. Japan), in which any exercise
213
-    of the right granted in Section 3(b) of this License (the right to
214
-    make Adaptations) would be deemed to be a distortion, mutilation,
215
-    modification or other derogatory action prejudicial to the Original
216
-    Author's honor and reputation, the Licensor will waive or not assert,
217
-    as appropriate, this Section, to the fullest extent permitted by the
218
-    applicable national law, to enable You to reasonably exercise Your
219
-    right under Section 3(b) of this License (right to make Adaptations)
220
-    but not otherwise.
221
-
222
-5. Representations, Warranties and Disclaimer
223
-
224
-UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
225
-OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
226
-KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
227
-INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
228
-FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
229
-LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
230
-WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
231
-OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
232
-
233
-6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
234
-LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
235
-ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
236
-ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
237
-BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
238
-
239
-7. Termination
240
-
241
- a. This License and the rights granted hereunder will terminate
242
-    automatically upon any breach by You of the terms of this License.
243
-    Individuals or entities who have received Adaptations or Collections
244
-    from You under this License, however, will not have their licenses
245
-    terminated provided such individuals or entities remain in full
246
-    compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
247
-    survive any termination of this License.
248
- b. Subject to the above terms and conditions, the license granted here is
249
-    perpetual (for the duration of the applicable copyright in the Work).
250
-    Notwithstanding the above, Licensor reserves the right to release the
251
-    Work under different license terms or to stop distributing the Work at
252
-    any time; provided, however that any such election will not serve to
253
-    withdraw this License (or any other license that has been, or is
254
-    required to be, granted under the terms of this License), and this
255
-    License will continue in full force and effect unless terminated as
256
-    stated above.
257
-
258
-8. Miscellaneous
259
-
260
- a. Each time You Distribute or Publicly Perform the Work or a Collection,
261
-    the Licensor offers to the recipient a license to the Work on the same
262
-    terms and conditions as the license granted to You under this License.
263
- b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
264
-    offers to the recipient a license to the original Work on the same
265
-    terms and conditions as the license granted to You under this License.
266
- c. If any provision of this License is invalid or unenforceable under
267
-    applicable law, it shall not affect the validity or enforceability of
268
-    the remainder of the terms of this License, and without further action
269
-    by the parties to this agreement, such provision shall be reformed to
270
-    the minimum extent necessary to make such provision valid and
271
-    enforceable.
272
- d. No term or provision of this License shall be deemed waived and no
273
-    breach consented to unless such waiver or consent shall be in writing
274
-    and signed by the party to be charged with such waiver or consent.
275
- e. This License constitutes the entire agreement between the parties with
276
-    respect to the Work licensed here. There are no understandings,
277
-    agreements or representations with respect to the Work not specified
278
-    here. Licensor shall not be bound by any additional provisions that
279
-    may appear in any communication from You. This License may not be
280
-    modified without the mutual written agreement of the Licensor and You.
281
- f. The rights granted under, and the subject matter referenced, in this
282
-    License were drafted utilizing the terminology of the Berne Convention
283
-    for the Protection of Literary and Artistic Works (as amended on
284
-    September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
285
-    Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
286
-    and the Universal Copyright Convention (as revised on July 24, 1971).
287
-    These rights and subject matter take effect in the relevant
288
-    jurisdiction in which the License terms are sought to be enforced
289
-    according to the corresponding provisions of the implementation of
290
-    those treaty provisions in the applicable national law. If the
291
-    standard suite of rights granted under applicable copyright law
292
-    includes additional rights not granted under this License, such
293
-    additional rights are deemed to be included in the License; this
294
-    License is not intended to restrict the license of any rights under
295
-    applicable law.
296
-
297
-
298
-Creative Commons Notice
299
-
300
-    Creative Commons is not a party to this License, and makes no warranty
301
-    whatsoever in connection with the Work. Creative Commons will not be
302
-    liable to You or any party on any legal theory for any damages
303
-    whatsoever, including without limitation any general, special,
304
-    incidental or consequential damages arising in connection to this
305
-    license. Notwithstanding the foregoing two (2) sentences, if Creative
306
-    Commons has expressly identified itself as the Licensor hereunder, it
307
-    shall have all rights and obligations of Licensor.
308
-
309
-    Except for the limited purpose of indicating to the public that the
310
-    Work is licensed under the CCPL, Creative Commons does not authorize
311
-    the use by either party of the trademark "Creative Commons" or any
312
-    related trademark or logo of Creative Commons without the prior
313
-    written consent of Creative Commons. Any permitted use will be in
314
-    compliance with Creative Commons' then-current trademark usage
315
-    guidelines, as may be published on its website or otherwise made
316
-    available upon request from time to time. For the avoidance of doubt,
317
-    this trademark restriction does not form part of this License.
318
-
319
-    Creative Commons may be contacted at https://creativecommons.org/.<Paste>

+ 19
- 0
Dockerfile View File

@@ -0,0 +1,19 @@
1
+FROM node:9
2
+
3
+LABEL name "lolisareinthe.club"
4
+LABEL version "3.0.0"
5
+LABEL maintainer "iCrawl <icrawltogo@gmail.com>"
6
+
7
+WORKDIR /usr/src/lolisareinthe.club
8
+
9
+COPY package.json yarn.lock ./
10
+
11
+RUN sh -c 'echo "deb http://www.deb-multimedia.org jessie main" >> /etc/apt/sources.list' \
12
+&& apt-key adv --keyserver keyring.debian.org --recv-keys 5C808C2B65558117 \
13
+&& apt-get update \
14
+&& apt-get install -y ffmpeg graphicsmagick \
15
+&& yarn install
16
+
17
+COPY . .
18
+
19
+CMD ["node", "lolisareinthe.club.js"]

+ 15
- 15
LICENSE View File

@@ -1,21 +1,21 @@
1
-Copyright (c) 2013, 2014, 2015 Eric Johansson <neku@pomf.se>
2
-Copyright (c) 2013, 2014 Peter Lejeck <peter.lejeck@gmail.com>
3
-Copyright (c) 2015 cenci0 <alchimist94@gmail.com>
4
-Copyright (c) 2015, 2016, 2017 the Pantsu.cat developers <hostmaster@pantsu.cat> <hostmaster@pantsu.cat>
1
+MIT License
5 2
 
6
-Permission is hereby granted, free of charge, to any person obtaining a copy of
7
-this software and associated documentation files (the "Software"), to deal in
8
-the Software without restriction, including without limitation the rights to
9
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
-the Software, and to permit persons to whom the Software is furnished to do so,
11
-subject to the following conditions:
3
+Copyright (c) 2017 Pitu
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
12 11
 
13 12
 The above copyright notice and this permission notice shall be included in all
14 13
 copies or substantial portions of the Software.
15 14
 
16 15
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 0
- 97
Makefile View File

@@ -1,97 +0,0 @@
1
-MAKE="make"
2
-INSTALL="install"
3
-TAR="tar"
4
-GREP="grep"
5
-NODE="node"
6
-NPM="npm"
7
-DESTDIR="./dist"
8
-PKG_VERSION := $( $(GREP) -Po '(?<="version": ")[^"]*' )
9
-TMPDIR := $(shell mktemp -d)
10
-# default modules
11
-MODULES="php"
12
-
13
-all: builddirs npm_dependencies swig htmlmin min-css min-js copy-img submodules
14
-	
15
-swig:
16
-	$(NODE) node_modules/swig/bin/swig.js render -j dist.json templates/faq.swig > $(CURDIR)/build/faq.html 
17
-	$(NODE) node_modules/swig/bin/swig.js render -j dist.json templates/index.swig > $(CURDIR)/build/index.html 
18
-	$(NODE) node_modules/swig/bin/swig.js render -j dist.json templates/tools.swig > $(CURDIR)/build/tools.html 
19
-
20
-htmlmin:
21
-	$(NODE) node_modules/htmlmin/bin/htmlmin $(CURDIR)/build/index.html -o $(CURDIR)/build/index.html 
22
-	$(NODE) node_modules/htmlmin/bin/htmlmin $(CURDIR)/build/faq.html -o $(CURDIR)/build/faq.html 
23
-	$(NODE) node_modules/htmlmin/bin/htmlmin $(CURDIR)/build/tools.html -o $(CURDIR)/build/tools.html 
24
-
25
-installdirs:
26
-	mkdir -p $(DESTDIR)/ $(DESTDIR)/img
27
-ifneq (,$(findstring php,$(MODULES)))
28
-	mkdir -p $(DESTDIR)/classes $(DESTDIR)/includes
29
-endif
30
-ifneq (,$(findstring moe,$(MODULES)))
31
-	mkdir -p $(DESTDIR)/moe/{css,fonts,includes,js,login,panel/css/font,panel/css/images,register,templates}
32
-endif
33
-	
34
-min-css:
35
-	$(NODE) $(CURDIR)/node_modules/.bin/cleancss --s0 $(CURDIR)/static/css/pomf.css > $(CURDIR)/build/pomf.min.css
36
-
37
-min-js:
38
-	echo "// @source https://github.com/pomf/pomf/tree/master/static/js" > $(CURDIR)/build/pomf.min.js 
39
-	echo "// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat" >> $(CURDIR)/build/pomf.min.js
40
-	$(NODE) $(CURDIR)/node_modules/.bin/uglifyjs  --screw-ie8 ./static/js/app.js >> $(CURDIR)/build/pomf.min.js 
41
-	echo "// @license-end" >> $(CURDIR)/build/pomf.min.js
42
-
43
-copy-img:
44
-	cp -v $(CURDIR)/static/img/*.png $(CURDIR)/build/img/
45
-	cp -vT $(CURDIR)/static/img/favicon.ico $(CURDIR)/build/favicon.ico
46
-
47
-copy-php:
48
-ifneq ($(wildcard $(CURDIR)/static/php/.),)
49
-	cp -rv $(CURDIR)/static/php/* $(CURDIR)/build/
50
-else
51
-	$(error The php submodule was not found)
52
-endif
53
-
54
-copy-moe:
55
-ifneq ($(wildcard $(CURDIR)/moe/.),)
56
-	cp -rv $(CURDIR)/moe $(CURDIR)/build/
57
-else
58
-	$(error The moe submodule was not found)
59
-endif
60
-
61
-install: installdirs
62
-	cp -rv $(CURDIR)/build/* $(DESTDIR)/
63
-
64
-dist:
65
-	DESTDIR=$(TMPDIR)/pomf-$(PKGVERSION)
66
-	export DESTDIR
67
-	install
68
-	$(TAR) cJf pomf-$(PKG_VERSION).tar.xz $(DESTDIR)
69
-	rm -rf $(TMPDIR)
70
-	
71
-clean:
72
-	rm -rvf $(CURDIR)/node_modules 
73
-	rm -rvf $(CURDIR)/build
74
-	
75
-uninstall:
76
-	rm -rvf $(DESTDIR)/
77
-	
78
-npm_dependencies:
79
-	$(NPM) install
80
-
81
-builddirs:
82
-	mkdir -p $(CURDIR)/build $(CURDIR)/build/img 
83
-ifneq (,$(findstring php,$(MODULES)))
84
-	mkdir -p $(CURDIR)/build/classes $(CURDIR)/build/includes
85
-endif
86
-ifneq (,$(findstring moe,$(MODULES)))
87
-	mkdir -p $(CURDIR)/build/moe/{css,fonts,includes,js,login,panel/css/font,panel/css/images,register,templates}
88
-endif
89
-
90
-submodules:
91
-	$(info The following modules will be enabled: $(MODULES))
92
-ifneq (,$(findstring php,$(MODULES)))
93
-	$(MAKE) copy-php
94
-endif
95
-ifneq (,$(findstring moe,$(MODULES)))
96
-	$(MAKE) copy-moe
97
-endif

+ 54
- 164
README.md View File

@@ -1,184 +1,74 @@
1
-# Pomf
2
-[![Build
3
-Status](https://travis-ci.org/pomf/pomf.svg?branch=master)](https://travis-ci.org/pomf/pomf)
4
-[![Dependency
5
-Status](https://david-dm.org/pomf/pomf.svg)](https://david-dm.org/pomf/pomf)
6
-[![devDependency
7
-Status](https://david-dm.org/pomf/pomf/dev-status.svg)](https://david-dm.org/pomf/pomf#info=devDependencies)
8
-[![MIT
9
-licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/pomf/pomf/master/LICENSE)
10
-[![Documentation Status](https://readthedocs.org/projects/pomf/badge/?version=latest)](http://pomf.readthedocs.io/en/latest/?badge=latest)
1
+![loli-safe](https://a.safe.moe/jcutlz.png)
2
+[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/kanadeko/Kuro/master/LICENSE)
3
+[![Chat / Support](https://img.shields.io/badge/Chat%20%2F%20Support-discord-7289DA.svg?style=flat-square)](https://discord.gg/5g6vgwn)
11 4
 
12
-Pomf is a simple file uploading and sharing platform.
5
+# lolisareinthe.club, a small safe worth protecting.
13 6
 
14
-## Features
7
+## What's new in v3.0.0
8
+- Backend rewrite to make it faster, better and easier to extend
9
+- Album downloads (Thanks to [PascalTemel](https://github.com/PascalTemel))
10
+- See releases for changelog
15 11
 
16
-- One click uploading, no registration required
17
-- A minimal, modern web interface
18
-- Drag & drop supported
19
-- Upload API with multiple response choices
20
-  - JSON
21
-  - HTML
22
-  - Text
23
-  - CSV
24
-- Supports [ShareX](https://getsharex.com/) and other screenshot tools
12
+If you're upgrading from a version prior to v3.0.0 make sure to run **ONCE** `node database/migration.js` to create the missing columns on the database.
25 13
 
26
-### Demo
14
+## Running
15
+1. Ensure you have at least version 7.6.0 of node installed
16
+2. Clone the repo
17
+3. Rename `config.sample.js` to `config.js`
18
+4. Modify port, domain and privacy options if desired
19
+5. run `npm install` to install all dependencies
20
+6. run `pm2 start lolisareinthe.club.js` or `node lolisareinthe.club.js` to start the service
27 21
 
28
-See the real world example at [sugoi.vidyagam.es](https://sugoi.vidyagam.es/).
22
+## Getting started
23
+This service supports running both as public and private. The only difference is that one needs a token to upload and the other one doesn't. If you want it to be public so anyone can upload files either from the website or API, just set the option `private: false` in the `config.js` file. In case you want to run it privately, you should set `private: true`.
29 24
 
30
-## Requirements
25
+Upon running the service for the first time, it's gonna create a user account with the username `root` and password `root`. This is your admin account and you should change the password immediately. This account will let you manage all uploaded files and remove any if necessary.
31 26
 
32
-Original development environment is Nginx + PHP5.5 + MySQL, but is confirmed to
33
-work with Apache 2.4 and newer PHP versions. Should work with any other
34
-PDO-compatible database.
27
+The option `serveFilesWithNode` in the `config.js` dictates if you want lolisareinthe.club to serve the files or nginx/apache once they are uploaded. The main difference between the two is the ease of use and the chance of analytics in the future.
28
+If you set it to `true`, the uploaded files will be located after the host like:
29
+	https://lolisareinthe.club/yourFile.jpg
35 30
 
36
-## Install
31
+If you set it to `false`, you need to set nginx to directly serve whatever folder it is you are serving your
32
+downloads in. This also gives you the ability to serve them, for example, like this:
33
+	https://files.lolisareinthe.club/yourFile.jpg
37 34
 
38
-For the purposes of this guide, we won't cover setting up Nginx, PHP, MySQL,
39
-Node, or NPM. So we'll just assume you already have them all running well.
35
+Both cases require you to type the domain where the files will be served on the `domain` key below.
36
+Which one you use is ultimately up to you. Either way, I've provided a sample config files for nginx that you can use to set it up quickly and painlessly!
37
+- [Normal Version](https://github.com/WeebDev/lolisareinthe.club/blob/master/nginx.sample.conf)
38
+- [SSL Version](https://github.com/WeebDev/lolisareinthe.club/blob/master/nginx-ssl.sample.conf)
40 39
 
41
-### Compiling
40
+If you set `enableUserAccounts: true`, people will be able to create accounts on the service to keep track of their uploaded files and create albums to upload stuff to, pretty much like imgur does, but only through the API. Every user account has a token that the user can use to upload stuff through the API. You can find this token on the section called `Change your token` on the administration dashboard, and if it gets leaked or compromised you can renew it by clicking the button titled `Request new token`.
42 41
 
43
-First you must get a copy of the pomf code.  To do so, clone this git repo.
44
-You will need to recursively clone the repo to get the required PHP submodule,
45
-and the optional user panel submodule.
46
-```bash
47
-git clone --recursive https://github.com/pomf/pomf
48
-```
49
-If you don't want either of the submodules run the following command,
50
-```bash
51
-git clone https://github.com/pomf/pomf
52
-```
53
-
54
-Assuming you already have Node and NPM working, compilation is easy. If you would like any additional submodules, or to exclude the default PHP submodule, use the `MODULES="..."` variable.
55
-
56
-Run the following commands to do so.
57
-```bash
58
-cd pomf/
59
-make
60
-# alternatively
61
-make MODULES="" # compile no submodules; exclude the default php backend module
62
-make MODULES="php moe" # compile the php and moe submodules
63
-#
64
-make install
65
-```
66
-OR
67
-```bash
68
-make install DESTDIR=/desired/path/for/site
69
-```
70
-After this, the pomf site is now compressed and set up inside `dist/`, or, if specified, `DESTDIR`.
71
-
72
-## Configuring
73
-
74
-Front-end related settings, such as the name of the site, and maximum allowable
75
-file size, are found in `dist.json`.  Changes made here will
76
-only take effect after rebuilding the site pages.  This may be done by running
77
-`make` from the root of the site directory.
78
-
79
-Back-end related settings, such as database configuration, and path for uploaded files, are found in `static/php/includes/settings.inc.php`.  Changes made here take effect immediately.
80
-
81
-If you intend to allow uploading files larger than 2 MB, you may also need to
82
-increase POST size limits in `php.ini` and webserver configuration. For PHP,
83
-modify `upload_max_filesize` and `post_max_size` values. The configuration
84
-option for nginx webserver is `client_max_body_size`.
42
+## Cloudflare Support
43
+If you are running lolisareinthe.club behind Cloudflare there is support to make the NGINX logs have the users IP instead of Cloudflares IP. You will need to compile NGINX from source with `--with-http_realip_module` as well as uncomment the following line in the NGINX config: `include /path/to/lolisareinthe.club/real-ip-from-cf;`
85 44
 
86
-Example nginx configs can be found in confs/.
45
+## Using loli-safe
46
+Once the service starts you can start hitting the upload endpoint at `/api/upload` with any file. If you're using the frontend to do so then you are pretty much set, but if using the API to upload make sure the form name is set to `files[]` and the form type to `multipart/form-data`. If the service is running in private mode, dont forget to send a header of type `token: YOUR-CLIENT-TOKEN` to validate the request.
87 47
 
88
-## Using SQLite as DB engine
89
-
90
-We need to create the SQLite database before it may be used by pomf.
91
-Fortunately, this is incredibly simple.  
92
-
93
-First create a directory for the database, e.g. `mkdir /var/db/pomf`.  
94
-Then, create a new SQLite database from the schema, e.g. `sqlite3 /var/db/pomf/pomf.sq3 -init /home/pomf/sqlite_schema.sql`.
95
-Then, finally, ensure the permissions are correct, e.g.
96
-```bash
97
-chown nginx:nginx /var/db/pomf
98
-chmod 0750 /var/db/pomf
99
-chmod 0640 /var/db/pomf/pomf.sq3
100
-```
101
-
102
-Finally, edit `php/includes/settings.inc.php` to indicate this is the database engine you would like to use.  Make the changes outlined below
103
-```php
104
-define('POMF_DB_CONN', '[stuff]'); ---> define('POMF_DB_CONN', 'sqlite:/var/db/pomf/pomf.sq3');
105
-define('POMF_DB_USER', '[stuff]'); ---> define('POMF_DB_USER', null);
106
-define('POMF_DB_PASS', '[stuff]'); ---> define('POMF_DB_PASS', null);
48
+A sample of the returning json from the endpoint can be seen below:
49
+```json
50
+{
51
+	"name": "EW7C.png",
52
+	"size": "71400",
53
+	"url": "https://i.kanacchi.moe/EW7C.png"
54
+}
107 55
 ```
108 56
 
109
-*NOTE: The directory where the SQLite database is stored, must be writable by the web server user*
110
-
111
-### Apache
112
-
113
-If you are running Apache and want to compress your output when serving files,
114
-add to your `.htaccess` file:
115
-
116
-    AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/x-javascript application/json
117
-
118
-Remember to enable `deflate_module` and `filter_module` modules in your Apache
119
-configuration file.
120
-
121
-### Migrating from MySQL to SQLite
122
- ,
123
-Compared to SQLite, MySQL is relatively complicated to administer, brings in many unneeded dependencies, and consumes more resources.  Additonally, as a network service, poorly configured installations have the potential
124
-to pose a security risk.
125
-
126
-For these reasons, you may wish to use SQLite rather than MySQL.
127
-
128
-Fortunately, it is incredibly simple to migrate your database.  This may be done on a live server, if you desire, and requires zero downtime.
129
-
130
-The process described below involves running these commands on a live server.  Nothing done here affects your main site, until running the very last command, which is done after verifying there are no issues.  
131
-
132
-No changes described here are destructive, and are easily reverted.  They only have the potential to cause uploading to fail gracefully, and will not affect downloading.
133
-
134
-Run the following commands as root, to dump your database, and make a SQLite database with the contents.  
135
-```bash
136
-mkdir /var/db/pomf
137
-wget -O /tmp/m2s https://github.com/dumblob/mysql2sqlite/raw/master/mysql2sqlite.sh
138
-mysqldump -u OLD_DB_USER -p OLD_DB_PASS pomf | sh /tmp/m2s | sqlite3 /var/db/pomf/sq3
139
-rm /tmp/m2s
140
-chown -R nginx:nginx /var/db/pomf #replace user as appropriate
141
-chmod 0750 /var/db/pomf && chmod 0640 /var/db/pomf/sq3
142
-```
143
-Edit the file `php/includes/settings.inc.php`, in your **source directory**, making the changes outlined below.  Note, changing the second two lines is optional, as they are simply ignored when using SQLite.
144
-```php
145
-define('POMF_DB_CONN', '[stuff]'); ---> define('POMF_DB_CONN', 'sqlite:/var/db/pomf/pomf.sq3');
146
-define('POMF_DB_USER', '[stuff]'); ---> define('POMF_DB_USER', null);
147
-define('POMF_DB_PASS', '[stuff]'); ---> define('POMF_DB_PASS', null);
148
-```
149
-Then, run `make DESTDIR=/path/to/main_site/testing_dir` (note the *testing_dir* component) to rebuild the website, and copy it into place, in a new testing subdirectory.
150
-
151
-Now, navigate to this subdirectory in your web browser, e.g. http://example.com/testing_dir, and verify that uploading works fine.  If so, excellent!  You may rerun `make DESTDIR=/path/to/main_site` to update your main site.
152
-
153
-All done! You may disable or uninstall MySQL if you wish.
154
-
155
-## Getting help
156
-
157
-The Pomf community gathers on IRC. You can also email the maintainer for help.
158
-
159
-- IRC (users): `#pomfret` on Rizon (`irc.rizon.net`)
160
-
161
-## Contributing
162
-
163
-We'd really like if you can take some time to make sure your coding style is
164
-consistent with the project. Pomf follows [PHP
165
-PSR-2](http://www.php-fig.org/psr/psr-2/) and [Airbnb JavaScript
166
-(ES5)](https://github.com/airbnb/javascript/tree/es5-deprecated/es5) (`airbnb/legacy`)
167
-coding style guides. We use ESLint and PHPCS tools to enforce these standards.
168
-
169
-You can also help by sending us feature requests or writing documentation and
170
-tests.
57
+To make it easier and better than any other service, you can download [our Chrome extension](https://chrome.google.com/webstore/detail/loli-safe-uploader/enkkmplljfjppcdaancckgilmgoiofnj) that will let you configure your hostname and tokens, so that you can simply `right click` -> `send to loli-safe` to any image/audio/video file on the web.
171 58
 
172
-Thanks!
59
+Because of how nodejs apps work, if you want it attached to a domain name you will need to make a reverse proxy for it. Here is a tutorial [on how to do this with nginx](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04). Keep in mind that this is only a requirement if you want to access your loli-safe service by using a domain name (ex: https://i.kanacchi.moe), otherwise you can use the service just fine by accessing it from your server's IP.
173 60
 
174
-## Credits
61
+## Sites using loli-safe
62
+- [lolisareinthe.club](https://lolisareinthe.club): A small safe worth protecting.
63
+- [safe.moe](https://safe.moe): The world's most ~~un~~safe pomf clone
64
+- [updx.xyz](http://updx.xyz): A shitty clone. ~~At least the files are more secure!~~
65
+- [safe.fiery.me](https://safe.fiery.me): Just another clone.
66
+- [kayo.pics](https://kayo.pics): File hosting for all~
67
+- Feel free to add yours here.
175 68
 
176
-Pomf was created by Eric Johansson and Peter Lejeck for
177
-[Pomf.se](http://pomf.se/). The software is currently maintained by the
178
-community.
69
+## Author
179 70
 
180
-## License
71
+**lolisareinthe.club** © [Pitu](https://github.com/Pitu), Released under the [MIT](https://github.com/WeebDev/loli-safe/blob/master/LICENSE) License.<br>
72
+Authored and maintained by Pitu.
181 73
 
182
-Pomf is free software, and is released under the terms of the Expat license. See
183
-`LICENSE`.
184
-# pomf
74
+> [lolisareinthe.club](https://lolisareinthe.club) · GitHub [@Pitu](https://github.com/Pitu)

+ 179
- 0
controllers/albumsController.js View File

@@ -0,0 +1,179 @@
1
+const config = require('../config.js');
2
+const db = require('knex')(config.database);
3
+const randomstring = require('randomstring');
4
+const utils = require('./utilsController.js');
5
+const path = require('path');
6
+const fs = require('fs');
7
+const Zip = require('jszip');
8
+const albumsController = {};
9
+
10
+albumsController.list = async (req, res, next) => {
11
+	const user = await utils.authorize(req, res);
12
+
13
+	const fields = ['id', 'name'];
14
+	if (req.params.sidebar === undefined) {
15
+		fields.push('timestamp');
16
+		fields.push('identifier');
17
+	}
18
+
19
+	const albums = await db.table('albums').select(fields).where({ enabled: 1, userid: user.id });
20
+	if (req.params.sidebar !== undefined) {
21
+		return res.json({ success: true, albums });
22
+	}
23
+
24
+	let ids = [];
25
+	for (let album of albums) {
26
+		album.date = new Date(album.timestamp * 1000)
27
+		album.date = utils.getPrettyDate(album.date)
28
+
29
+		album.identifier = `${config.domain}/a/${album.identifier}`;
30
+		ids.push(album.id);
31
+	}
32
+
33
+	const files = await db.table('files').whereIn('albumid', ids).select('albumid');
34
+	const albumsCount = {};
35
+
36
+	for (let id of ids) albumsCount[id] = 0;
37
+	for (let file of files) albumsCount[file.albumid] += 1;
38
+	for (let album of albums) album.files = albumsCount[album.id];
39
+
40
+	return res.json({ success: true, albums });
41
+};
42
+
43
+albumsController.create = async (req, res, next) => {
44
+	const user = await utils.authorize(req, res);
45
+
46
+	const name = req.body.name;
47
+	if (name === undefined || name === '') {
48
+		return res.json({ success: false, description: 'No album name specified' });
49
+	}
50
+
51
+	const album = await db.table('albums').where({
52
+		name: name,
53
+		enabled: 1,
54
+		userid: user.id
55
+	}).first();
56
+
57
+	if (album) {
58
+		return res.json({ success: false, description: 'There\'s already an album with that name' })
59
+	}
60
+
61
+	await db.table('albums').insert({
62
+		name: name,
63
+		enabled: 1,
64
+		userid: user.id,
65
+		identifier: randomstring.generate(8),
66
+		timestamp: Math.floor(Date.now() / 1000)
67
+	});
68
+
69
+	return res.json({ success: true });
70
+};
71
+
72
+albumsController.delete = async (req, res, next) => {
73
+	const user = await utils.authorize(req, res);
74
+
75
+	const id = req.body.id;
76
+	if (id === undefined || id === '') {
77
+		return res.json({ success: false, description: 'No album specified' });
78
+	}
79
+
80
+	await db.table('albums').where({ id: id, userid: user.id }).update({ enabled: 0 });
81
+	return res.json({ success: true });
82
+};
83
+
84
+albumsController.rename = async (req, res, next) => {
85
+	const user = await utils.authorize(req, res);
86
+
87
+	const id = req.body.id;
88
+	if (id === undefined || id === '') {
89
+		return res.json({ success: false, description: 'No album specified' });
90
+	}
91
+
92
+	const name = req.body.name;
93
+	if (name === undefined || name === '') {
94
+		return res.json({ success: false, description: 'No name specified' });
95
+	}
96
+
97
+	const album = await db.table('albums').where({ name: name, userid: user.id }).first();
98
+	if (album) {
99
+		return res.json({ success: false, description: 'Name already in use' })
100
+	}
101
+
102
+	await db.table('albums').where({ id: id, userid: user.id }).update({ name: name })
103
+	return res.json({ success: true });
104
+};
105
+
106
+albumsController.get = async (req, res, next) => {
107
+	const identifier = req.params.identifier;
108
+	if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' });
109
+
110
+	const album = await db.table('albums').where({ identifier, enabled: 1 }).first();
111
+	if (!album) return res.json({ success: false, description: 'Album not found' });
112
+
113
+	const title = album.name;
114
+	const files = await db.table('files').select('name').where('albumid', album.id).orderBy('id', 'DESC');
115
+
116
+	for (let file of files) {
117
+		file.file = `${config.domain}/${file.name}`;
118
+
119
+		const ext = path.extname(file.name).toLowerCase();
120
+		if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) {
121
+			file.thumb = `${config.domain}/thumbs/${file.name.slice(0, -ext.length)}.png`;
122
+		}
123
+	}
124
+
125
+	return res.json({
126
+		success: true,
127
+		title: title,
128
+		count: files.length,
129
+		files
130
+	});
131
+};
132
+
133
+
134
+albumsController.generateZip = async (req, res, next) => {
135
+	const identifier = req.params.identifier;
136
+	if (identifier === undefined) return res.status(401).json({ success: false, description: 'No identifier provided' });
137
+	if (!config.uploads.generateZips) return res.status(401).json({ success: false, description: 'Zip generation disabled' });
138
+
139
+	const album = await db.table('albums').where({ identifier, enabled: 1 }).first();
140
+	if (!album) return res.json({ success: false, description: 'Album not found' });
141
+
142
+	if (album.zipGeneratedAt > album.editedAt) {
143
+		const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`);
144
+		const fileName = `${album.name}.zip`;
145
+		return res.download(filePath, fileName);
146
+	} else {
147
+		console.log(`Generating zip for album identifier: ${identifier}`);
148
+		const files = await db.table('files').select('name').where('albumid', album.id);
149
+		if (files.length === 0) return res.json({ success: false, description: 'There are no files in the album' });
150
+
151
+		const zipPath = path.join(__dirname, '..', config.uploads.folder, 'zips', `${album.identifier}.zip`);
152
+		let archive = new Zip();
153
+
154
+		for (let file of files) {
155
+			try {
156
+				const exists = fs.statSync(path.join(__dirname, '..', config.uploads.folder, file.name));
157
+				archive.file(file.name, fs.readFileSync(path.join(__dirname, '..', config.uploads.folder, file.name)));
158
+			} catch (err) {
159
+				console.log(err);
160
+			}
161
+		}
162
+
163
+		archive
164
+			.generateNodeStream({ type: 'nodebuffer', streamFiles: true })
165
+			.pipe(fs.createWriteStream(zipPath))
166
+			.on('finish', async () => {
167
+				console.log(`Generated zip for album identifier: ${identifier}`);
168
+				await db.table('albums')
169
+					.where('id', album.id)
170
+					.update({ zipGeneratedAt: Math.floor(Date.now() / 1000) });
171
+
172
+				const filePath = path.join(config.uploads.folder, 'zips', `${identifier}.zip`);
173
+				const fileName = `${album.name}.zip`;
174
+				return res.download(filePath, fileName);
175
+			});
176
+	}
177
+};
178
+
179
+module.exports = albumsController;

+ 91
- 0
controllers/authController.js View File

@@ -0,0 +1,91 @@
1
+const config = require('../config.js');
2
+const db = require('knex')(config.database);
3
+const bcrypt = require('bcrypt');
4
+const randomstring = require('randomstring');
5
+const utils = require('./utilsController.js');
6
+
7
+let authController = {};
8
+
9
+authController.verify = async (req, res, next) => {
10
+	const username = req.body.username;
11
+	const password = req.body.password;
12
+
13
+	if (username === undefined) return res.json({ success: false, description: 'No username provided' });
14
+	if (password === undefined) return res.json({ success: false, description: 'No password provided' });
15
+
16
+	const user = await db.table('users').where('username', username).first();
17
+	if (!user) return res.json({ success: false, description: 'Username doesn\'t exist' });
18
+	if (user.enabled === false || user.enabled === 0) return res.json({
19
+		success: false,
20
+		description: 'This account has been disabled'
21
+	});
22
+
23
+	bcrypt.compare(password, user.password, (err, result) => {
24
+		if (err) {
25
+			console.log(err);
26
+			return res.json({ success: false, description: 'There was an error' });
27
+		}
28
+		if (result === false) return res.json({ success: false, description: 'Wrong password' });
29
+		return res.json({ success: true, token: user.token });
30
+	});
31
+};
32
+
33
+authController.register = async (req, res, next) => {
34
+	if (config.enableUserAccounts === false) {
35
+		return res.json({ success: false, description: 'Register is disabled at the moment' });
36
+	}
37
+
38
+	const username = req.body.username;
39
+	const password = req.body.password;
40
+
41
+	if (username === undefined) return res.json({ success: false, description: 'No username provided' });
42
+	if (password === undefined) return res.json({ success: false, description: 'No password provided' });
43
+
44
+	if (username.length < 4 || username.length > 32) {
45
+		return res.json({ success: false, description: 'Username must have 4-32 characters' })
46
+	}
47
+	if (password.length < 6 || password.length > 64) {
48
+		return res.json({ success: false, description: 'Password must have 6-64 characters' })
49
+	}
50
+
51
+	const user = await db.table('users').where('username', username).first();
52
+	if (user) return res.json({ success: false, description: 'Username already exists' });
53
+
54
+	bcrypt.hash(password, 10, async (err, hash) => {
55
+		if (err) {
56
+			console.log(err);
57
+			return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' });
58
+		}
59
+		const token = randomstring.generate(64);
60
+		await db.table('users').insert({
61
+			username: username,
62
+			password: hash,
63
+			token: token,
64
+			enabled: 1
65
+		});
66
+		return res.json({ success: true, token: token })
67
+	});
68
+};
69
+
70
+authController.changePassword = async (req, res, next) => {
71
+	const user = await utils.authorize(req, res);
72
+
73
+	let password = req.body.password;
74
+	if (password === undefined) return res.json({ success: false, description: 'No password provided' });
75
+
76
+	if (password.length < 6 || password.length > 64) {
77
+		return res.json({ success: false, description: 'Password must have 6-64 characters' });
78
+	}
79
+
80
+	bcrypt.hash(password, 10, async (err, hash) => {
81
+		if (err) {
82
+			console.log(err);
83
+			return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻' });
84
+		}
85
+
86
+		await db.table('users').where('id', user.id).update({ password: hash });
87
+		return res.json({ success: true });
88
+	});
89
+};
90
+
91
+module.exports = authController;

+ 34
- 0
controllers/tokenController.js View File

@@ -0,0 +1,34 @@
1
+const config = require('../config.js');
2
+const db = require('knex')(config.database);
3
+const randomstring = require('randomstring');
4
+const utils = require('./utilsController.js');
5
+
6
+const tokenController = {};
7
+
8
+tokenController.verify = async (req, res, next) => {
9
+	const token = req.body.token;
10
+	if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' });
11
+
12
+	const user = await db.table('users').where('token', token).first();
13
+	if (!user) return res.status(401).json({ success: false, description: 'Invalid token' });
14
+	return res.json({ success: true, username: user.username });
15
+};
16
+
17
+tokenController.list = async (req, res, next) => {
18
+	const user = await utils.authorize(req, res);
19
+	return res.json({ success: true, token: user.token });
20
+};
21
+
22
+tokenController.change = async (req, res, next) => {
23
+	const user = await utils.authorize(req, res);
24
+	const newtoken = randomstring.generate(64);
25
+
26
+	await db.table('users').where('token', user.token).update({
27
+		token: newtoken,
28
+		timestamp: Math.floor(Date.now() / 1000)
29
+	});
30
+
31
+	res.json({ success: true, token: newtoken });
32
+};
33
+
34
+module.exports = tokenController;

+ 302
- 0
controllers/uploadController.js View File

@@ -0,0 +1,302 @@
1
+const config = require('../config.js');
2
+const path = require('path');
3
+const multer = require('multer');
4
+const randomstring = require('randomstring');
5
+const db = require('knex')(config.database);
6
+const crypto = require('crypto');
7
+const fs = require('fs');
8
+const utils = require('./utilsController.js');
9
+
10
+const uploadsController = {};
11
+
12
+// Let's default it to only 1 try
13
+const maxTries = config.uploads.maxTries || 1;
14
+const uploadDir = path.join(__dirname, '..', config.uploads.folder);
15
+
16
+const storage = multer.diskStorage({
17
+	destination: function(req, file, cb) {
18
+		cb(null, uploadDir);
19
+	},
20
+  	filename: function(req, file, cb) {
21
+		const access = i => {
22
+			const name = randomstring.generate(config.uploads.fileLength) + path.extname(file.originalname);
23
+			fs.access(path.join(uploadDir, name), err => {
24
+				if (err) return cb(null, name);
25
+				console.log(`A file named "${name}" already exists (${++i}/${maxTries}).`);
26
+				if (i < maxTries) return access(i);
27
+				return cb('Could not allocate a unique file name. Try again?');
28
+			});
29
+		};
30
+		access(0);
31
+	}
32
+});
33
+
34
+const upload = multer({
35
+	storage: storage,
36
+	limits: { fileSize: config.uploads.maxSize },
37
+	fileFilter: function(req, file, cb) {
38
+		if (config.blockedExtensions !== undefined) {
39
+			if (config.blockedExtensions.some(extension => path.extname(file.originalname).toLowerCase() === extension)) {
40
+				return cb('This file extension is not allowed');
41
+			}
42
+			return cb(null, true);
43
+		}
44
+		return cb(null, true);
45
+	}
46
+}).array('files[]');
47
+
48
+uploadsController.upload = async (req, res, next) => {
49
+	if (config.private === true) {
50
+		await utils.authorize(req, res);
51
+	}
52
+
53
+	const token = req.headers.token || '';
54
+	const user = await db.table('users').where('token', token).first();
55
+	if (user && (user.enabled === false || user.enabled === 0)) return res.json({
56
+		success: false,
57
+		description: 'This account has been disabled'
58
+	});
59
+	const albumid = req.headers.albumid || req.params.albumid;
60
+
61
+	if (albumid && user) {
62
+		const album = await db.table('albums').where({ id: albumid, userid: user.id }).first();
63
+		if (!album) {
64
+			return res.json({
65
+				success: false,
66
+				description: 'Album doesn\'t exist or it doesn\'t belong to the user'
67
+			});
68
+		}
69
+		return uploadsController.actuallyUpload(req, res, user, albumid);
70
+	}
71
+	return uploadsController.actuallyUpload(req, res, user, albumid);
72
+};
73
+
74
+uploadsController.actuallyUpload = async (req, res, userid, album) => {
75
+	upload(req, res, async err => {
76
+		if (err) {
77
+			console.error(err);
78
+			return res.json({ success: false, description: err });
79
+		}
80
+
81
+		if (req.files.length === 0) return res.json({ success: false, description: 'no-files' });
82
+
83
+		const files = [];
84
+		const existingFiles = [];
85
+		let iteration = 1;
86
+
87
+		req.files.forEach(async file => {
88
+			// Check if the file exists by checking hash and size
89
+			let hash = crypto.createHash('md5');
90
+			let stream = fs.createReadStream(path.join(__dirname, '..', config.uploads.folder, file.filename));
91
+
92
+			stream.on('data', data => {
93
+				hash.update(data, 'utf8');
94
+			});
95
+
96
+			stream.on('end', async () => {
97
+				const fileHash = hash.digest('hex');
98
+				const dbFile = await db.table('files')
99
+					.where(function() {
100
+						if (userid === undefined) this.whereNull('userid');
101
+						else this.where('userid', userid.id);
102
+					})
103
+					.where({
104
+						hash: fileHash,
105
+						size: file.size
106
+					})
107
+					.first();
108
+
109
+				if (!dbFile) {
110
+					files.push({
111
+						name: file.filename,
112
+						original: file.originalname,
113
+						type: file.mimetype,
114
+						size: file.size,
115
+						hash: fileHash,
116
+						ip: req.ip,
117
+						albumid: album,
118
+						userid: userid !== undefined ? userid.id : null,
119
+						timestamp: Math.floor(Date.now() / 1000)
120
+					});
121
+				} else {
122
+					uploadsController.deleteFile(file.filename).then(() => {}).catch(err => console.error(err));
123
+					existingFiles.push(dbFile);
124
+				}
125
+
126
+				if (iteration === req.files.length) {
127
+					return uploadsController.processFilesForDisplay(req, res, files, existingFiles);
128
+				}
129
+				iteration++;
130
+			});
131
+		});
132
+	});
133
+};
134
+
135
+uploadsController.processFilesForDisplay = async (req, res, files, existingFiles) => {
136
+	let basedomain = config.domain;
137
+	if (files.length === 0) {
138
+		return res.json({
139
+			success: true,
140
+			files: existingFiles.map(file => {
141
+				return {
142
+					name: file.name,
143
+					size: file.size,
144
+					url: `${basedomain}/${file.name}`
145
+				};
146
+			})
147
+		});
148
+	}
149
+
150
+	await db.table('files').insert(files);
151
+	for (let efile of existingFiles) files.push(efile);
152
+
153
+	res.json({
154
+		success: true,
155
+		files: files.map(file => {
156
+			return {
157
+				name: file.name,
158
+				size: file.size,
159
+				url: `${basedomain}/${file.name}`
160
+			};
161
+		})
162
+	});
163
+
164
+	for (let file of files) {
165
+		let ext = path.extname(file.name).toLowerCase();
166
+		if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) {
167
+			file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`;
168
+			utils.generateThumbs(file);
169
+		}
170
+
171
+		if (file.albumid) {
172
+			db.table('albums').where('id', file.albumid).update('editedAt', file.timestamp).then(() => {})
173
+				.catch(error => { console.log(error); res.json({ success: false, description: 'Error updating album' }); });
174
+		}
175
+	}
176
+};
177
+
178
+uploadsController.delete = async (req, res) => {
179
+	const user = await utils.authorize(req, res);
180
+	const id = req.body.id;
181
+	if (id === undefined || id === '') {
182
+		return res.json({ success: false, description: 'No file specified' });
183
+	}
184
+
185
+	const file = await db.table('files')
186
+		.where('id', id)
187
+		.where(function() {
188
+			if (user.username !== 'root') {
189
+				this.where('userid', user.id);
190
+			}
191
+		})
192
+		.first();
193
+
194
+	try {
195
+		await uploadsController.deleteFile(file.name);
196
+		await db.table('files').where('id', id).del();
197
+		if (file.albumid) {
198
+			await db.table('albums').where('id', file.albumid).update('editedAt', Math.floor(Date.now() / 1000));
199
+		}
200
+	} catch (err) {
201
+		console.log(err);
202
+	}
203
+
204
+	return res.json({ success: true });
205
+};
206
+
207
+uploadsController.deleteFile = function(file) {
208
+	const ext = path.extname(file).toLowerCase();
209
+	return new Promise((resolve, reject) => {
210
+		fs.stat(path.join(__dirname, '..', config.uploads.folder, file), (err, stats) => {
211
+			if (err) { return reject(err); }
212
+			fs.unlink(path.join(__dirname, '..', config.uploads.folder, file), err => {
213
+				if (err) { return reject(err); }
214
+				if (!utils.imageExtensions.includes(ext) && !utils.videoExtensions.includes(ext)) {
215
+					return resolve();
216
+				}
217
+				file = file.substr(0, file.lastIndexOf('.')) + '.png';
218
+				fs.stat(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), (err, stats) => {
219
+					if (err) {
220
+						console.log(err);
221
+						return resolve();
222
+					}
223
+					fs.unlink(path.join(__dirname, '..', config.uploads.folder, 'thumbs/', file), err => {
224
+						if (err) { return reject(err); }
225
+						return resolve();
226
+					});
227
+				});
228
+			});
229
+		});
230
+	});
231
+};
232
+
233
+uploadsController.list = async (req, res) => {
234
+	const user = await utils.authorize(req, res);
235
+
236
+	let offset = req.params.page;
237
+	if (offset === undefined) offset = 0;
238
+
239
+	const files = await db.table('files')
240
+		.where(function() {
241
+			if (req.params.id === undefined) this.where('id', '<>', '');
242
+			else this.where('albumid', req.params.id);
243
+		})
244
+		.where(function() {
245
+			if (user.username !== 'root') this.where('userid', user.id);
246
+		})
247
+		.orderBy('id', 'DESC')
248
+		.limit(25)
249
+		.offset(25 * offset)
250
+		.select('id', 'albumid', 'timestamp', 'name', 'userid');
251
+
252
+	const albums = await db.table('albums');
253
+	let basedomain = config.domain;
254
+	let userids = [];
255
+
256
+	for (let file of files) {
257
+		file.file = `${basedomain}/${file.name}`;
258
+		file.date = new Date(file.timestamp * 1000);
259
+		file.date = utils.getPrettyDate(file.date);
260
+
261
+		file.album = '';
262
+
263
+		if (file.albumid !== undefined) {
264
+			for (let album of albums) {
265
+				if (file.albumid === album.id) {
266
+					file.album = album.name;
267
+				}
268
+			}
269
+		}
270
+
271
+		// Only push usernames if we are root
272
+		if (user.username === 'root') {
273
+			if (file.userid !== undefined && file.userid !== null && file.userid !== '') {
274
+				userids.push(file.userid);
275
+			}
276
+		}
277
+
278
+		let ext = path.extname(file.name).toLowerCase();
279
+		if (utils.imageExtensions.includes(ext) || utils.videoExtensions.includes(ext)) {
280
+			file.thumb = `${basedomain}/thumbs/${file.name.slice(0, -ext.length)}.png`;
281
+		}
282
+	}
283
+
284
+	// If we are a normal user, send response
285
+	if (user.username !== 'root') return res.json({ success: true, files });
286
+
287
+	// If we are root but there are no uploads attached to a user, send response
288
+	if (userids.length === 0) return res.json({ success: true, files });
289
+
290
+	const users = await db.table('users').whereIn('id', userids);
291
+	for (let dbUser of users) {
292
+		for (let file of files) {
293
+			if (file.userid === dbUser.id) {
294
+				file.username = dbUser.username;
295
+			}
296
+		}
297
+	}
298
+
299
+	return res.json({ success: true, files });
300
+};
301
+
302
+module.exports = uploadsController;

+ 67
- 0
controllers/utilsController.js View File

@@ -0,0 +1,67 @@
1
+const path = require('path');
2
+const config = require('../config.js');
3
+const fs = require('fs');
4
+const gm = require('gm');
5
+const ffmpeg = require('fluent-ffmpeg');
6
+const db = require('knex')(config.database);
7
+
8
+const utilsController = {};
9
+utilsController.imageExtensions = ['.jpg', '.jpeg', '.bmp', '.gif', '.png'];
10
+utilsController.videoExtensions = ['.webm', '.mp4', '.wmv', '.avi', '.mov'];
11
+
12
+utilsController.getPrettyDate = function(date) {
13
+	return date.getFullYear() + '-'
14
+		+ (date.getMonth() + 1) + '-'
15
+		+ date.getDate() + ' '
16
+		+ (date.getHours() < 10 ? '0' : '')
17
+		+ date.getHours() + ':'
18
+		+ (date.getMinutes() < 10 ? '0' : '')
19
+		+ date.getMinutes() + ':'
20
+		+ (date.getSeconds() < 10 ? '0' : '')
21
+		+ date.getSeconds();
22
+}
23
+
24
+utilsController.authorize = async (req, res) => {
25
+	const token = req.headers.token;
26
+	if (token === undefined) return res.status(401).json({ success: false, description: 'No token provided' });
27
+
28
+	const user = await db.table('users').where('token', token).first();
29
+	if (!user) return res.status(401).json({ success: false, description: 'Invalid token' });
30
+	return user;
31
+};
32
+
33
+utilsController.generateThumbs = function(file, basedomain) {
34
+	if (config.uploads.generateThumbnails !== true) return;
35
+	const ext = path.extname(file.name).toLowerCase();
36
+
37
+	let thumbname = path.join(__dirname, '..', config.uploads.folder, 'thumbs', file.name.slice(0, -ext.length) + '.png');
38
+	fs.access(thumbname, err => {
39
+		if (err && err.code === 'ENOENT') {
40
+			if (utilsController.videoExtensions.includes(ext)) {
41
+				ffmpeg(path.join(__dirname, '..', config.uploads.folder, file.name))
42
+					.thumbnail({
43
+						timestamps: [0],
44
+						filename: '%b.png',
45
+						folder: path.join(__dirname, '..', config.uploads.folder, 'thumbs'),
46
+						size: '200x?'
47
+					})
48
+					.on('error', error => console.log('Error - ', error.message));
49
+			} else {
50
+				let size = {
51
+					width: 200,
52
+					height: 200
53
+				};
54
+				gm(path.join(__dirname, '..', config.uploads.folder, file.name))
55
+					.resize(size.width, size.height + '>')
56
+					.gravity('Center')
57
+					.extent(size.width, size.height)
58
+					.background('transparent')
59
+					.write(thumbname, error => {
60
+						if (error) console.log('Error - ', error);
61
+					});
62
+			}
63
+		}
64
+	});
65
+};
66
+
67
+module.exports = utilsController;

+ 53
- 0
database/db.js View File

@@ -0,0 +1,53 @@
1
+let init = function(db){
2
+
3
+	// Create the tables we need to store galleries and files
4
+	db.schema.createTableIfNotExists('albums', function (table) {
5
+		table.increments()
6
+		table.integer('userid')
7
+		table.string('name')
8
+		table.string('identifier')
9
+		table.integer('enabled')
10
+		table.integer('timestamp')
11
+		table.integer('editedAt');
12
+		table.integer('zipGeneratedAt');
13
+	}).then(() => {})
14
+
15
+	db.schema.createTableIfNotExists('files', function (table) {
16
+		table.increments()
17
+		table.integer('userid')
18
+		table.string('name')
19
+		table.string('original')
20
+		table.string('type')
21
+		table.string('size')
22
+		table.string('hash')
23
+		table.string('ip')
24
+		table.integer('albumid')
25
+		table.integer('timestamp')
26
+	}).then(() => {})
27
+
28
+	db.schema.createTableIfNotExists('users', function (table) {
29
+		table.increments()
30
+		table.string('username')
31
+		table.string('password')
32
+		table.string('token')
33
+		table.integer('enabled')
34
+		table.integer('timestamp')
35
+	}).then(() => {
36
+		db.table('users').where({username: 'root'}).then((user) => {
37
+			if(user.length > 0) return
38
+
39
+			require('bcrypt').hash('root', 10, function(err, hash) {
40
+				if(err) console.error('Error generating password hash for root')
41
+
42
+				db.table('users').insert({
43
+					username: 'root',
44
+					password: hash,
45
+					token: require('randomstring').generate(64),
46
+					timestamp: Math.floor(Date.now() / 1000)
47
+				}).then(() => {})
48
+			})
49
+		})
50
+	})
51
+}
52
+
53
+module.exports = init

+ 13
- 0
database/migration.js View File

@@ -0,0 +1,13 @@
1
+const config = require('../config.js');
2
+const db = require('knex')(config.database);
3
+
4
+const migration = {};
5
+migration.start = async () => {
6
+	await db.schema.table('albums', table => {
7
+		table.integer('editedAt');
8
+		table.integer('zipGeneratedAt');
9
+	});
10
+	console.log('Migration finished! Now start lolisareinthe.club normally');
11
+};
12
+
13
+migration.start();

+ 0
- 26
dist.json View File

@@ -1,26 +0,0 @@
1
-{
2
-	"init": {
3
-          "allowErrors": false
4
-        },
5
-        "dest": "dist",
6
-        "pkgVersion": "<%= pkg.version %>",
7
-	"banners": [
8
-		"banners/donations.swig"
9
-	],
10
-	"src": [
11
-		"templates/index.swig",
12
-		"templates/faq.swig",
13
-		"templates/tools.swig"
14
-	],
15
-	"generateRobotstxt": false,
16
-	"generateSitemap": false,
17
-	"max_upload_size": 300,
18
-	"production": false,
19
-	"siteName": "Lolis are in the club",
20
-	"siteUrl": "http://lolisareinthe.club/",
21
-	"abuseContact": "abuse@lolisareinthe.club",
22
-	"infoContact": "contact@lolisareinthe.club",
23
-	"paypalUrl": "https://paypal.me/sagiribot",
24
-	"bitcoinAddress": "1LT4sjk3iqDFiQWqNENtbYmkahJK5MvCcn",
25
-	"flattrUrl": ""
26
-}

+ 58
- 0
lolisafe.js View File

@@ -0,0 +1,58 @@
1
+const config = require('./config.js');
2
+const api = require('./routes/api.js');
3
+const album = require('./routes/album.js');
4
+const express = require('express');
5
+const helmet = require('helmet');
6
+const bodyParser = require('body-parser');
7
+const RateLimit = require('express-rate-limit');
8
+const db = require('knex')(config.database);
9
+const fs = require('fs');
10
+const exphbs = require('express-handlebars');
11
+const safe = express();
12
+
13
+require('./database/db.js')(db);
14
+
15
+fs.existsSync('./pages/custom' ) || fs.mkdirSync('./pages/custom');
16
+fs.existsSync('./' + config.logsFolder) || fs.mkdirSync('./' + config.logsFolder);
17
+fs.existsSync('./' + config.uploads.folder) || fs.mkdirSync('./' + config.uploads.folder);
18
+fs.existsSync('./' + config.uploads.folder + '/thumbs') || fs.mkdirSync('./' + config.uploads.folder + '/thumbs');
19
+fs.existsSync('./' + config.uploads.folder + '/zips') || fs.mkdirSync('./' + config.uploads.folder + '/zips')
20
+
21
+safe.use(helmet());
22
+safe.set('trust proxy', 1);
23
+
24
+safe.engine('handlebars', exphbs({ defaultLayout: 'main' }));
25
+safe.set('view engine', 'handlebars');
26
+safe.enable('view cache');
27
+
28
+let limiter = new RateLimit({ windowMs: 5000, max: 2 });
29
+safe.use('/api/login/', limiter);
30
+safe.use('/api/register/', limiter);
31
+
32
+safe.use(bodyParser.urlencoded({ extended: true }));
33
+safe.use(bodyParser.json());
34
+
35
+if (config.serveFilesWithNode) {
36
+	safe.use('/', express.static(config.uploads.folder));
37
+}
38
+
39
+safe.use('/', express.static('./public'));
40
+safe.use('/', album);
41
+safe.use('/api', api);
42
+
43
+for (let page of config.pages) {
44
+	let root = './pages/';
45
+	if (fs.existsSync(`./pages/custom/${page}.html`)) {
46
+		root = './pages/custom/';
47
+	}
48
+	if (page === 'home') {
49
+		safe.get('/', (req, res, next) => res.sendFile(`${page}.html`, { root: root }));
50
+	} else {
51
+		safe.get(`/${page}`, (req, res, next) => res.sendFile(`${page}.html`, { root: root }));
52
+	}
53
+}
54
+
55
+safe.use((req, res, next) => res.status(404).sendFile('404.html', { root: './pages/error/' }));
56
+safe.use((req, res, next) => res.status(500).sendFile('500.html', { root: './pages/error/' }));
57
+
58
+safe.listen(config.port, () => console.log(`lolisareinthe.club started on port ${config.port}`));

+ 0
- 20
mysql_schema.sql View File

@@ -1,27 +0,0 @@
1
---
2
-
3
---
4
---
5
-
6
-CREATE TABLE `files` (
7
-  `id` int(10) unsigned NOT NULL auto_increment,
8
-  `hash` char(40) default NULL,
9
-  `originalname` varchar(255) default NULL,
10
-  `filename` varchar(30) default NULL,
11
-  `size` int(10) unsigned default NULL,
12
-  `date` date default NULL,
13
-  `expire` date default NULL,
14
-  `delid` char(40) default NULL,
15
-  `user` int(10) unsigned default '0',
16
-  `dir` int(2) default '0',
17
-  PRIMARY KEY (`id`)
18
-) ENGINE=InnoDB;
19
-
20
-

+ 38
- 20
package.json View File

@@ -1,30 +1,48 @@
1 1
 {
2
-  "name": "pomf",
3
-  "version": "2.1.0",
4
-  "description": "Kawaii file host",
5
-  "homepage": "https://lolisareinthe.club/",
2
+  "name": "lolisareinthe.club",
3
+  "version": "3.0.0",
4
+  "description": "Blazing fast file uploader and awesome bunker written in node! 🚀",
5
+  "author": "Pitu",
6 6
   "repository": {
7 7
     "type": "git",
8
-    "url": "https://github.com/pomf/pomf"
8
+    "url": "https://github.com/WeebDev/lolisareinthe.club"
9 9
   },
10
-  "author": "Eliot Whalan <ewhal@pantsu.cat>",
11
-  "contributors": [
12
-    "Eric Johansson <neku@pomf.se>",
13
-    "Peter Lejeck <peter.lejeck@gmail.com>",
14
-    "cenci0 <alchimist94@gmail.com>",
15
-    "Juuso Lapinlampi <wub@partyvan.eu>",
16
-    "Luminarys <postmaster@gensok.io>"
17
-  ],
18
-  "license": "MIT",
19 10
   "bugs": {
20
-    "url": "https://github.com/pomf/pomf/issues"
11
+    "url": "https://github.com/WeebDev/loli-safe/issues"
21 12
   },
22
-  "devDependencies": {
23
-    "clean-css": "^3.4.18",
24
-    "uglify-js": "^2.6.2",
25
-    "swig": "^1.4.2"
13
+  "engines": {
14
+    "node": ">=7.0.0"
26 15
   },
16
+  "license": "MIT",
27 17
   "dependencies": {
28
-    "htmlmin": "0.0.6"
18
+    "bcrypt": "^1.0.3",
19
+    "body-parser": "^1.18.2",
20
+    "express": "^4.16.1",
21
+    "express-handlebars": "^3.0.0",
22
+    "express-rate-limit": "^2.11.0",
23
+    "fluent-ffmpeg": "^2.1.2",
24
+    "gm": "^1.23.1",
25
+    "helmet": "^3.11.0",
26
+    "jszip": "^3.1.5",
27
+    "knex": "^0.14.4",
28
+    "multer": "^1.3.0",
29
+    "randomstring": "^1.1.5",
30
+    "sqlite3": "^3.1.13"
31
+  },
32
+  "devDependencies": {
33
+    "eslint": "^4.18.1",
34
+    "eslint-config-aqua": "^1.4.1"
35
+  },
36
+  "eslintConfig": {
37
+    "extends": [
38
+      "aqua"
39
+    ],
40
+    "env": {
41
+      "browser": true,
42
+      "node": true
43
+    },
44
+    "rules": {
45
+      "func-names": 0
46
+    }
29 47
   }
30 48
 }

+ 60
- 0
pages/album.html View File

@@ -0,0 +1,60 @@
1
+<!DOCTYPE html>
2
+<html>
3
+	<head>
4
+		<meta name="description" content="A pomf-like file uploading service that doesn't suck.">
5
+		<meta name="keywords" content="upload,lolisareinthe.club,file,images,hosting">
6
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
8
+
9
+		<link rel="apple-touch-icon" sizes="180x180" href="https://lolisareinthe.club/images/icons/apple-touch-icon.png?v=XBreOJMe24">
10
+		<link rel="icon" type="image/png" href="https://lolisareinthe.club/images/icons/favicon-32x32.png?v=XBreOJMe24" sizes="32x32">
11
+		<link rel="icon" type="image/png" href="https://lolisareinthe.club/images/icons/favicon-16x16.png?v=XBreOJMe24" sizes="16x16">
12
+		<link rel="manifest" href="https://lolisareinthe.club/images/icons/manifest.json?v=XBreOJMe24">
13
+		<link rel="mask-icon" href="https://lolisareinthe.club/images/icons/safari-pinned-tab.svg?v=XBreOJMe24" color="#5bbad5">
14
+		<link rel="shortcut icon" href="https://lolisareinthe.club/images/icons/favicon.ico?v=XBreOJMe24">
15
+		<meta name="apple-mobile-web-app-title" content="lolisareinthe.club">
16
+		<meta name="application-name" content="lolisareinthe.club">
17
+		<meta name="msapplication-config" content="https://lolisareinthe.club/images/icons/browserconfig.xml?v=XBreOJMe24">
18
+		<meta name="theme-color" content="#ffffff">
19
+
20
+		<meta property="og:url" content="https://lolisareinthe.club" />
21
+		<meta property="og:type" content="website" />
22
+		<meta property="og:title" content="lolisareinthe.club | A small safe worth protecting." />
23
+		<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
24
+		<meta property="og:image" content="http://lolisareinthe.club/images/logo_square.png" />
25
+		<meta property="og:image:secure_url" content="https://lolisareinthe.club/images/logo_square.png" />
26
+
27
+		<meta name="twitter:card" content="summary">
28
+		<meta name="twitter:title" content="lolisareinthe.club | A small safe worth protecting.">
29
+		<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
30
+		<meta name="twitter:image" content="https://listen.moe/files/images/logo_square.png">
31
+		<meta name="twitter:image:src" content="https://lolisareinthe.club/images/logo_square.png">
32
+
33
+		<title>lolisareinthe.club - A small safe worth protecting.</title>
34
+		<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.0/css/bulma.min.css">
35
+		<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
36
+		<link rel="stylesheet" type="text/css" href="/css/style.css">
37
+		<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
38
+		<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
39
+		<script type="text/javascript" src="/js/album.js"></script>
40
+	</head>
41
+
42
+	<body>
43
+
44
+		<section class="hero is-fullheight">
45
+			<div class="hero-head">
46
+				<div class="container">
47
+					<h1 class="title" id='title' style='margin-top: 1.5rem;'></h1>
48
+					<h1 class="subtitle" id='count'></h1>
49
+					<hr>
50
+				</div>
51
+			</div>
52
+			<div class="hero-body">
53
+				<div class="container" id='container'>
54
+
55
+				</div>
56
+			</div>
57
+		</section>
58
+
59
+	</body>
60
+</html>

+ 87
- 0
pages/auth.html View File

@@ -0,0 +1,87 @@
1
+<!DOCTYPE html>
2
+<html>
3
+	<head>
4
+
5
+		<meta name="description" content="A pomf-like file uploading service that doesn't suck.">
6
+		<meta name="keywords" content="upload,lolisareinthe.club,file,images,hosting">
7
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
9
+
10
+		<link rel="apple-touch-icon" sizes="180x180" href="https://lolisareinthe.club/images/icons/apple-touch-icon.png?v=XBreOJMe24">
11
+		<link rel="icon" type="image/png" href="https://lolisareinthe.club/images/icons/favicon-32x32.png?v=XBreOJMe24" sizes="32x32">
12
+		<link rel="icon" type="image/png" href="https://lolisareinthe.club/images/icons/favicon-16x16.png?v=XBreOJMe24" sizes="16x16">
13
+		<link rel="manifest" href="https://lolisareinthe.club/images/icons/manifest.json?v=XBreOJMe24">
14
+		<link rel="mask-icon" href="https://lolisareinthe.club/images/icons/safari-pinned-tab.svg?v=XBreOJMe24" color="#5bbad5">
15
+		<link rel="shortcut icon" href="https://lolisareinthe.club/images/icons/favicon.ico?v=XBreOJMe24">
16
+		<meta name="apple-mobile-web-app-title" content="lolisareinthe.club">
17
+		<meta name="application-name" content="lolisareinthe.club">
18
+		<meta name="msapplication-config" content="https://lolisareinthe.club/images/icons/browserconfig.xml?v=XBreOJMe24">
19
+		<meta name="theme-color" content="#ffffff">
20
+
21
+		<meta property="og:url" content="https://lolisareinthe.club" />
22
+		<meta property="og:type" content="website" />
23
+		<meta property="og:title" content="lolisareinthe.club | A small safe worth protecting." />
24
+		<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
25
+		<meta property="og:image" content="http://lolisareinthe.club/images/logo_square.png" />
26
+		<meta property="og:image:secure_url" content="https://lolisareinthe.club/images/logo_square.png" />
27
+
28
+		<meta name="twitter:card" content="summary">
29
+		<meta name="twitter:title" content="lolisareinthe.club | A small safe worth protecting.">
30
+		<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
31
+		<meta name="twitter:image" content="https://listen.moe/files/images/logo_square.png">
32
+		<meta name="twitter:image:src" content="https://lolisareinthe.club/images/logo_square.png">
33
+
34
+		<title>lolisareinthe.club - A small safe worth protecting.</title>
35
+		<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.0/css/bulma.min.css">
36
+		<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
37
+		<link rel="stylesheet" type="text/css" href="/css/style.css">
38
+		<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
39
+		<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
40
+		<script type="text/javascript" src="https://use.fontawesome.com/cd26baa9bd.js"></script>
41
+		<script type="text/javascript" src="/js/auth.js"></script>
42
+	</head>
43
+	<body>
44
+
45
+		<style type="text/css">
46
+			section#login {
47
+				background-color: #232629;
48
+			}
49
+		</style>
50
+
51
+		<section id='login' class="hero is-fullheight">
52
+			<div class="hero-body">
53
+				<div class="container">
54
+					<h1 class="title">
55
+						Dashboard Access
56
+					</h1>
57
+					<h2 class="subtitle">
58
+						Login or register
59
+					</h2>
60
+					<div class="columns">
61
+						<div class="column">
62
+							<p class="control">
63
+								<input id='user' class="input" type="text" placeholder="Your username">
64
+							</p>
65
+							<p class="control">
66
+								<input id='pass' class="input" type="password" placeholder="Your password">
67
+							</p>
68
+
69
+							<p class="control has-addons is-pulled-right">
70
+								<a class="button" id='registerBtn' onclick="page.do('register')">
71
+									<span>Register</span>
72
+								</a>
73
+								<a class="button" id='loginBtn' onclick="page.do('login')">
74
+									<span>Log in</span>
75
+								</a>
76
+							</p>
77
+
78
+						</div>
79
+						<div class="column is-hidden-mobile"></div>
80
+						<div class="column is-hidden-mobile"></div>
81
+					</div>
82
+				</div>
83
+			</div>
84
+		</section>
85
+
86
+	</body>
87
+</html>

+ 100
- 0
pages/dashboard.html View File

@@ -0,0 +1,100 @@
1
+<!DOCTYPE html>
2
+<html>
3
+	<head>
4
+
5
+		<meta name="description" content="A pomf-like file uploading service that doesn't suck.">
6
+		<meta name="keywords" content="upload,lolisareinthe.club,file,images,hosting">
7
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
9
+
10
+		<link rel="apple-touch-icon" sizes="180x180" href="https://lolisareinthe.club/images/icons/apple-touch-icon.png?v=XBreOJMe24">
11
+		<link rel="icon" type="image/png" href="https://lolisareinthe.club/images/icons/favicon-32x32.png?v=XBreOJMe24" sizes="32x32">
12
+		<link rel="icon" type="image/png" href="https://lolisareinthe.club/images/icons/favicon-16x16.png?v=XBreOJMe24" sizes="16x16">
13
+		<link rel="manifest" href="https://lolisareinthe.club/images/icons/manifest.json?v=XBreOJMe24">
14
+		<link rel="mask-icon" href="https://lolisareinthe.club/images/icons/safari-pinned-tab.svg?v=XBreOJMe24" color="#5bbad5">
15
+		<link rel="shortcut icon" href="https://lolisareinthe.club/images/icons/favicon.ico?v=XBreOJMe24">
16
+		<meta name="apple-mobile-web-app-title" content="lolisareinthe.club">
17
+		<meta name="application-name" content="lolisareinthe.club">
18
+		<meta name="msapplication-config" content="https://lolisareinthe.club/images/icons/browserconfig.xml?v=XBreOJMe24">
19
+		<meta name="theme-color" content="#ffffff">
20
+
21
+		<meta property="og:url" content="https://lolisareinthe.club" />
22
+		<meta property="og:type" content="website" />
23
+		<meta property="og:title" content="lolisareinthe.club | A small safe worth protecting." />
24
+		<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
25
+		<meta property="og:image" content="http://lolisareinthe.club/images/logo_square.png" />
26
+		<meta property="og:image:secure_url" content="https://lolisareinthe.club/images/logo_square.png" />
27
+
28
+		<meta name="twitter:card" content="summary">
29
+		<meta name="twitter:title" content="lolisareinthe.club | A small safe worth protecting.">
30
+		<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
31
+		<meta name="twitter:image" content="https://listen.moe/files/images/logo_square.png">
32
+		<meta name="twitter:image:src" content="https://lolisareinthe.club/images/logo_square.png">
33
+
34
+		<title>lolisareinthe.club - A small safe worth protecting.</title>
35
+		<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.0/css/bulma.min.css">
36
+		<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
37
+		<link rel="stylesheet" type="text/css" href="/css/style.css">
38
+		<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
39
+		<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
40
+		<script type="text/javascript" src="https://use.fontawesome.com/cd26baa9bd.js"></script>
41
+		<script type="text/javascript" src="/js/dashboard.js"></script>
42
+	</head>
43
+	<body>
44
+
45
+		<section id='auth' class="hero is-light is-fullheight">
46
+
47
+			<div class="hero-body">
48
+				<div class="container">
49
+					<h1 class="title">
50
+						Admin dashboard
51
+					</h1>
52
+					<h2 class="subtitle">
53
+						<p class="control has-addons">
54
+							<input id='token' class="input is-danger" type="text" placeholder="Your admin token">
55
+							<a id='tokenSubmit' class="button is-danger is-outlined">Check</a>
56
+						</p>
57
+					</h2>
58
+				</div>
59
+			</div>
60
+
61
+		</section>
62
+
63
+		<section id='dashboard' class="section">
64
+
65
+			<div id="panel" class="container">
66
+				<h1 class="title">Dashboard</h1>
67
+				<h2 class="subtitle">A simple <strong>dashboard</strong>, to sort your uploaded stuff</h2>
68
+				<hr>
69
+				<div class="columns">
70
+					<div class="column is-3">
71
+						<aside class="menu" id="menu">
72
+							<p class="menu-label">General</p>
73
+							<ul class="menu-list">
74
+								<li><a href="/">Frontpage</a></li>
75
+								<li><a id="itemUploads" onclick="panel.getUploads()">Uploads</a></li>
76
+							</ul>
77
+							<p class="menu-label">Albums</p>
78
+							<ul class="menu-list">
79
+								<li><a id="itemManageGallery" onclick="panel.getAlbums()">Manage your albums</a></li>
80
+								<li>
81
+									<ul id='albumsContainer'></ul>
82
+								</li>
83
+							</ul>
84
+							<p class="menu-label">Administration</p>
85
+							<ul class="menu-list">
86
+								<li><a id="itemTokens" onclick="panel.changeToken()">Change your token</a></li>
87
+								<li><a id="itemPassword" onclick="panel.changePassword()">Change your password</a></li>
88
+								<li><a id="itemLogout"onclick="panel.logout()">Logout</a></li>
89
+							</ul>
90
+						</aside>
91
+					</div>
92
+					<div class="column has-text-centered" id='page'>
93
+						<img src="/images/logo.png">
94
+					</div>
95
+				</div>
96
+			</div>
97
+
98
+		</section>
99
+	</body>
100
+</html>

+ 47
- 0
pages/error/404.html View File

@@ -0,0 +1,47 @@
1
+<!DOCTYPE html>
2
+<html>
3
+	<head>
4
+		<title>loli-safe</title>
5
+
6
+		<link href='//fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
7
+
8
+		<style>
9
+			html, body {
10
+				height: 100%;
11
+			}
12
+
13
+			body {
14
+				margin: 0;
15
+				padding: 0;
16
+				width: 100%;
17
+				color: #B0BEC5;
18
+				display: table;
19
+				font-weight: 100;
20
+				font-family: 'Lato';
21
+			}
22
+
23
+			.container {
24
+				text-align: center;
25
+				display: table-cell;
26
+				vertical-align: middle;
27
+			}
28
+
29
+			.content {
30
+				text-align: center;
31
+				display: inline-block;
32
+			}
33
+
34
+			.title {
35
+				font-size: 72px;
36
+				margin-bottom: 40px;
37
+			}
38
+		</style>
39
+	</head>
40
+	<body>
41
+		<div class="container">
42
+			<div class="content">
43
+				<div class="title">Page not found.</div>
44
+			</div>
45
+		</div>
46
+	</body>
47
+</html>

+ 47
- 0
pages/error/500.html View File

@@ -0,0 +1,47 @@
1
+<!DOCTYPE html>
2
+<html>
3
+	<head>
4
+		<title>loli-safe</title>
5
+
6
+		<link href='//fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
7
+
8
+		<style>
9
+			html, body {
10
+				height: 100%;
11
+			}
12
+
13
+			body {
14
+				margin: 0;
15
+				padding: 0;
16
+				width: 100%;
17
+				color: #B0BEC5;
18
+				display: table;
19
+				font-weight: 100;
20
+				font-family: 'Lato';
21
+			}
22
+
23
+			.container {
24
+				text-align: center;
25
+				display: table-cell;
26
+				vertical-align: middle;
27
+			}
28
+
29
+			.content {
30
+				text-align: center;
31
+				display: inline-block;
32
+			}
33
+
34
+			.title {
35
+				font-size: 72px;
36
+				margin-bottom: 40px;
37
+			}
38
+		</style>
39
+	</head>
40
+	<body>
41
+		<div class="container">
42
+			<div class="content">
43
+				<div class="title">Internal server error.</div>
44
+			</div>
45
+		</div>
46
+	</body>
47
+</html>

+ 108
- 0
pages/faq.html View File

@@ -0,0 +1,108 @@
1
+<!DOCTYPE html>
2
+<html>
3
+	<head>
4
+		<meta name="description" content="A pomf-like file uploading service that doesn't suck.">
5
+		<meta name="keywords" content="upload,lolisareinthe.club,file,images,hosting">
6
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
8
+
9
+		<link rel="apple-touch-icon" sizes="180x180" href="https://lolisareinthe.club/images/icons/apple-touch-icon.png?v=XBreOJMe24">
10
+		<link rel="icon" type="image/png" href="https://lolisareinthe.club/images/icons/favicon-32x32.png?v=XBreOJMe24" sizes="32x32">
11
+		<link rel="icon" type="image/png" href="https://lolisareinthe.club/images/icons/favicon-16x16.png?v=XBreOJMe24" sizes="16x16">
12
+		<link rel="manifest" href="https://lolisareinthe.club/images/icons/manifest.json?v=XBreOJMe24">
13
+		<link rel="mask-icon" href="https://lolisareinthe.club/images/icons/safari-pinned-tab.svg?v=XBreOJMe24" color="#5bbad5">
14
+		<link rel="shortcut icon" href="https://lolisareinthe.club/images/icons/favicon.ico?v=XBreOJMe24">
15
+		<meta name="apple-mobile-web-app-title" content="lolisareinthe.club">
16
+		<meta name="application-name" content="lolisareinthe.club">
17
+		<meta name="msapplication-config" content="https://lolisareinthe.club/images/icons/browserconfig.xml?v=XBreOJMe24">
18
+		<meta name="theme-color" content="#ffffff">
19
+
20
+		<meta property="og:url" content="https://lolisareinthe.club" />
21
+		<meta property="og:type" content="website" />
22
+		<meta property="og:title" content="lolisareinthe.club | A small safe worth protecting." />
23
+		<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
24
+		<meta property="og:image" content="http://lolisareinthe.club/images/logo_square.png" />
25
+		<meta property="og:image:secure_url" content="https://lolisareinthe.club/images/logo_square.png" />
26
+
27
+		<meta name="twitter:card" content="summary">
28
+		<meta name="twitter:title" content="lolisareinthe.club | A small safe worth protecting.">
29
+		<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
30
+		<meta name="twitter:image" content="https://listen.moe/files/images/logo_square.png">
31
+		<meta name="twitter:image:src" content="https://lolisareinthe.club/images/logo_square.png">
32
+
33
+		<title>lolisareinthe.club - A small safe worth protecting.</title>
34
+		<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.0/css/bulma.min.css">
35
+		<link rel="stylesheet" type="text/css" href="/css/style.css">
36
+	</head>
37
+
38
+	<body>
39
+		<section class="hero is-fullheight has-text-centered" id="home">
40
+			<div class="hero-body">
41
+				<div class="container has-text-left">
42
+
43
+					<h2 class='subtitle'>What is lolisareinthe.club?</h2>
44
+					<article class="message">
45
+						<div class="message-body">
46
+							lolisareinthe.club is a simple to use free file hosting service.</span> It lets you share your photos, documents, music, videos and more with others online.</p>
47
+						</div>
48
+					</article>
49
+
50
+					<h2 class='subtitle'>What works are allowed?</h2>
51
+					<article class="message">
52
+						<div class="message-body">
53
+							lolisareinthe.club welcomes uploading all works, as long as the work is legal in France and you have the legal right to publish the work on our service.</p>
54
+<p>As an exception to this policy to prevent abuse, we do not allow malware on our service. Any malware that could be used to infect other computers may be removed from our service at our discretion.</p>
55
+						</div>
56
+					</article>
57
+
58
+					<h2 class='subtitle'>How can I keep track of my uploads?</h2>
59
+					<article class="message">
60
+						<div class="message-body">
61
+							Simply create a user on the site and every upload will be associated with your account, granting you access to your uploaded files through our dashboard.
62
+						</div>
63
+					</article>
64
+
65
+					<h2 class='subtitle'>What are albums?</h2>
66
+					<article class="message">
67
+						<div class="message-body">
68
+							Albums are a simple way of sorting uploads together. Right now you can create albums through the dashboard and use them only with <a target="_blank" href="https://chrome.google.com/webstore/detail/loli-safe-uploader/enkkmplljfjppcdaancckgilmgoiofnj">our chrome extension</a> which will enable you to <strong>right click -> send to lolisareinthe.club</strong> or to a desired album if you have any.
69
+						</div>
70
+					</article>
71
+
72
+					<h2 class='subtitle'>Do you keep logs of uploaded works?</h2>
73
+					<article class="message">
74
+						<div class="message-body">
75
+							We don't collect or log any data of our users in respect for privacy. We only have files uploaded by our users.
76
+						</div>
77
+					</article>
78
+					<h2 class='subtitle'>Can you remove my copyrighted work?</h2>
79
+					<article class="message">
80
+						<div class="message-body">
81
+							Please submit your copyright takedown notice to <a href="mailto:abuse@lolisareinthe.club">abuse@lolisareinthe.club</a>. We will handle your notice within 24 hours and disable access to the infringing work after receiving a notice compliant with the Copyright Act 1968 (France).
82
+						</div>
83
+					</article>
84
+					<h2 class='subtitle'>Can you remove works that are defaming me or otherwise infringing my non-copyright rights?</h2>
85
+					<article class="message">
86
+						<div class="message-body">
87
+							lolisareinthe.club respects takedowns for other works when accompanied with a certified French court order. If you are unable to obtain the order, a preliminary injuction or court order is typically also sufficient. Please forward the notice to <a href="mailto:abuse@lolisareinthe.club">abuse@lolisareinthe.club</a>
88
+						</div>
89
+					</article>
90
+					<h2 class='subtitle'>Can you remove illegal works?</h2>
91
+					<article class="message">
92
+						<div class="message-body">
93
+							Please contact the appropriate law enforcement agency if you notice illegal works hosted on lolisareinthe.club. We have not been trained or qualified to investigate and fight crimes and enforce the law, so it's not appropriate to send accusations of illegal activity to us. <strong>You must contact the appropriate law enforcement office.</strong> They may then contact us if appropriate.</p> <p>If you are an French law enforcement official and you need our assistance, please contact <a href="mailto:abuse@lolisareinthe.club">abuse@lolisareinthe.club</a>. If you are a law enforcement official from another country, we may voluntarily cooperate if the crime you are investigating would also be illegal in France.</p>
94
+						</div>
95
+					</article>
96
+					<h2 class='subtitle'>I have a question...</h2>
97
+					<article class="message">
98
+						<div class="message-body">
99
+							Send us an email at <a href="mailto:contact@lolisareinthe.club">contact@lolisareinthe.club</a> and let's talk!
100
+						</div>
101
+					</article>
102
+					
103
+				</div>
104
+			</div>
105
+		</section>
106
+
107
+	</body>
108
+</html>

+ 93
- 0
pages/home.html View File

@@ -0,0 +1,93 @@
1
+<!DOCTYPE html>
2
+<html>
3
+	<head>
4
+		<meta name="description" content="A pomf-like file uploading service that doesn't suck.">
5
+		<meta name="keywords" content="upload,lolisareinthe.club,file,images,hosting">
6
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+		<meta http-equiv="X-UA-Compatible" content="IE=edge">
8
+
9
+		<link rel="apple-touch-icon" sizes="180x180" href="https://lolisareinthe.club/images/icons/apple-touch-icon.png?v=XBreOJMe24">
10
+		<link rel="icon" type="image/png" href="https://lolisareinthe.club/images/icons/favicon-32x32.png?v=XBreOJMe24" sizes="32x32">
11
+		<link rel="icon" type="image/png" href="https://lolisareinthe.club/images/icons/favicon-16x16.png?v=XBreOJMe24" sizes="16x16">
12
+		<link rel="manifest" href="https://lolisareinthe.club/images/icons/manifest.json?v=XBreOJMe24">
13
+		<link rel="mask-icon" href="https://lolisareinthe.club/images/icons/safari-pinned-tab.svg?v=XBreOJMe24" color="#5bbad5">
14
+		<link rel="shortcut icon" href="https://lolisareinthe.club/images/icons/favicon.ico?v=XBreOJMe24">
15
+		<meta name="apple-mobile-web-app-title" content="lolisareinthe.club">
16
+		<meta name="application-name" content="lolisareinthe.club">
17
+		<meta name="msapplication-config" content="https://lolisareinthe.club/images/icons/browserconfig.xml?v=XBreOJMe24">
18
+		<meta name="theme-color" content="#0000">
19
+
20
+		<meta property="og:url" content="https://lolisareinthe.club" />
21
+		<meta property="og:type" content="website" />
22
+		<meta property="og:title" content="lolisareinthe.club | A small safe worth protecting." />
23
+		<meta property="og:description" content="A pomf-like file uploading service that doesn't suck." />
24
+		<meta property="og:image" content="http://lolisareinthe.club/images/logo_square.png" />
25
+		<meta property="og:image:secure_url" content="https://lolisareinthe.club/images/logo_square.png" />
26
+
27
+		<meta name="twitter:card" content="summary">
28
+		<meta name="twitter:title" content="lolisareinthe.club | A small safe worth protecting.">
29
+		<meta name="twitter:description" content="A pomf-like file uploading service that doesn't suck.">
30
+		<meta name="twitter:image" content="https://listen.moe/files/images/logo_square.png">
31
+		<meta name="twitter:image:src" content="https://lolisareinthe.club/images/logo_square.png">
32
+
33
+		<title>lolisareinthe.club - A small safe worth protecting.</title>
34
+		<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.0/css/bulma.min.css">
35
+		<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
36
+		<link rel="stylesheet" type="text/css" href="/css/style.css">
37
+		<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
38
+		<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.3.0/min/dropzone.min.js"></script>
39
+		<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script>
40
+		<script type="text/javascript" src="/js/home.js"></script>
41
+	</head>
42
+
43
+	<body>
44
+		<section class="hero is-fullheight has-text-centered" id="home">
45
+			<div class="hero-body">
46
+				<div class="container">
47
+					<p id="b">
48
+						<img class="logo" src="/images/logo_smol.png">
49
+					</p>
50
+					<h1 class="title">lolisareinthe.club</h1>
51
+					<h2 class="subtitle">A <strong>modern</strong> self-hosted file upload service</h2>
52
+
53
+					<h3 class="subtitle" id="maxFileSize"></h3>
54
+					<div class="columns">
55
+						<div class="column is-hidden-mobile"></div>
56
+						<div class="column" id="uploadContainer">
57
+							<a id="loginToUpload" href="/auth" class="button is-danger">Running in private mode. Log in to upload.</a>
58
+							<div class="field" id="albumDiv" style="display: none">
59
+								<p class="control select-wrapper">
60
+									<span class="select">
61
+										<select id="albumSelect">
62
+											<option value="">Upload to album</option>
63
+										</select>
64
+									</span>
65
+								</p>
66
+							</div>
67
+						</div>
68
+						<div class="column is-hidden-mobile"></div>
69
+					</div>
70
+
71
+					<div id="uploads">
72
+						<div id="template" class="columns">
73
+							<div class="column is-hidden-mobile"></div>
74
+							<div class="column">
75
+								<progress class="progress is-small is-danger" value="0" max="100" data-dz-uploadprogress></progress>
76
+								<p data-dz-errormessage></p>
77
+								<p class="link"></p>
78
+							</div>
79
+							<div class="column is-hidden-mobile"></div>
80
+						</div>
81
+					</div>
82
+
83
+					<h3 class="subtitle"><a href="/auth" id="loginLinkText"></a></h3>
84
+					<h3 id="links">
85
+						<a href="https://github.com/WeebDev/lolisareinthe.club" target="_blank" class="is-danger">View on GitHub</a><span>|</span><a id="ShareX" href="https://lolisareinthe.club/sharex.txt">ShareX</a><span>|</span><a href="https://chrome.google.com/webstore/detail/loli-safe-uploader/enkkmplljfjppcdaancckgilmgoiofnj" target="_blank" class="is-danger">Chrome extension</a><span>|</span><a href="/faq" class="is-danger">FAQ</a><span>|</span><a href="/auth" target="_blank" class="is-danger">Dashboard</a>
86
+					</h3>
87
+
88
+				</div>
89
+			</div>
90
+		</section>
91
+
92
+	</body>
93
+</html>

+ 210
- 0
public/css/style.css View File

@@ -0,0 +1,210 @@
1
+/* ------------------
2
+		HOME
3
+------------------ */
4
+
5
+section#home #b {
6
+	-webkit-animation-delay: 0.5s;
7
+	animation-delay: 0.5s;
8
+	-webkit-animation-duration: 1.5s;
9
+	animation-duration: 1.5s;
10
+	-webkit-animation-fill-mode: both;
11
+	animation-fill-mode: both;
12
+	-webkit-animation-name: floatUp;
13
+	animation-name: floatUp;
14
+	-webkit-animation-timing-function: cubic-bezier(0, 0.71, 0.29, 1);
15
+	animation-timing-function: cubic-bezier(0, 0.71, 0.29, 1);
16
+	border-radius: 24px;
17
+	display: inline-block;
18
+	height: 240px;
19
+	margin-bottom: 40px;
20
+	position: relative;
21
+	vertical-align: top;
22
+	width: 240px;
23
+	box-shadow: 0 20px 60px rgba(10, 10, 10, 0.05), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2);
24
+}
25
+
26
+section#home div#dropzone {
27
+	border: 1px solid #dbdbdb;
28
+	background-color: rgba(0, 0, 0, 0);
29
+    border-color: #ff3860;
30
+    color: #ff3860;
31
+    display: none;
32
+    width: 100%;
33
+    border-radius: 3px;
34
+    box-shadow: none;
35
+    height: 2.5em;
36
+    -webkit-box-align: center;
37
+    -ms-flex-align: center;
38
+    align-items: center;
39
+    user-select: none;
40
+    justify-content: center;
41
+    padding-left: .75em;
42
+    padding-right: .75em;
43
+    text-align: center;
44
+    cursor: pointer;   
45
+}
46
+
47
+section#home div#uploads, section#home p#tokenContainer, section#home a#panel { display: none; }
48
+section#home div#dropzone:hover { background-color: #ff3860; border-color: #ff3860; color: #fff; }
49
+section#home h3#maxFileSize { font-size: 14px; }
50
+section#home h3#links span { padding-left: 5px; padding-right: 5px; }
51
+section#home img.logo { height: 200px; margin-top: 20px; }
52
+section#home .dz-preview .dz-details { display: flex; }
53
+section#home .dz-preview .dz-details .dz-size, section#home .dz-preview .dz-details .dz-filename { flex: 1; }
54
+section#home .dz-preview img, section#home .dz-preview .dz-success-mark, section#home .dz-preview .dz-error-mark { display: none; }
55
+section#home div#uploads { margin-bottom: 25px; }
56
+
57
+@keyframes floatUp {
58
+	0% {
59
+		opacity: 0;
60
+		box-shadow: 0 0 0 rgba(10, 10, 10, 0), 0 0 0 rgba(10, 10, 10, 0), 0 0 0 rgba(10, 10, 10, 0);
61
+		-webkit-transform: scale(0.86);
62
+		transform: scale(0.86);
63
+	}
64
+	25% { opacity: 100; }
65
+	67% {
66
+		box-shadow: 0 0 0 rgba(10, 10, 10, 0), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2);
67
+		-webkit-transform: scale(1);
68
+		transform: scale(1);
69
+	}
70
+	100% {
71
+		box-shadow: 0 20px 60px rgba(10, 10, 10, 0.05), 0 5px 10px rgba(10, 10, 10, 0.1), 0 1px 1px rgba(10, 10, 10, 0.2);
72
+		-webkit-transform: scale(1);
73
+		transform: scale(1);
74
+	}
75
+}
76
+
77
+/* ------------------
78
+		PANEL
79
+------------------ */
80
+
81
+section#login input, section#login p.control a.button {
82
+	border-left: 0px;
83
+    border-top: 0px;
84
+    border-right: 0px;
85
+    border-radius: 0px;
86
+    box-shadow: 0 0 0;
87
+}
88
+
89
+section#login p.control a.button { margin-left: 10px; }
90
+section#login p.control a#loginBtn { border-right: 0px; }
91
+section#login p.control a#registerBtn { border-left: 0px; }
92
+
93
+
94
+section#auth, section#dashboard { display: none }
95
+section#auth input { background: rgba(0, 0, 0, 0); }
96
+section#auth input, section#auth a {
97
+	border-left: 0px;
98
+    border-top: 0px;
99
+    border-right: 0px;
100
+    border-radius: 0px;
101
+    box-shadow: 0 0 0;
102
+}
103
+
104
+section#dashboard .table { font-size: 12px }
105
+section#dashboard div#table div.column { display:flex; width: 200px; height: 200px; margin: 9px; background: #232629; overflow: hidden; align-items: center; }
106
+section#dashboard div#table div.column a { width: 100%; }
107
+section#dashboard div#table div.column a img { width:200px; }
108
+
109
+html {
110
+	background-color: #232629;
111
+  }
112
+  .section {
113
+	background: none;
114
+  }
115
+  .menu-list a {
116
+	color: #2980b9;
117
+  }
118
+  .menu-list a:hover {
119
+	color: #3daee9;
120
+	background-color: #4d4d4d;
121
+  }
122
+  .menu-list a.is-active {
123
+	color: #eff0f1;
124
+	background-color: #2980b9;
125
+  }
126
+  .button.is-primary {
127
+	background-color: #2980b9;
128
+  }
129
+  .button.is-primary.is-hovered, .button.is-primary:hover {
130
+	background-color: #2980b9;
131
+  }
132
+  .pagination a {
133
+	color: #eff0f1;
134
+	border-color: #4d4d4d;
135
+	background-color: #31363b;
136
+  }
137
+  .pagination-link:hover, .pagination-next:hover, .pagination-previous:hover {
138
+	color: #eff0f1;
139
+	border-color: #3daee9;
140
+	background-color: #31363b;
141
+  }
142
+  .label {
143
+	color: #bdc3c7;
144
+  }
145
+  .table {
146
+	color: #bdc3c7;
147
+	background-color: #31363b;
148
+  }
149
+  .table tr:hover, .table.is-striped tbody tr:nth-child(2n) {
150
+	background: none;
151
+  }
152
+  .table.is-striped tbody tr:hover, .table.is-striped tbody tr:nth-child(2n):hover, .tag {
153
+	background-color: #4d4d4d;
154
+  }
155
+  .table thead td, .table thead th {
156
+	color: #eff0f1;
157
+	background-color: #ff3860;
158
+  }
159
+  .table th {
160
+	color: #eff0f1;
161
+  }
162
+  .table td, .table th {
163
+	border: 0;
164
+  }
165
+  section#dashboard div#table div.column {
166
+	background-color: #31363b;
167
+  }
168
+  .menu-list li ul {
169
+	border-left-color: #898b8d;
170
+  }
171
+.select-wrapper {
172
+	text-align: center;
173
+	margin-bottom: 10px;
174
+}
175
+
176
+
177
+.hero {
178
+	  background-color: #232629;
179
+	  color: #eff0f1;
180
+	}
181
+	
182
+	.title {
183
+	  color: #eff0f1;
184
+	}
185
+	
186
+	.subtitle {
187
+	  color: #bdc3c7;
188
+	}
189
+	
190
+	.subtitle strong {
191
+	  color: #bdc3c7;