Merge branch 'dev' into master
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"comments": false,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"targets": {
|
||||||
|
"browsers": [
|
||||||
|
"last 6 Chrome major versions",
|
||||||
|
"last 6 Firefox major versions",
|
||||||
|
"last 4 Safari major versions",
|
||||||
|
"last 4 Edge major versions",
|
||||||
|
"last 3 iOS major versions",
|
||||||
|
"last 3 Android major versions",
|
||||||
|
"last 2 ChromeAndroid major versions",
|
||||||
|
"Explorer 11"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"stage-2"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,27 +1,14 @@
|
|||||||
{
|
{
|
||||||
"extends": "standard",
|
"extends": "requarks",
|
||||||
"env": {
|
"env": {
|
||||||
"node": true,
|
"node": true,
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"jest": true
|
"jest": true
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
// Client
|
|
||||||
"document": false,
|
"document": false,
|
||||||
"navigator": false,
|
"navigator": false,
|
||||||
"window": false,
|
"window": false,
|
||||||
"siteLang": false,
|
"FuseBox": false
|
||||||
"socket": true,
|
|
||||||
"wikijs": true,
|
|
||||||
"FuseBox": false,
|
|
||||||
// Server
|
|
||||||
"appconfig": true,
|
|
||||||
"appdata": true,
|
|
||||||
"ROOTPATH": true,
|
|
||||||
"SERVERPATH": true,
|
|
||||||
"IS_DEBUG": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"space-before-function-paren": 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,11 +237,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
### Changed
|
### Changed
|
||||||
- Updated dependencies + snyk policy
|
- Updated dependencies + snyk policy
|
||||||
|
|
||||||
[v1.0.11]: https://github.com/Requarks/wiki/releases/tag/v1.0.11
|
|
||||||
[v1.0.10]: https://github.com/Requarks/wiki/releases/tag/v1.0.10
|
|
||||||
[v1.0.9]: https://github.com/Requarks/wiki/releases/tag/v1.0.9
|
|
||||||
[v1.0.8]: https://github.com/Requarks/wiki/releases/tag/v1.0.8
|
|
||||||
[v1.0.7]: https://github.com/Requarks/wiki/releases/tag/v1.0.7
|
|
||||||
[v1.0.6]: https://github.com/Requarks/wiki/releases/tag/v1.0.6
|
[v1.0.6]: https://github.com/Requarks/wiki/releases/tag/v1.0.6
|
||||||
[v1.0.5]: https://github.com/Requarks/wiki/releases/tag/v1.0.5
|
[v1.0.5]: https://github.com/Requarks/wiki/releases/tag/v1.0.5
|
||||||
[v1.0.4]: https://github.com/Requarks/wiki/releases/tag/v1.0.4
|
[v1.0.4]: https://github.com/Requarks/wiki/releases/tag/v1.0.4
|
||||||
|
|||||||
|
After Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 134 KiB |
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="100%" height="100%" viewBox="0 0 159 158" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
||||||
|
<path fill="#51b5d7" d="M87.477,3.864c-4.583,-4.583 -12.024,-4.583 -16.608,0l-66.432,66.432c-4.583,4.583 -4.583,12.025 0,16.608l67.352,67.352c4.583,4.583 12.025,4.583 16.608,0l66.432,-66.432c4.583,-4.583 4.583,-12.025 0,-16.608l-67.352,-67.352Zm-14.477,44.282c-3.497,-2.176 -5.826,-6.054 -5.826,-10.472c0,-6.803 5.523,-12.326 12.326,-12.326c6.803,0 12.326,5.523 12.326,12.326c0,4.418 -2.329,8.296 -5.826,10.472l0,61.157c0.415,0.214 0.818,0.447 1.208,0.699l24.89,-24.89c-0.909,-1.718 -1.424,-3.676 -1.424,-5.753c0,-6.803 5.523,-12.326 12.326,-12.326c6.803,0 12.326,5.523 12.326,12.326c0,6.803 -5.523,12.326 -12.326,12.326c-1.493,0 -2.924,-0.266 -4.249,-0.753l-25.952,25.951c0.606,1.58 0.938,3.295 0.938,5.088c0,7.857 -6.38,14.236 -14.237,14.236c-7.857,0 -14.237,-6.379 -14.237,-14.236c0,-1.804 0.337,-3.531 0.95,-5.119l-26.466,-26.466c-1.182,0.377 -2.441,0.581 -3.747,0.581c-6.803,0 -12.326,-5.523 -12.326,-12.326c0,-6.803 5.523,-12.326 12.326,-12.326c6.803,0 12.326,5.523 12.326,12.326c0,2.254 -0.606,4.368 -1.665,6.187l25.157,25.157c0.382,-0.245 0.776,-0.473 1.182,-0.682l0,-61.157Z" />
|
||||||
|
<path fill="#97cbe1" d="M91.745,39.094l28.297,28.297c-4.527,1.116 -8.072,4.737 -9.079,9.306l-24.963,-24.963l0,-3.588c3.116,-1.939 5.306,-5.231 5.745,-9.052Zm-24.49,0c0.439,3.821 2.629,7.113 5.745,9.052l0,3.588l-24.846,24.846c-0.785,-4.655 -4.183,-8.427 -8.632,-9.753l27.733,-27.733Z" />
|
||||||
|
<circle fill="#97cbe1" cx="35.84" cy="78.701" r="6.701" />
|
||||||
|
<circle fill="#97cbe1" cx="123.16" cy="79.778" r="6.701" />
|
||||||
|
<circle fill="#97cbe1" cx="79.299" cy="37.243" r="6.701" />
|
||||||
|
<circle fill="#97cbe1" cx="79.633" cy="122.201" r="8.201" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="100%" height="100%" viewBox="0 0 500 500" preserveAspectRatio="xMinYMin meet">
|
||||||
|
<path fill="#3B5998" d="M288.714,500l0,-228.073l76.554,0l11.461,-88.885l-88.017,0l0,-56.749c0,-25.735 7.145,-43.271 44.049,-43.271l47.067,-0.022l0,-79.498c-8.141,-1.081 -36.082,-3.502 -68.584,-3.502c-67.862,0 -114.321,41.422 -114.321,117.492l0,65.55l-76.751,0l0,88.885l76.751,0l0,228.071l91.791,0l0,0.002Z" style="fill-rule:nonzero;"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 434 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg width="2500" height="2432" viewBox="0 0 256 249" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><g fill="#161614"><path d="M127.505 0C57.095 0 0 57.085 0 127.505c0 56.336 36.534 104.13 87.196 120.99 6.372 1.18 8.712-2.766 8.712-6.134 0-3.04-.119-13.085-.173-23.739-35.473 7.713-42.958-15.044-42.958-15.044-5.8-14.738-14.157-18.656-14.157-18.656-11.568-7.914.872-7.752.872-7.752 12.804.9 19.546 13.14 19.546 13.14 11.372 19.493 29.828 13.857 37.104 10.6 1.144-8.242 4.449-13.866 8.095-17.05-28.32-3.225-58.092-14.158-58.092-63.014 0-13.92 4.981-25.295 13.138-34.224-1.324-3.212-5.688-16.18 1.235-33.743 0 0 10.707-3.427 35.073 13.07 10.17-2.826 21.078-4.242 31.914-4.29 10.836.048 21.752 1.464 31.942 4.29 24.337-16.497 35.029-13.07 35.029-13.07 6.94 17.563 2.574 30.531 1.25 33.743 8.175 8.929 13.122 20.303 13.122 34.224 0 48.972-29.828 59.756-58.22 62.912 4.573 3.957 8.648 11.717 8.648 23.612 0 17.06-.148 30.791-.148 34.991 0 3.393 2.295 7.369 8.759 6.117 50.634-16.879 87.122-64.656 87.122-120.973C255.009 57.085 197.922 0 127.505 0"/><path d="M47.755 181.634c-.28.633-1.278.823-2.185.389-.925-.416-1.445-1.28-1.145-1.916.275-.652 1.273-.834 2.196-.396.927.415 1.455 1.287 1.134 1.923M54.027 187.23c-.608.564-1.797.302-2.604-.589-.834-.889-.99-2.077-.373-2.65.627-.563 1.78-.3 2.616.59.834.899.996 2.08.36 2.65M58.33 194.39c-.782.543-2.06.034-2.849-1.1-.781-1.133-.781-2.493.017-3.038.792-.545 2.05-.055 2.85 1.07.78 1.153.78 2.513-.019 3.069M65.606 202.683c-.699.77-2.187.564-3.277-.488-1.114-1.028-1.425-2.487-.724-3.258.707-.772 2.204-.555 3.302.488 1.107 1.026 1.445 2.496.7 3.258M75.01 205.483c-.307.998-1.741 1.452-3.185 1.028-1.442-.437-2.386-1.607-2.095-2.616.3-1.005 1.74-1.478 3.195-1.024 1.44.435 2.386 1.596 2.086 2.612M85.714 206.67c.036 1.052-1.189 1.924-2.705 1.943-1.525.033-2.758-.818-2.774-1.852 0-1.062 1.197-1.926 2.721-1.951 1.516-.03 2.758.815 2.758 1.86M96.228 206.267c.182 1.026-.872 2.08-2.377 2.36-1.48.27-2.85-.363-3.039-1.38-.184-1.052.89-2.105 2.367-2.378 1.508-.262 2.857.355 3.049 1.398"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg width="2443" height="2500" viewBox="0 0 256 262" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027" fill="#4285F4"/><path d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1" fill="#34A853"/><path d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782" fill="#FBBC05"/><path d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251" fill="#EB4335"/></svg>
|
||||||
|
After Width: | Height: | Size: 1018 B |
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
||||||
|
<path fill="#458BC4" d="M44.804,30.404l-20-27C24.615,3.15,24.308,3.023,24,3.023V46c0.2,0,0.401-0.061,0.573-0.181l20-14
|
||||||
|
c0.222-0.155,0.37-0.393,0.414-0.659C45.03,30.895,44.964,30.622,44.804,30.404z"/>
|
||||||
|
<path fill="#43A6DD" d="M23.196,3.405l-20,27c-0.16,0.218-0.227,0.49-0.184,0.756c0.044,0.267,0.192,0.504,0.414,0.659l20,14
|
||||||
|
C23.599,45.939,23.8,46,24,46V3.023C23.692,3.023,23.385,3.15,23.196,3.405z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 872 B |
@@ -0,0 +1,8 @@
|
|||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
|
||||||
|
<path d="M32,19c13.089,0,27-3.154,27-9S45.089,1,32,1S5,4.154,5,10S18.911,19,32,19z"/>
|
||||||
|
<path d="M32,41c13.089,0,27-3.154,27-9V14.436c-1.481,1.493-3.963,2.968-8.022,4.174C45.864,20.128,38.946,21,32,21
|
||||||
|
s-13.864-0.872-18.978-2.391C8.963,17.403,6.481,15.929,5,14.436V32C5,37.846,18.911,41,32,41z"/>
|
||||||
|
<path d="M32,63c13.089,0,27-3.154,27-9V36.436c-1.481,1.493-3.963,2.968-8.022,4.174C45.864,42.128,38.946,43,32,43
|
||||||
|
s-13.864-0.872-18.978-2.391C8.963,39.403,6.481,37.929,5,36.436V54C5,59.846,18.911,63,32,63z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 729 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg width="2490" height="2500" viewBox="0 0 256 257" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M0 36.357L104.62 22.11l.045 100.914-104.57.595L0 36.358zm104.57 98.293l.08 101.002L.081 221.275l-.006-87.302 104.494.677zm12.682-114.405L255.968 0v121.74l-138.716 1.1V20.246zM256 135.6l-.033 121.191-138.716-19.578-.194-101.84L256 135.6z" fill="#00ADEF"/></svg>
|
||||||
|
After Width: | Height: | Size: 389 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg width="2500" height="2500" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M165.964 15.838c-3.89-11.975-16.752-18.528-28.725-14.636-11.975 3.89-18.528 16.752-14.636 28.725l58.947 181.365c4.048 11.187 16.132 17.473 27.732 14.135 12.1-3.483 19.475-16.334 15.614-28.217L165.964 15.838" fill="#DFA22F"/><path d="M74.626 45.516C70.734 33.542 57.873 26.989 45.9 30.879 33.924 34.77 27.37 47.631 31.263 59.606l58.948 181.366c4.047 11.186 16.132 17.473 27.732 14.132 12.099-3.481 19.474-16.332 15.613-28.217L74.626 45.516" fill="#3CB187"/><path d="M240.162 166.045c11.975-3.89 18.526-16.75 14.636-28.726-3.89-11.973-16.752-18.527-28.725-14.636L44.708 181.632c-11.187 4.046-17.473 16.13-14.135 27.73 3.483 12.099 16.334 19.475 28.217 15.614l181.372-58.93" fill="#CE1E5B"/><path d="M82.508 217.27l43.347-14.084-14.086-43.352-43.35 14.09 14.089 43.347" fill="#392538"/><path d="M173.847 187.591c16.388-5.323 31.62-10.273 43.348-14.084l-14.088-43.36-43.35 14.09 14.09 43.354" fill="#BB242A"/><path d="M210.484 74.706c11.974-3.89 18.527-16.751 14.637-28.727-3.89-11.973-16.752-18.526-28.727-14.636L15.028 90.293C3.842 94.337-2.445 106.422.896 118.022c3.481 12.098 16.332 19.474 28.217 15.613l181.371-58.93" fill="#72C5CD"/><path d="M52.822 125.933c11.805-3.836 27.025-8.782 43.354-14.086-5.323-16.39-10.273-31.622-14.084-43.352l-43.36 14.092 14.09 43.346" fill="#248C73"/><path d="M144.16 96.256l43.356-14.088a546179.21 546179.21 0 0 0-14.089-43.36L130.07 52.9l14.09 43.356" fill="#62803A"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 1500 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
||||||
|
<path d="M1500,543c0,0 -135.753,54.677 -252.197,101.577c-106.023,42.702 -223.596,47.2 -332.571,12.724c-56.088,-17.745 -118.404,-37.46 -173.999,-55.048c-103.773,-32.831 -216.376,-22.709 -312.631,28.103c0,0 0,0.001 0,0.001c-94.821,50.055 -206.441,57.476 -307.053,20.415c-63.317,-23.322 -121.549,-44.772 -121.549,-44.772l0,417l1500,0l0,-480Z" style="fill:#2d2d2d;fill-opacity:0.282609;"/>
|
||||||
|
<path d="M1510,580c0,0 -144.155,47.882 -252.311,83.806c-74.651,24.796 -156.199,17.958 -225.679,-18.923c0,0 0,0 0,0c-62.207,-33.021 -133.629,-44.415 -203.023,-32.389c-98.381,17.051 -244.859,42.438 -352.664,61.121c-92.259,15.99 -187.076,8.079 -275.41,-22.977c-93.342,-32.818 -200.913,-70.638 -200.913,-70.638l0,466l1500,0l10,-466Z" style="fill:#2d2d2d;fill-opacity:0.550725;"/>
|
||||||
|
<path d="M1500,650c0,0 -143.367,28.581 -239.425,47.731c-56.087,11.181 -113.694,12.508 -170.237,3.922c-74.75,-11.351 -183.318,-27.838 -261.719,-39.743c-65.252,-9.909 -131.707,-8.759 -196.577,3.4c-49.655,9.308 -109.704,20.564 -158.992,29.803c-63.125,11.833 -127.839,12.479 -191.188,1.911c-111.875,-18.665 -281.862,-47.024 -281.862,-47.024l0,430l1500,0l0,-430Z" style="fill:#2d2d2d;"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 1500 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
||||||
|
<path d="M1500,543c0,0 -135.753,54.677 -252.197,101.577c-106.023,42.702 -223.596,47.2 -332.571,12.724c-56.088,-17.745 -118.404,-37.46 -173.999,-55.048c-103.773,-32.831 -216.376,-22.709 -312.631,28.103c0,0 0,0.001 0,0.001c-94.821,50.055 -206.441,57.476 -307.053,20.415c-63.317,-23.322 -121.549,-44.772 -121.549,-44.772l0,417l1500,0l0,-480Z" style="fill:#fff;fill-opacity:0.282609;"/>
|
||||||
|
<path d="M1510,580c0,0 -144.155,47.882 -252.311,83.806c-74.651,24.796 -156.199,17.958 -225.679,-18.923c0,0 0,0 0,0c-62.207,-33.021 -133.629,-44.415 -203.023,-32.389c-98.381,17.051 -244.859,42.438 -352.664,61.121c-92.259,15.99 -187.076,8.079 -275.41,-22.977c-93.342,-32.818 -200.913,-70.638 -200.913,-70.638l0,466l1500,0l10,-466Z" style="fill:#fff;fill-opacity:0.550725;"/>
|
||||||
|
<path d="M1500,650c0,0 -143.367,28.581 -239.425,47.731c-56.087,11.181 -113.694,12.508 -170.237,3.922c-74.75,-11.351 -183.318,-27.838 -261.719,-39.743c-65.252,-9.909 -131.707,-8.759 -196.577,3.4c-49.655,9.308 -109.704,20.564 -158.992,29.803c-63.125,11.833 -127.839,12.479 -191.188,1.911c-111.875,-18.665 -281.862,-47.024 -281.862,-47.024l0,430l1500,0l0,-430Z" style="fill:#fff;"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,4 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
require('./scss/configure.scss')
|
|
||||||
require('./js/configure.js')
|
|
||||||
@@ -1,17 +1,4 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
let logic = document.documentElement.dataset.logic
|
|
||||||
|
|
||||||
switch (logic) {
|
|
||||||
case 'error':
|
|
||||||
require('./scss/error.scss')
|
|
||||||
break
|
|
||||||
case 'login':
|
|
||||||
require('./scss/login.scss')
|
|
||||||
require('./js/login.js')
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
require('./scss/app.scss')
|
require('./scss/app.scss')
|
||||||
require('./js/app.js')
|
require('./js/app.js')
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global $, siteRoot */
|
/* global siteConfig */
|
||||||
/* eslint-disable no-new */
|
/* eslint-disable no-new */
|
||||||
|
|
||||||
|
import CONSTANTS from './constants'
|
||||||
|
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import VueResource from 'vue-resource'
|
import VueResource from 'vue-resource'
|
||||||
import VueClipboards from 'vue-clipboards'
|
import VueClipboards from 'vue-clipboards'
|
||||||
import VueLodash from 'vue-lodash'
|
import VeeValidate from 'vee-validate'
|
||||||
|
import { ApolloClient, createBatchingNetworkInterface } from 'apollo-client'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import io from 'socket-io-client'
|
|
||||||
import i18next from 'i18next'
|
// ====================================
|
||||||
import i18nextXHR from 'i18next-xhr-backend'
|
// Load Modules
|
||||||
import VueI18Next from '@panter/vue-i18next'
|
// ====================================
|
||||||
import 'jquery-contextmenu'
|
|
||||||
import 'jquery-simple-upload'
|
import localization from './modules/localization'
|
||||||
import 'jquery-smooth-scroll'
|
|
||||||
import 'jquery-sticky'
|
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Load Helpers
|
// Load Helpers
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
import helpers from './helpers'
|
import helpers from './helpers'
|
||||||
import _ from './helpers/lodash'
|
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Load Vue Components
|
// Load Vue Components
|
||||||
@@ -36,6 +36,7 @@ import editorFileComponent from './components/editor-file.vue'
|
|||||||
import editorVideoComponent from './components/editor-video.vue'
|
import editorVideoComponent from './components/editor-video.vue'
|
||||||
import historyComponent from './components/history.vue'
|
import historyComponent from './components/history.vue'
|
||||||
import loadingSpinnerComponent from './components/loading-spinner.vue'
|
import loadingSpinnerComponent from './components/loading-spinner.vue'
|
||||||
|
import loginComponent from './components/login.vue'
|
||||||
import modalCreatePageComponent from './components/modal-create-page.vue'
|
import modalCreatePageComponent from './components/modal-create-page.vue'
|
||||||
import modalCreateUserComponent from './components/modal-create-user.vue'
|
import modalCreateUserComponent from './components/modal-create-user.vue'
|
||||||
import modalDeletePageComponent from './components/modal-delete-page.vue'
|
import modalDeletePageComponent from './components/modal-delete-page.vue'
|
||||||
@@ -53,19 +54,48 @@ import adminEditUserComponent from './pages/admin-edit-user.component.js'
|
|||||||
import adminProfileComponent from './pages/admin-profile.component.js'
|
import adminProfileComponent from './pages/admin-profile.component.js'
|
||||||
import adminSettingsComponent from './pages/admin-settings.component.js'
|
import adminSettingsComponent from './pages/admin-settings.component.js'
|
||||||
import adminThemeComponent from './pages/admin-theme.component.js'
|
import adminThemeComponent from './pages/admin-theme.component.js'
|
||||||
|
import configManagerComponent from './components/config-manager.component.js'
|
||||||
import contentViewComponent from './pages/content-view.component.js'
|
import contentViewComponent from './pages/content-view.component.js'
|
||||||
import editorComponent from './components/editor.component.js'
|
import editorComponent from './components/editor.component.js'
|
||||||
import sourceViewComponent from './pages/source-view.component.js'
|
import sourceViewComponent from './pages/source-view.component.js'
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// Initialize Global Vars
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
window.wiki = null
|
||||||
|
window.CONSTANTS = CONSTANTS
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// Initialize Apollo Client (GraphQL)
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
window.graphQL = new ApolloClient({
|
||||||
|
networkInterface: createBatchingNetworkInterface({
|
||||||
|
uri: window.location.protocol + '//' + window.location.host + siteConfig.path + '/graphql'
|
||||||
|
}),
|
||||||
|
connectToDevTools: true
|
||||||
|
})
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Initialize Vue Modules
|
// Initialize Vue Modules
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
Vue.use(VueResource)
|
Vue.use(VueResource)
|
||||||
Vue.use(VueClipboards)
|
Vue.use(VueClipboards)
|
||||||
Vue.use(VueI18Next)
|
Vue.use(localization.VueI18Next)
|
||||||
Vue.use(VueLodash, _)
|
|
||||||
Vue.use(helpers)
|
Vue.use(helpers)
|
||||||
|
Vue.use(VeeValidate, {
|
||||||
|
enableAutoClasses: true,
|
||||||
|
classNames: {
|
||||||
|
touched: 'is-touched', // the control has been blurred
|
||||||
|
untouched: 'is-untouched', // the control hasn't been blurred
|
||||||
|
valid: 'is-valid', // model is valid
|
||||||
|
invalid: 'is-invalid', // model is invalid
|
||||||
|
pristine: 'is-pristine', // control has not been interacted with
|
||||||
|
dirty: 'is-dirty' // control has been interacted with
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Register Vue Components
|
// Register Vue Components
|
||||||
@@ -78,6 +108,7 @@ Vue.component('adminSettings', adminSettingsComponent)
|
|||||||
Vue.component('adminTheme', adminThemeComponent)
|
Vue.component('adminTheme', adminThemeComponent)
|
||||||
Vue.component('anchor', anchorComponent)
|
Vue.component('anchor', anchorComponent)
|
||||||
Vue.component('colorPicker', colorPickerComponent)
|
Vue.component('colorPicker', colorPickerComponent)
|
||||||
|
Vue.component('configManager', configManagerComponent)
|
||||||
Vue.component('contentView', contentViewComponent)
|
Vue.component('contentView', contentViewComponent)
|
||||||
Vue.component('editor', editorComponent)
|
Vue.component('editor', editorComponent)
|
||||||
Vue.component('editorCodeblock', editorCodeblockComponent)
|
Vue.component('editorCodeblock', editorCodeblockComponent)
|
||||||
@@ -85,6 +116,7 @@ Vue.component('editorFile', editorFileComponent)
|
|||||||
Vue.component('editorVideo', editorVideoComponent)
|
Vue.component('editorVideo', editorVideoComponent)
|
||||||
Vue.component('history', historyComponent)
|
Vue.component('history', historyComponent)
|
||||||
Vue.component('loadingSpinner', loadingSpinnerComponent)
|
Vue.component('loadingSpinner', loadingSpinnerComponent)
|
||||||
|
Vue.component('login', loginComponent)
|
||||||
Vue.component('modalCreatePage', modalCreatePageComponent)
|
Vue.component('modalCreatePage', modalCreatePageComponent)
|
||||||
Vue.component('modalCreateUser', modalCreateUserComponent)
|
Vue.component('modalCreateUser', modalCreateUserComponent)
|
||||||
Vue.component('modalDeletePage', modalDeletePageComponent)
|
Vue.component('modalDeletePage', modalDeletePageComponent)
|
||||||
@@ -99,52 +131,26 @@ Vue.component('sourceView', sourceViewComponent)
|
|||||||
Vue.component('toggle', toggleComponent)
|
Vue.component('toggle', toggleComponent)
|
||||||
Vue.component('tree', treeComponent)
|
Vue.component('tree', treeComponent)
|
||||||
|
|
||||||
// ====================================
|
document.addEventListener('DOMContentLoaded', ev => {
|
||||||
// Load Localization strings
|
|
||||||
// ====================================
|
|
||||||
|
|
||||||
i18next
|
|
||||||
.use(i18nextXHR)
|
|
||||||
.init({
|
|
||||||
backend: {
|
|
||||||
loadPath: siteRoot + '/js/i18n/{{lng}}.json'
|
|
||||||
},
|
|
||||||
lng: siteLang,
|
|
||||||
fallbackLng: siteLang
|
|
||||||
})
|
|
||||||
|
|
||||||
$(() => {
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Notifications
|
// Notifications
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
$(window).bind('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
store.dispatch('startLoading')
|
store.dispatch('startLoading')
|
||||||
})
|
})
|
||||||
$(document).ajaxSend(() => {
|
|
||||||
store.dispatch('startLoading')
|
|
||||||
}).ajaxComplete(() => {
|
|
||||||
store.dispatch('stopLoading')
|
|
||||||
})
|
|
||||||
|
|
||||||
// ====================================
|
|
||||||
// Establish WebSocket connection
|
|
||||||
// ====================================
|
|
||||||
|
|
||||||
let socket = io(window.location.origin)
|
|
||||||
window.socket = socket
|
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Bootstrap Vue
|
// Bootstrap Vue
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
const i18n = new VueI18Next(i18next)
|
const i18n = localization.init()
|
||||||
window.wikijs = new Vue({
|
window.wiki = new Vue({
|
||||||
mixins: [helpers],
|
mixins: [helpers],
|
||||||
components: {},
|
components: {},
|
||||||
store,
|
store,
|
||||||
i18n,
|
i18n,
|
||||||
el: '#root',
|
el: '#app',
|
||||||
methods: {
|
methods: {
|
||||||
changeTheme(opts) {
|
changeTheme(opts) {
|
||||||
this.$el.className = `has-stickynav is-primary-${opts.primary} is-alternate-${opts.alt}`
|
this.$el.className = `has-stickynav is-primary-${opts.primary} is-alternate-${opts.alt}`
|
||||||
@@ -153,9 +159,7 @@ $(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
$('a:not(.toc-anchor)').smoothScroll({ speed: 500, offset: -50 })
|
|
||||||
$('#header').sticky({ topSpacing: 0 })
|
|
||||||
$('.sidebar-pagecontents').sticky({ topSpacing: 15, bottomSpacing: 75 })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global $, siteConfig */
|
||||||
|
/* eslint-disable no-new */
|
||||||
|
|
||||||
|
import Vue from 'vue'
|
||||||
|
import VueResource from 'vue-resource'
|
||||||
|
import VueClipboards from 'vue-clipboards'
|
||||||
|
import VueLodash from 'vue-lodash'
|
||||||
|
import store from './store'
|
||||||
|
import i18next from 'i18next'
|
||||||
|
import i18nextXHR from 'i18next-xhr-backend'
|
||||||
|
import VueI18Next from '@panter/vue-i18next'
|
||||||
|
import 'jquery-contextmenu'
|
||||||
|
import 'jquery-simple-upload'
|
||||||
|
import 'jquery-smooth-scroll'
|
||||||
|
import 'jquery-sticky'
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// Load Helpers
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
import helpers from './helpers'
|
||||||
|
import _ from './helpers/lodash'
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// Load Vue Components
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
import alertComponent from './components/alert.vue'
|
||||||
|
import anchorComponent from './components/anchor.vue'
|
||||||
|
import colorPickerComponent from './components/color-picker.vue'
|
||||||
|
import editorCodeblockComponent from './components/editor-codeblock.vue'
|
||||||
|
import editorFileComponent from './components/editor-file.vue'
|
||||||
|
import editorVideoComponent from './components/editor-video.vue'
|
||||||
|
import historyComponent from './components/history.vue'
|
||||||
|
import loadingSpinnerComponent from './components/loading-spinner.vue'
|
||||||
|
import modalCreatePageComponent from './components/modal-create-page.vue'
|
||||||
|
import modalCreateUserComponent from './components/modal-create-user.vue'
|
||||||
|
import modalDeleteUserComponent from './components/modal-delete-user.vue'
|
||||||
|
import modalDiscardPageComponent from './components/modal-discard-page.vue'
|
||||||
|
import modalMovePageComponent from './components/modal-move-page.vue'
|
||||||
|
import modalProfile2faComponent from './components/modal-profile-2fa.vue'
|
||||||
|
import modalUpgradeSystemComponent from './components/modal-upgrade-system.vue'
|
||||||
|
import pageLoaderComponent from './components/page-loader.vue'
|
||||||
|
import searchComponent from './components/search.vue'
|
||||||
|
import toggleComponent from './components/toggle.vue'
|
||||||
|
import treeComponent from './components/tree.vue'
|
||||||
|
|
||||||
|
import adminEditUserComponent from './pages/admin-edit-user.component.js'
|
||||||
|
import adminProfileComponent from './pages/admin-profile.component.js'
|
||||||
|
import adminSettingsComponent from './pages/admin-settings.component.js'
|
||||||
|
import adminThemeComponent from './pages/admin-theme.component.js'
|
||||||
|
import contentViewComponent from './pages/content-view.component.js'
|
||||||
|
import editorComponent from './components/editor.component.js'
|
||||||
|
import sourceViewComponent from './pages/source-view.component.js'
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// Initialize Vue Modules
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
Vue.use(VueResource)
|
||||||
|
Vue.use(VueClipboards)
|
||||||
|
Vue.use(VueI18Next)
|
||||||
|
Vue.use(VueLodash, _)
|
||||||
|
Vue.use(helpers)
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// Register Vue Components
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
Vue.component('alert', alertComponent)
|
||||||
|
Vue.component('adminEditUser', adminEditUserComponent)
|
||||||
|
Vue.component('adminProfile', adminProfileComponent)
|
||||||
|
Vue.component('adminSettings', adminSettingsComponent)
|
||||||
|
Vue.component('adminTheme', adminThemeComponent)
|
||||||
|
Vue.component('anchor', anchorComponent)
|
||||||
|
Vue.component('colorPicker', colorPickerComponent)
|
||||||
|
Vue.component('contentView', contentViewComponent)
|
||||||
|
Vue.component('editor', editorComponent)
|
||||||
|
Vue.component('editorCodeblock', editorCodeblockComponent)
|
||||||
|
Vue.component('editorFile', editorFileComponent)
|
||||||
|
Vue.component('editorVideo', editorVideoComponent)
|
||||||
|
Vue.component('history', historyComponent)
|
||||||
|
Vue.component('loadingSpinner', loadingSpinnerComponent)
|
||||||
|
Vue.component('modalCreatePage', modalCreatePageComponent)
|
||||||
|
Vue.component('modalCreateUser', modalCreateUserComponent)
|
||||||
|
Vue.component('modalDeleteUser', modalDeleteUserComponent)
|
||||||
|
Vue.component('modalDiscardPage', modalDiscardPageComponent)
|
||||||
|
Vue.component('modalMovePage', modalMovePageComponent)
|
||||||
|
Vue.component('modalProfile2fa', modalProfile2faComponent)
|
||||||
|
Vue.component('modalUpgradeSystem', modalUpgradeSystemComponent)
|
||||||
|
Vue.component('pageLoader', pageLoaderComponent)
|
||||||
|
Vue.component('search', searchComponent)
|
||||||
|
Vue.component('sourceView', sourceViewComponent)
|
||||||
|
Vue.component('toggle', toggleComponent)
|
||||||
|
Vue.component('tree', treeComponent)
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// Load Localization strings
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
i18next
|
||||||
|
.use(i18nextXHR)
|
||||||
|
.init({
|
||||||
|
backend: {
|
||||||
|
loadPath: siteConfig.path + '/js/i18n/{{lng}}.json'
|
||||||
|
},
|
||||||
|
lng: siteConfig.lang,
|
||||||
|
fallbackLng: siteConfig.lang
|
||||||
|
})
|
||||||
|
|
||||||
|
$(() => {
|
||||||
|
// ====================================
|
||||||
|
// Notifications
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
$(window).bind('beforeunload', () => {
|
||||||
|
store.dispatch('startLoading')
|
||||||
|
})
|
||||||
|
$(document).ajaxSend(() => {
|
||||||
|
store.dispatch('startLoading')
|
||||||
|
}).ajaxComplete(() => {
|
||||||
|
store.dispatch('stopLoading')
|
||||||
|
})
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// Bootstrap Vue
|
||||||
|
// ====================================
|
||||||
|
|
||||||
|
const i18n = new VueI18Next(i18next)
|
||||||
|
if (document.querySelector('#root')) {
|
||||||
|
window.wikijs = new Vue({
|
||||||
|
mixins: [helpers],
|
||||||
|
components: {},
|
||||||
|
store,
|
||||||
|
i18n,
|
||||||
|
el: '#root',
|
||||||
|
methods: {
|
||||||
|
changeTheme(opts) {
|
||||||
|
this.$el.className = `has-stickynav is-primary-${opts.primary} is-alternate-${opts.alt}`
|
||||||
|
this.$refs.header.className = `nav is-${opts.primary}`
|
||||||
|
this.$refs.footer.className = `footer is-${opts.footer}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
$('a:not(.toc-anchor)').smoothScroll({ speed: 500, offset: -50 })
|
||||||
|
$('#header').sticky({ topSpacing: 0 })
|
||||||
|
$('.sidebar-pagecontents').sticky({ topSpacing: 15, bottomSpacing: 75 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
/* global siteConfig */
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'configManager',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
state: 'welcome',
|
||||||
|
syscheck: {
|
||||||
|
ok: false,
|
||||||
|
error: '',
|
||||||
|
results: []
|
||||||
|
},
|
||||||
|
dbcheck: {
|
||||||
|
ok: false,
|
||||||
|
error: ''
|
||||||
|
},
|
||||||
|
gitcheck: {
|
||||||
|
ok: false,
|
||||||
|
error: ''
|
||||||
|
},
|
||||||
|
final: {
|
||||||
|
ok: false,
|
||||||
|
error: '',
|
||||||
|
results: []
|
||||||
|
},
|
||||||
|
conf: {
|
||||||
|
telemetry: true,
|
||||||
|
upgrade: false,
|
||||||
|
title: siteConfig.title || 'Wiki',
|
||||||
|
host: siteConfig.host || 'http://',
|
||||||
|
port: siteConfig.port || 80,
|
||||||
|
lang: siteConfig.lang || 'en',
|
||||||
|
public: (siteConfig.public === true),
|
||||||
|
pathData: './data',
|
||||||
|
pathRepo: './repo',
|
||||||
|
gitUseRemote: (siteConfig.git !== false),
|
||||||
|
gitUrl: '',
|
||||||
|
gitBranch: 'master',
|
||||||
|
gitAuthType: 'ssh',
|
||||||
|
gitAuthSSHKey: '',
|
||||||
|
gitAuthUser: '',
|
||||||
|
gitAuthPass: '',
|
||||||
|
gitAuthSSL: true,
|
||||||
|
gitShowUserEmail: true,
|
||||||
|
gitServerEmail: '',
|
||||||
|
adminEmail: '',
|
||||||
|
adminPassword: '',
|
||||||
|
adminPasswordConfirm: ''
|
||||||
|
},
|
||||||
|
considerations: {
|
||||||
|
https: false,
|
||||||
|
port: false,
|
||||||
|
localhost: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentProgress: function () {
|
||||||
|
let perc = '0%'
|
||||||
|
switch (this.state) {
|
||||||
|
case 'welcome':
|
||||||
|
perc = '0%'
|
||||||
|
break
|
||||||
|
case 'syscheck':
|
||||||
|
perc = (this.syscheck.ok) ? '15%' : '5%'
|
||||||
|
break
|
||||||
|
case 'general':
|
||||||
|
perc = '25%'
|
||||||
|
break
|
||||||
|
case 'considerations':
|
||||||
|
perc = '30%'
|
||||||
|
break
|
||||||
|
case 'git':
|
||||||
|
perc = '50%'
|
||||||
|
break
|
||||||
|
case 'gitcheck':
|
||||||
|
perc = (this.gitcheck.ok) ? '70%' : '55%'
|
||||||
|
break
|
||||||
|
case 'admin':
|
||||||
|
perc = '75%'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return perc
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
/* if (appconfig.paths) {
|
||||||
|
this.conf.pathData = appconfig.paths.data || './data'
|
||||||
|
this.conf.pathRepo = appconfig.paths.repo || './repo'
|
||||||
|
}
|
||||||
|
if (appconfig.git !== false && _.isPlainObject(appconfig.git)) {
|
||||||
|
this.conf.gitUrl = appconfig.git.url || ''
|
||||||
|
this.conf.gitBranch = appconfig.git.branch || 'master'
|
||||||
|
this.conf.gitShowUserEmail = (appconfig.git.showUserEmail !== false)
|
||||||
|
this.conf.gitServerEmail = appconfig.git.serverEmail || ''
|
||||||
|
if (_.isPlainObject(appconfig.git.auth)) {
|
||||||
|
this.conf.gitAuthType = appconfig.git.auth.type || 'ssh'
|
||||||
|
this.conf.gitAuthSSHKey = appconfig.git.auth.privateKey || ''
|
||||||
|
this.conf.gitAuthUser = appconfig.git.auth.username || ''
|
||||||
|
this.conf.gitAuthPass = appconfig.git.auth.password || ''
|
||||||
|
this.conf.gitAuthSSL = (appconfig.git.auth.sslVerify !== false)
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
proceedToWelcome: function (ev) {
|
||||||
|
this.state = 'welcome'
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
proceedToSyscheck: function (ev) {
|
||||||
|
let self = this
|
||||||
|
this.state = 'syscheck'
|
||||||
|
this.loading = true
|
||||||
|
self.syscheck = {
|
||||||
|
ok: false,
|
||||||
|
error: '',
|
||||||
|
results: []
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$helpers._.delay(() => {
|
||||||
|
axios.post('/syscheck').then(resp => {
|
||||||
|
if (resp.data.ok === true) {
|
||||||
|
self.syscheck.ok = true
|
||||||
|
self.syscheck.results = resp.data.results
|
||||||
|
} else {
|
||||||
|
self.syscheck.ok = false
|
||||||
|
self.syscheck.error = resp.data.error
|
||||||
|
}
|
||||||
|
self.loading = false
|
||||||
|
self.$nextTick()
|
||||||
|
}).catch(err => {
|
||||||
|
window.alert(err.message)
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
proceedToGeneral: function (ev) {
|
||||||
|
let self = this
|
||||||
|
self.state = 'general'
|
||||||
|
self.loading = false
|
||||||
|
self.$nextTick(() => {
|
||||||
|
self.$validator.validateAll('general')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
proceedToConsiderations: function (ev) {
|
||||||
|
this.considerations = {
|
||||||
|
https: !this.$helpers._.startsWith(this.conf.host, 'https'),
|
||||||
|
port: false, // TODO
|
||||||
|
localhost: this.$helpers._.includes(this.conf.host, 'localhost')
|
||||||
|
}
|
||||||
|
this.state = 'considerations'
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
proceedToGit: function (ev) {
|
||||||
|
let self = this
|
||||||
|
self.state = 'git'
|
||||||
|
self.loading = false
|
||||||
|
self.$nextTick(() => {
|
||||||
|
self.$validator.validateAll('git')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
proceedToGitCheck: function (ev) {
|
||||||
|
let self = this
|
||||||
|
this.state = 'gitcheck'
|
||||||
|
this.loading = true
|
||||||
|
self.gitcheck = {
|
||||||
|
ok: false,
|
||||||
|
results: [],
|
||||||
|
error: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$helpers._.delay(() => {
|
||||||
|
axios.post('/gitcheck', self.conf).then(resp => {
|
||||||
|
if (resp.data.ok === true) {
|
||||||
|
self.gitcheck.ok = true
|
||||||
|
self.gitcheck.results = resp.data.results
|
||||||
|
} else {
|
||||||
|
self.gitcheck.ok = false
|
||||||
|
self.gitcheck.error = resp.data.error
|
||||||
|
}
|
||||||
|
self.loading = false
|
||||||
|
self.$nextTick()
|
||||||
|
}).catch(err => {
|
||||||
|
window.alert(err.message)
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
proceedToAdmin: function (ev) {
|
||||||
|
let self = this
|
||||||
|
self.state = 'admin'
|
||||||
|
self.loading = false
|
||||||
|
self.$nextTick(() => {
|
||||||
|
self.$validator.validateAll('admin')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
proceedToFinal: function (ev) {
|
||||||
|
let self = this
|
||||||
|
self.state = 'final'
|
||||||
|
self.loading = true
|
||||||
|
self.final = {
|
||||||
|
ok: false,
|
||||||
|
error: '',
|
||||||
|
results: []
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$helpers._.delay(() => {
|
||||||
|
axios.post('/finalize', self.conf).then(resp => {
|
||||||
|
if (resp.data.ok === true) {
|
||||||
|
self.final.ok = true
|
||||||
|
self.final.results = resp.data.results
|
||||||
|
} else {
|
||||||
|
self.final.ok = false
|
||||||
|
self.final.error = resp.data.error
|
||||||
|
}
|
||||||
|
self.loading = false
|
||||||
|
self.$nextTick()
|
||||||
|
}).catch(err => {
|
||||||
|
window.alert(err.message)
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
finish: function (ev) {
|
||||||
|
let self = this
|
||||||
|
self.state = 'restart'
|
||||||
|
|
||||||
|
this.$helpers._.delay(() => {
|
||||||
|
axios.post('/restart', {}).then(resp => {
|
||||||
|
this.$helpers._.delay(() => {
|
||||||
|
window.location.assign(self.conf.host)
|
||||||
|
}, 30000)
|
||||||
|
}).catch(err => {
|
||||||
|
window.alert(err.message)
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,9 +34,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
const videoRules = {
|
const videoRules = {
|
||||||
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/, 'i'),
|
'youtube': new RegExp('/(?:(?:youtu\\.be\\/|v\\/|vi\\/|u\\/\\w\\/|embed\\/)|(?:(?:watch)?\\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/', 'i'),
|
||||||
'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
|
'vimeo': new RegExp('/vimeo.com\\/(?:channels\\/(?:\\w+\\/)?|groups\\/(?:[^/]*)\\/videos\\/|album\\/(?:\\d+)\\/video\\/|)(\\d+)(?:$|\\/|\\?)/', 'i'),
|
||||||
'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i')
|
'dailymotion': new RegExp('/(?:dailymotion\\.com(?:\\/embed)?(?:\\/video|\\/hub)|dai\\.ly)\\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/', 'i')
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
.login(:class='{ "is-error": error }')
|
||||||
|
.login-container(:class='{ "is-expanded": strategies.length > 1 }')
|
||||||
|
.login-error(v-if='error')
|
||||||
|
strong
|
||||||
|
i.icon-warning-outline
|
||||||
|
| {{ error.title }}
|
||||||
|
span {{ error.message }}
|
||||||
|
.login-providers(v-show='strategies.length > 1')
|
||||||
|
button(v-for='strategy in strategies', :class='{ "is-active": strategy.key === selectedStrategy }', @click='selectStrategy(strategy.key, strategy.useForm)', :title='strategy.title')
|
||||||
|
em(v-html='strategy.icon')
|
||||||
|
span {{ strategy.title }}
|
||||||
|
.login-frame
|
||||||
|
h1 {{ siteTitle }}
|
||||||
|
h2 {{ $t('auth:loginrequired') }}
|
||||||
|
form(method='post', action='/login')
|
||||||
|
input#login-user(type='text', name='email', :placeholder='$t("auth:fields.emailuser")')
|
||||||
|
input#login-pass(type='password', name='password', :placeholder='$t("auth:fields.password")')
|
||||||
|
button.button.is-light-blue.is-fullwidth(type='submit')
|
||||||
|
span {{ $t('auth:actions.login') }}
|
||||||
|
.login-copyright
|
||||||
|
span {{ $t('footer.poweredby') }}
|
||||||
|
a(href='https://wiki.js.org', rel='external', title='Wiki.js') Wiki.js
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'login',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
strategies: [],
|
||||||
|
selectedStrategy: 'local'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
siteTitle() {
|
||||||
|
return siteConfig.title
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectStrategy(key, useForm) {
|
||||||
|
this.selectedStrategy = key
|
||||||
|
if (!useForm) {
|
||||||
|
window.location.assign(siteConfig.path + '/login/' + key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refreshStrategies() {
|
||||||
|
graphQL.query({
|
||||||
|
query: CONSTANTS.GRAPHQL.GQL_QUERY_AUTHENTICATION,
|
||||||
|
variables: {
|
||||||
|
mode: 'active'
|
||||||
|
}
|
||||||
|
}).then(resp => {
|
||||||
|
if (resp.data.authentication) {
|
||||||
|
this.strategies = resp.data.authentication
|
||||||
|
} else {
|
||||||
|
throw new Error('No authentication providers available!')
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.refreshStrategies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
span {{ msg }}
|
span {{ msg }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script type='js'>
|
||||||
export default {
|
export default {
|
||||||
name: 'page-loader',
|
name: 'page-loader',
|
||||||
props: ['text'],
|
props: ['text'],
|
||||||
@@ -20,5 +20,4 @@
|
|||||||
this.$store.commit('pageLoader/msgChange', this.text)
|
this.$store.commit('pageLoader/msgChange', this.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
GQL_QUERY_AUTHENTICATION: gql`
|
||||||
|
query($mode: String!) {
|
||||||
|
authentication(mode:$mode) {
|
||||||
|
key
|
||||||
|
useForm
|
||||||
|
title
|
||||||
|
icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
GQL_QUERY_TRANSLATIONS: gql`
|
||||||
|
query($locale: String!, $namespace: String!) {
|
||||||
|
translations(locale:$locale, namespace:$namespace) {
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import GRAPHQL from './graphql'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
GRAPHQL
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const helpers = {
|
const helpers = {
|
||||||
|
_: require('./lodash'),
|
||||||
common: require('./common'),
|
common: require('./common'),
|
||||||
form: require('./form'),
|
form: require('./form'),
|
||||||
pages: require('./pages')
|
pages: require('./pages')
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
/* global $ */
|
|
||||||
|
|
||||||
$(() => {
|
|
||||||
$('#login-user').focus()
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import i18next from 'i18next'
|
||||||
|
import i18nextXHR from 'i18next-xhr-backend'
|
||||||
|
import i18nextCache from 'i18next-localstorage-cache'
|
||||||
|
import VueI18Next from '@panter/vue-i18next'
|
||||||
|
import loSet from 'lodash/set'
|
||||||
|
|
||||||
|
/* global siteConfig, graphQL, CONSTANTS */
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
VueI18Next,
|
||||||
|
init() {
|
||||||
|
i18next
|
||||||
|
.use(i18nextXHR)
|
||||||
|
.use(i18nextCache)
|
||||||
|
.init({
|
||||||
|
backend: {
|
||||||
|
loadPath: '{{lng}}/{{ns}}',
|
||||||
|
parse: (data) => data,
|
||||||
|
ajax: (url, opts, cb, data) => {
|
||||||
|
let langParams = url.split('/')
|
||||||
|
graphQL.query({
|
||||||
|
query: CONSTANTS.GRAPHQL.GQL_QUERY_TRANSLATIONS,
|
||||||
|
variables: {
|
||||||
|
locale: langParams[0],
|
||||||
|
namespace: langParams[1]
|
||||||
|
}
|
||||||
|
}).then(resp => {
|
||||||
|
let ns = {}
|
||||||
|
if (resp.data.translations.length > 0) {
|
||||||
|
resp.data.translations.forEach(entry => {
|
||||||
|
loSet(ns, entry.key, entry.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return cb(ns, {status: '200'})
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
return cb(null, {status: '404'})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
enabled: true,
|
||||||
|
expiration: 60 * 60 * 1000
|
||||||
|
},
|
||||||
|
defaultNS: 'common',
|
||||||
|
lng: siteConfig.lang,
|
||||||
|
fallbackLng: siteConfig.lang,
|
||||||
|
ns: ['common', 'admin', 'auth']
|
||||||
|
})
|
||||||
|
return new VueI18Next(i18next)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
@import 'components/button';
|
@import 'components/button';
|
||||||
@import 'components/collapsable-nav';
|
@import 'components/collapsable-nav';
|
||||||
@import 'components/color-picker';
|
@import 'components/color-picker';
|
||||||
|
@import 'components/config-manager';
|
||||||
@import 'components/footer';
|
@import 'components/footer';
|
||||||
@import 'components/form';
|
@import 'components/form';
|
||||||
@import 'components/grid';
|
@import 'components/grid';
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
@import 'layout/_loader';
|
@import 'layout/_loader';
|
||||||
@import 'layout/_rtl';
|
@import 'layout/_rtl';
|
||||||
|
|
||||||
@import 'pages/_welcome';
|
@import 'pages/login';
|
||||||
|
@import 'pages/welcome';
|
||||||
|
|
||||||
@import 'base/print';
|
@import 'base/print';
|
||||||
|
|||||||
@@ -11,10 +11,15 @@ [v-cloak], .is-hidden {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#app {
|
||||||
padding-bottom: 67px;
|
padding-bottom: 67px;
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
|
||||||
|
&.is-fullscreen {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ .button {
|
|||||||
border: 1px solid mc('orange','700');
|
border: 1px solid mc('orange','700');
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
height: 30px;
|
height: 40px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -62,6 +62,10 @@ .button {
|
|||||||
color: #FFF;
|
color: #FFF;
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: inset 0 0 0 3px rgba(255,255,255, .4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -76,6 +80,12 @@ .button {
|
|||||||
animation: btnInvertedPulse .6s ease alternate infinite;
|
animation: btnInvertedPulse .6s ease alternate infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-fullwidth {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
&.is-disabled, &:disabled {
|
&.is-disabled, &:disabled {
|
||||||
background-color: mc('grey', '300');
|
background-color: mc('grey', '300');
|
||||||
border: 1px solid mc('grey','400');
|
border: 1px solid mc('grey','400');
|
||||||
@@ -89,6 +99,10 @@ .button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-small {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-group {
|
.button-group {
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
.config-manager {
|
||||||
|
.welcome {
|
||||||
|
text-align: center;
|
||||||
|
padding: 50px 0 15px 0;
|
||||||
|
color: mc('grey', '700');
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
i.icon-loader {
|
||||||
|
display: inline-block;
|
||||||
|
color: mc('indigo', '500')
|
||||||
|
}
|
||||||
|
i.icon-check {
|
||||||
|
color: mc('green', '500')
|
||||||
|
}
|
||||||
|
i.icon-square-cross {
|
||||||
|
color: mc('red', '500')
|
||||||
|
}
|
||||||
|
i.icon-warning-outline {
|
||||||
|
color: mc('orange', '500')
|
||||||
|
}
|
||||||
|
|
||||||
|
.tst-welcome-leave-active, .tst-welcome-enter-active {
|
||||||
|
transition: all .5s;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
.tst-welcome-leave, .tst-welcome-enter-to {
|
||||||
|
opacity: 1;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
.tst-welcome-leave-to, .tst-welcome-enter {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
width: 150px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: mc('indigo', '50');
|
||||||
|
border:1px solid mc('indigo', '100');
|
||||||
|
border-radius: 3px;
|
||||||
|
position: absolute;
|
||||||
|
left: 15px;
|
||||||
|
top: 21px;
|
||||||
|
padding: 1px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
width: 5px;
|
||||||
|
height: 6px;
|
||||||
|
background-color: mc('indigo', '200');
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
@charset "utf-8";
|
|
||||||
|
|
||||||
$primary: 'indigo';
|
|
||||||
|
|
||||||
@import "base/variables";
|
|
||||||
@import "base/colors";
|
|
||||||
@import "base/reset";
|
|
||||||
@import "base/mixins";
|
|
||||||
@import "base/fonts";
|
|
||||||
@import "base/base";
|
|
||||||
|
|
||||||
@import "libs/animate";
|
|
||||||
@import 'pages/login';
|
|
||||||
@@ -1,105 +1,213 @@
|
|||||||
|
.login {
|
||||||
body {
|
background-image: linear-gradient(to right, mc('blue', '400'), mc('blue', '600'));
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-family: $core-font-standard;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #FFF;
|
|
||||||
transition: color 0.4s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: mc('orange','600');
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#bg {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 1;
|
display: flex;
|
||||||
background-color: #000;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
> div {
|
&.is-error {
|
||||||
background-size: cover;
|
background-image: linear-gradient(to right, mc('red', '400'), mc('red', '600'));
|
||||||
background-position: center center;
|
}
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
&::before {
|
||||||
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
background-image: url('../svg/login-bg.svg');
|
||||||
|
background-position: center bottom;
|
||||||
|
background-size: cover;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0;
|
width: 100vw;
|
||||||
visibility: hidden;
|
height: 100vh;
|
||||||
transition: opacity 3s ease, visibility 3s;
|
|
||||||
animation: bg 30s linear infinite;
|
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
animation-delay: 10s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(2) {
|
&-container {
|
||||||
animation-delay: 20s;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
width: 400px;
|
||||||
|
align-items: stretch;
|
||||||
|
box-shadow: 0 14px 28px rgba(0,0,0,0.2);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
&.is-expanded {
|
||||||
|
width: 650px;
|
||||||
|
|
||||||
|
.login-frame {
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
width: 350px;
|
||||||
|
|
||||||
|
&.is-expanded {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-error {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 105%;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 50px;
|
||||||
|
background-image: radial-gradient(ellipse at top left, rgba(255,255,255,.9) 0%,rgba(255,255,255,.8) 100%);
|
||||||
|
box-shadow: 0 5px 10px rgba(0,0,0,0.2);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: mc('red', '800');
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: block;
|
||||||
|
padding: 0 1rem 0 0;
|
||||||
|
border-right: 1px solid mc('red', '200');
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
padding: 0 0 0 1rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
&-providers {
|
||||||
position: fixed;
|
|
||||||
top: 15vh;
|
|
||||||
left: 10vw;
|
|
||||||
z-index: 2;
|
|
||||||
color: #FFF;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
|
border-right: none;
|
||||||
|
border-radius: 6px 0 0 6px;
|
||||||
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
flex: 1 1;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border: none;
|
||||||
|
color: #FFF;
|
||||||
|
background: linear-gradient(to right, rgba(mc('light-blue', '800'), .7), rgba(mc('light-blue', '800'), 1));
|
||||||
|
border-top: 1px solid rgba(mc('light-blue', '900'), .5);
|
||||||
|
font-family: $core-font-standard;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
min-height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
transition: all .4s ease;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: mc('light-blue', '900');
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top: none;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
border-top: 1px solid rgba(255,255,255, .5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background-image: linear-gradient(to right, rgba(255,255,255,1) 0%,rgba(255,255,255,.77) 100%);
|
||||||
|
color: mc('light-blue', '700');
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path {
|
||||||
|
fill: mc('light-blue', '800');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
margin-right: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-right: 10px;
|
||||||
|
width: auto;
|
||||||
|
height: 20px;
|
||||||
|
max-width: 18px;
|
||||||
|
max-height: 20px;
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
margin-right: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-frame {
|
||||||
|
background-image: radial-gradient(circle at top center, rgba(255,255,255,1) 5%,rgba(255,255,255,.6) 100%);
|
||||||
|
border: 1px solid rgba(255,255,255, .5);
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 400px;
|
||||||
|
padding: 1rem;
|
||||||
|
color: mc('grey', '700');
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 4rem;
|
font-size: 2rem;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
color: #FFF;
|
color: mc('light-blue', '700');
|
||||||
|
text-shadow: 1px 1px 0 #FFF;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
animation: headerIntro 3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: normal;
|
font-weight: 300;
|
||||||
color: rgba(255,255,255,0.7);
|
color: mc('grey', '700');
|
||||||
|
text-shadow: 1px 1px 0 #FFF;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 0 25px 0;
|
margin: 0 0 25px 0;
|
||||||
animation: headerIntro 3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #FB8C00;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
animation: shake 1s ease;
|
|
||||||
|
|
||||||
> .fa {
|
|
||||||
margin-right: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: normal;
|
|
||||||
color: rgba(255,255,255,0.7);
|
|
||||||
padding: 0;
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
animation: fadeIn 3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
@@ -108,199 +216,49 @@ #root {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input[type=text], input[type=password] {
|
input[type=text], input[type=password] {
|
||||||
width: 350px;
|
width: 100%;
|
||||||
max-width: 80vw;
|
border: 1px solid #FFF;
|
||||||
border: 1px solid rgba(255,255,255,0.3);
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: rgba(0,0,0,0.2);
|
background-color: rgba(255,255,255,.9);
|
||||||
|
box-shadow: inset 0 0 0 3px rgba(255,255,255, .25);
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
color: #FFF;
|
color: mc('grey', '700');
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: .8rem;
|
||||||
transition: all 0.4s ease;
|
transition: all 0.4s ease;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: mc('orange','600');
|
border-color: mc('light-blue','500');
|
||||||
}
|
background-color: rgba(255,255,255,1);
|
||||||
|
box-shadow: inset 0 0 0 3px rgba(mc('light-blue','500'), .25);
|
||||||
}
|
color: mc('light-blue', '800');
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: mc('orange','600');
|
|
||||||
color: #FFF;
|
|
||||||
border: 1px solid lighten(mc('orange','600'), 10%);
|
|
||||||
border-radius: 3px;
|
|
||||||
height: 40px;
|
|
||||||
width: 125px;
|
|
||||||
padding: 0;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 15px 0 0 0;
|
|
||||||
transition: all 0.4s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: darken(mc('orange','600'), 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#social {
|
|
||||||
margin-top: 25px;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
display: block;
|
|
||||||
font-weight: bold;
|
|
||||||
color: rgba(255,255,255,0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-right: 5px;
|
|
||||||
width: auto;
|
|
||||||
padding: 0 15px;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.ms {
|
|
||||||
background-color: mc('blue','600');
|
|
||||||
border-color: lighten(mc('blue','600'), 10%);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: darken(mc('blue','600'), 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
&.google {
|
|
||||||
background-color: mc('light-blue','600');
|
|
||||||
border-color: lighten(mc('light-blue','600'), 10%);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: darken(mc('light-blue','600'), 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
&.facebook {
|
|
||||||
background-color: mc('indigo','600');
|
|
||||||
border-color: lighten(mc('indigo','600'), 10%);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: darken(mc('indigo','600'), 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
&.github {
|
|
||||||
background-color: mc('blue-grey','700');
|
|
||||||
border-color: lighten(mc('blue-grey','700'), 10%);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: darken(mc('blue-grey','700'), 10%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.slack {
|
|
||||||
background-color: mc('purple','700');
|
|
||||||
border-color: lighten(mc('purple','700'), 10%);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: darken(mc('purple','700'), 10%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
&-copyright {
|
||||||
|
|
||||||
#copyright {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 10vw;
|
left: 0;
|
||||||
bottom: 10vh;
|
bottom: 10vh;
|
||||||
|
width: 100%;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
color: rgba(255,255,255,0.5);
|
color: mc('grey', '500');
|
||||||
font-weight: bold;
|
font-weight: 400;
|
||||||
|
|
||||||
.icon {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
margin: 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
opacity: 0.75;
|
font-weight: 600;
|
||||||
|
color: mc('light-blue', '500');
|
||||||
|
margin-left: .25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include keyframes(bg) {
|
|
||||||
0% {
|
|
||||||
@include prefix(transform, scale(1,1));
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 0;
|
|
||||||
},
|
|
||||||
5% {
|
|
||||||
opacity: 0.5;
|
|
||||||
},
|
|
||||||
33% {
|
|
||||||
opacity: 0.5;
|
|
||||||
},
|
|
||||||
38% {
|
|
||||||
@include prefix(transform, scale(1.2, 1.2));
|
|
||||||
opacity: 0;
|
|
||||||
},
|
|
||||||
39% {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
visibility: hidden;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include keyframes(headerIntro) {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,23 +5,9 @@
|
|||||||
# https://docs.requarks.io/wiki/install
|
# https://docs.requarks.io/wiki/install
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# Title of this site
|
# Port the main server should listen to
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
|
|
||||||
title: Wiki
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Full public path to the site, without the trailing slash
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# INCLUDE CLIENT PORT IF NOT 80/443!
|
|
||||||
|
|
||||||
host: http://localhost
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Port the main server should listen to (80 by default)
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# To use process.env.PORT, comment the line below:
|
|
||||||
|
|
||||||
port: 80
|
port: 80
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
@@ -33,140 +19,38 @@ paths:
|
|||||||
data: ./data
|
data: ./data
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# Upload Limits
|
# Database
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# In megabytes (MB)
|
|
||||||
|
|
||||||
uploads:
|
|
||||||
maxImageFileSize: 3
|
|
||||||
maxOtherFileSize: 100
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Site Language
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Possible values: en, de, es, fa, fr, ja, ko, nl, pt, ru, sr, tr or zh
|
|
||||||
|
|
||||||
lang: en
|
|
||||||
|
|
||||||
# Enable for right to left languages (e.g. arabic):
|
|
||||||
langRtl: false
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Site Authentication
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
|
|
||||||
public: false
|
db:
|
||||||
|
host: localhost
|
||||||
auth:
|
port: 5432
|
||||||
defaultReadAccess: false
|
user: wikijs
|
||||||
local:
|
pass: wikijsrocks
|
||||||
enabled: true
|
db: wiki
|
||||||
google:
|
|
||||||
enabled: true
|
|
||||||
clientId: GOOGLE_CLIENT_ID
|
|
||||||
clientSecret: GOOGLE_CLIENT_SECRET
|
|
||||||
microsoft:
|
|
||||||
enabled: true
|
|
||||||
clientId: MS_APP_ID
|
|
||||||
clientSecret: MS_APP_SECRET
|
|
||||||
facebook:
|
|
||||||
enabled: false
|
|
||||||
clientId: FACEBOOK_APP_ID
|
|
||||||
clientSecret: FACEBOOK_APP_SECRET
|
|
||||||
github:
|
|
||||||
enabled: false
|
|
||||||
clientId: GITHUB_CLIENT_ID
|
|
||||||
clientSecret: GITHUB_CLIENT_SECRET
|
|
||||||
slack:
|
|
||||||
enabled: false
|
|
||||||
clientId: 'SLACK_CLIENT_ID'
|
|
||||||
clientSecret: 'SLACK_CLIENT_SECRET'
|
|
||||||
ldap:
|
|
||||||
enabled: false
|
|
||||||
url: ldap://serverhost:389
|
|
||||||
bindDn: cn='root'
|
|
||||||
bindCredentials: BIND_PASSWORD
|
|
||||||
searchBase: o=users,o=example.com
|
|
||||||
searchFilter: (uid={{username}})
|
|
||||||
tlsEnabled: false
|
|
||||||
tlsCertPath: C:\example\root_ca_cert.crt
|
|
||||||
azure:
|
|
||||||
enabled: false
|
|
||||||
clientId: APP_ID
|
|
||||||
clientSecret: APP_SECRET_KEY
|
|
||||||
resource: '00000002-0000-0000-c000-000000000000'
|
|
||||||
tenant: 'YOUR_TENANT.onmicrosoft.com'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# Secret key to use when encrypting sessions
|
# Redis
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Use a long and unique random string (256-bit keys are perfect!)
|
|
||||||
|
|
||||||
sessionSecret: 1234567890abcdefghijklmnopqrstuvxyz
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Database Connection String
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# You can also use an ENV variable by using $ENV_VAR_NAME as the value
|
|
||||||
|
|
||||||
db: mongodb://localhost:27017/wiki
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Git Connection Info
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
|
|
||||||
git:
|
redis:
|
||||||
url: https://github.com/Organization/Repo
|
host: localhost
|
||||||
branch: master
|
port: 6379
|
||||||
auth:
|
db: 0
|
||||||
|
password: null
|
||||||
# Type: basic or ssh
|
|
||||||
type: ssh
|
|
||||||
|
|
||||||
# Only for Basic authentication:
|
|
||||||
username: marty
|
|
||||||
password: MartyMcFly88
|
|
||||||
|
|
||||||
# Only for SSH authentication:
|
|
||||||
privateKey: /etc/wiki/keys/git.pem
|
|
||||||
|
|
||||||
sslVerify: true
|
|
||||||
|
|
||||||
# Default email to use as commit author
|
|
||||||
serverEmail: marty@example.com
|
|
||||||
|
|
||||||
# Whether to use user email as author in commits
|
|
||||||
showUserEmail: true
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# Features
|
# Background Workers
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# You can enable / disable specific features below
|
# Leave 0 for auto based on CPU cores
|
||||||
|
|
||||||
features:
|
workers: 0
|
||||||
linebreaks: true
|
|
||||||
mathjax: true
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# External Logging
|
# High Availability
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
|
# Read the docs BEFORE changing these settings!
|
||||||
|
|
||||||
externalLogging:
|
ha:
|
||||||
bugsnag: false
|
nodeuid: primary
|
||||||
loggly: false
|
readonly: false
|
||||||
papertrail: false
|
|
||||||
rollbar: false
|
|
||||||
sentry: false
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Color Theme
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
theme:
|
|
||||||
primary: indigo
|
|
||||||
alt: blue-grey
|
|
||||||
viewSource: all # all | write | false
|
|
||||||
footer: blue-grey
|
|
||||||
code:
|
|
||||||
dark: true
|
|
||||||
colorize: true
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||

|
|
||||||
|
|
||||||
# Wiki.js
|
|
||||||
|
|
||||||
[](https://github.com/Requarks)
|
|
||||||
[](https://github.com/Requarks/wiki/releases)
|
|
||||||
[](https://github.com/requarks/wiki/blob/master/LICENSE)
|
|
||||||
|
|
||||||
This npm package is an installer for Wiki.js.
|
|
||||||
For information about Wiki.js, including detailed installation steps, read the following links:
|
|
||||||
|
|
||||||
- [Official Website](https://wiki.js.org/)
|
|
||||||
- [Installation Guide](https://wiki.js.org/get-started.html)
|
|
||||||
- [GitHub Repository](https://github.com/Requarks/wiki)
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// Wiki.js
|
|
||||||
// Installation Script
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
const path = require('path')
|
|
||||||
const spawn = require('child_process').spawn
|
|
||||||
const installDir = path.resolve(__dirname, '../..')
|
|
||||||
const cmd = (process.platform !== 'win32')
|
|
||||||
? 'curl -s -S -o- https://wiki.js.org/install.sh | bash'
|
|
||||||
: `PowerShell.exe -NoProfile -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://wiki.js.org/install.ps1'))"`
|
|
||||||
|
|
||||||
console.info(`Executing installation script for ${process.platform} platform...`)
|
|
||||||
|
|
||||||
let inst = spawn(cmd, [], {
|
|
||||||
cwd: installDir,
|
|
||||||
env: process.env,
|
|
||||||
shell: true,
|
|
||||||
stdio: 'inherit',
|
|
||||||
detached: true
|
|
||||||
})
|
|
||||||
|
|
||||||
inst.unref()
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "wiki.js",
|
|
||||||
"version": "1.0.5-rev.1",
|
|
||||||
"description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown",
|
|
||||||
"main": "install.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "exit 1",
|
|
||||||
"postinstall": "node install.js"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/Requarks/wiki.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"wiki",
|
|
||||||
"wikis",
|
|
||||||
"wikijs",
|
|
||||||
"wiki.js",
|
|
||||||
"wiki-js",
|
|
||||||
"docs",
|
|
||||||
"documentation",
|
|
||||||
"markdown",
|
|
||||||
"guides"
|
|
||||||
],
|
|
||||||
"author": "Nicolas Giard",
|
|
||||||
"license": "AGPL-3.0",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/Requarks/wiki/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/Requarks/wiki#readme",
|
|
||||||
"dependencies": {}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wiki",
|
"name": "wiki",
|
||||||
"version": "1.0.11",
|
"version": "2.0.0",
|
||||||
"description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown",
|
"description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown",
|
||||||
"main": "wiki.js",
|
"main": "wiki.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
"restart": "node wiki restart",
|
"restart": "node wiki restart",
|
||||||
"build": "node tools/fuse",
|
"build": "node tools/fuse",
|
||||||
"dev": "node tools/fuse -d",
|
"dev": "node tools/fuse -d",
|
||||||
"dev-configure": "node tools/fuse -c",
|
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"postinstall": "opencollective postinstall"
|
"postinstall": "opencollective postinstall"
|
||||||
},
|
},
|
||||||
@@ -38,135 +37,143 @@
|
|||||||
"node": ">=6.11.1"
|
"node": ">=6.11.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"auto-load": "~3.0.0",
|
"apollo-server-express": "1.1.3",
|
||||||
"axios": "~0.16.2",
|
"auto-load": "3.0.0",
|
||||||
"bcryptjs-then": "~1.0.1",
|
"axios": "0.16.2",
|
||||||
"bluebird": "~3.5.0",
|
"bcryptjs-then": "1.0.1",
|
||||||
"body-parser": "~1.17.2",
|
"bluebird": "3.5.1",
|
||||||
"bunyan": "~1.8.12",
|
"body-parser": "1.18.2",
|
||||||
"cheerio": "~1.0.0-rc.2",
|
"bull": "3.3.0",
|
||||||
"child-process-promise": "~2.2.1",
|
"bunyan": "1.8.12",
|
||||||
"chokidar": "~1.7.0",
|
"cheerio": "1.0.0-rc.2",
|
||||||
"compression": "~1.7.0",
|
"child-process-promise": "2.2.1",
|
||||||
"connect-flash": "~0.1.1",
|
"chokidar": "1.7.0",
|
||||||
"connect-mongo": "~1.3.2",
|
"compression": "1.7.1",
|
||||||
"cookie-parser": "~1.4.3",
|
"connect-flash": "0.1.1",
|
||||||
"cron": "~1.2.1",
|
"connect-redis": "3.3.2",
|
||||||
"diff2html": "~2.3.0",
|
"cookie-parser": "1.4.3",
|
||||||
"execa": "~0.8.0",
|
"diff2html": "2.3.1",
|
||||||
"express": "~4.15.4",
|
"dotize": "^0.2.0",
|
||||||
|
"execa": "0.8.0",
|
||||||
|
"express": "4.16.1",
|
||||||
"express-brute": "1.0.1",
|
"express-brute": "1.0.1",
|
||||||
"express-brute-mongoose": "~0.0.9",
|
"express-brute-redis": "0.0.1",
|
||||||
"express-session": "~1.15.5",
|
"express-session": "1.15.6",
|
||||||
"file-type": "~6.1.0",
|
"file-type": "6.2.0",
|
||||||
"filesize.js": "~1.0.2",
|
"filesize.js": "1.0.2",
|
||||||
"follow-redirects": "~1.2.4",
|
"follow-redirects": "1.2.5",
|
||||||
"fs-extra": "~4.0.1",
|
"fs-extra": "4.0.2",
|
||||||
"git-wrapper2-promise": "~0.2.9",
|
"git-wrapper2-promise": "0.2.9",
|
||||||
"highlight.js": "~9.12.0",
|
"graphql": "0.10.5",
|
||||||
"i18next": "~9.0.0",
|
"graphql-tools": "2.2.1",
|
||||||
"i18next-express-middleware": "~1.0.5",
|
"highlight.js": "9.12.0",
|
||||||
"i18next-node-fs-backend": "~1.0.0",
|
"i18next": "9.1.0",
|
||||||
"image-size": "~0.6.0",
|
"i18next-express-middleware": "1.0.7",
|
||||||
"jimp": "~0.2.28",
|
"i18next-localstorage-cache": "1.1.1",
|
||||||
"js-yaml": "~3.9.1",
|
"i18next-node-fs-backend": "1.0.0",
|
||||||
"jsonwebtoken": "~7.4.3",
|
"image-size": "0.6.1",
|
||||||
"klaw": "~2.1.0",
|
"ioredis": "3.1.4",
|
||||||
"levelup": "~1.3.9",
|
"jimp": "0.2.28",
|
||||||
"lodash": "~4.17.4",
|
"js-yaml": "3.10.0",
|
||||||
"markdown-it": "~8.4.0",
|
"jsonwebtoken": "8.0.1",
|
||||||
"markdown-it-abbr": "~1.0.4",
|
"klaw": "2.1.0",
|
||||||
"markdown-it-anchor": "~4.0.0",
|
"lodash": "4.17.4",
|
||||||
"markdown-it-attrs": "~1.1.0",
|
"markdown-it": "8.4.0",
|
||||||
"markdown-it-emoji": "~1.4.0",
|
"markdown-it-abbr": "1.0.4",
|
||||||
"markdown-it-expand-tabs": "~1.0.12",
|
"markdown-it-anchor": "4.0.0",
|
||||||
|
"markdown-it-attrs": "1.2.0",
|
||||||
|
"markdown-it-emoji": "1.4.0",
|
||||||
|
"markdown-it-expand-tabs": "1.0.12",
|
||||||
"markdown-it-external-links": "0.0.6",
|
"markdown-it-external-links": "0.0.6",
|
||||||
"markdown-it-footnote": "~3.0.1",
|
"markdown-it-footnote": "3.0.1",
|
||||||
"markdown-it-mathjax": "~2.0.0",
|
"markdown-it-mathjax": "2.0.0",
|
||||||
"markdown-it-task-lists": "~2.0.1",
|
"markdown-it-task-lists": "2.0.1",
|
||||||
"mathjax-node": "~1.2.0",
|
"mathjax-node": "1.2.1",
|
||||||
"memdown": "~1.2.4",
|
"mime-types": "2.1.17",
|
||||||
"mime-types": "~2.1.16",
|
"moment": "2.18.1",
|
||||||
"moment": "~2.18.1",
|
"moment-timezone": "0.5.13",
|
||||||
"moment-timezone": "~0.5.13",
|
"multer": "1.3.0",
|
||||||
"mongodb": "~2.2.31",
|
"node-2fa": "1.1.2",
|
||||||
"mongoose": "~4.11.9",
|
"node-graceful": "0.2.3",
|
||||||
"multer": "~1.3.0",
|
"ora": "1.3.0",
|
||||||
"node-2fa": "~1.1.2",
|
"passport": "0.4.0",
|
||||||
"node-graceful": "~0.2.3",
|
|
||||||
"opencollective": "~1.0.3",
|
|
||||||
"ora": "~1.3.0",
|
|
||||||
"passport": "~0.4.0",
|
|
||||||
"passport-azure-ad-oauth2": "0.0.4",
|
"passport-azure-ad-oauth2": "0.0.4",
|
||||||
"passport-facebook": "~2.1.1",
|
"passport-facebook": "2.1.1",
|
||||||
"passport-github2": "~0.1.10",
|
"passport-github2": "0.1.11",
|
||||||
"passport-google-oauth20": "~1.0.0",
|
"passport-google-oauth20": "1.0.0",
|
||||||
"passport-ldapauth": "~2.0.0",
|
"passport-ldapauth": "2.0.0",
|
||||||
"passport-local": "~1.0.0",
|
"passport-local": "1.0.0",
|
||||||
"passport-slack": "0.0.7",
|
"passport-slack": "0.0.7",
|
||||||
"passport-windowslive": "~1.0.2",
|
"passport-windowslive": "1.0.2",
|
||||||
"passport.socketio": "~3.7.0",
|
"pg": "6.4.2",
|
||||||
"pm2": "~2.6.1",
|
"pg-hstore": "2.3.2",
|
||||||
"pug": "~2.0.0-rc.3",
|
"pg-promise": "6.10.3",
|
||||||
"read-chunk": "~2.1.0",
|
"pm2": "2.7.1",
|
||||||
"remove-markdown": "~0.2.2",
|
"pug": "2.0.0-rc.4",
|
||||||
"request": "~2.81.0",
|
"qr-image": "3.2.0",
|
||||||
"search-index-adder": "~0.3.9",
|
"read-chunk": "2.1.0",
|
||||||
"search-index-searcher": "~0.2.10",
|
"remove-markdown": "0.2.2",
|
||||||
"semver": "~5.4.1",
|
"request": "2.83.0",
|
||||||
"serve-favicon": "~2.4.3",
|
"semver": "5.4.1",
|
||||||
"simplemde": "~1.11.2",
|
"sequelize": "4.13.5",
|
||||||
"socket.io": "~2.0.2",
|
"serve-favicon": "2.4.5",
|
||||||
"stopword": "~0.1.6",
|
"simplemde": "1.11.2",
|
||||||
"stream-to-promise": "~2.2.0",
|
"stream-to-promise": "2.2.0",
|
||||||
"tar": "~4.0.1",
|
"tar": "4.0.1",
|
||||||
"through2": "~2.0.3",
|
"through2": "2.0.3",
|
||||||
"validator": "~8.1.0",
|
"validator": "9.0.0",
|
||||||
"validator-as-promised": "~1.0.2",
|
"validator-as-promised": "1.0.2",
|
||||||
"winston": "~2.3.1",
|
"winston": "2.4.0",
|
||||||
"yargs": "~8.0.1"
|
"yargs": "9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@glimpse/glimpse": "~0.22.15",
|
"@glimpse/glimpse": "0.22.15",
|
||||||
"@panter/vue-i18next": "~0.5.0",
|
"@panter/vue-i18next": "0.6.1",
|
||||||
"babel-cli": "~6.26.0",
|
"apollo-client": "^1.9.3",
|
||||||
"babel-jest": "~20.0.3",
|
"autoprefixer": "7.1.5",
|
||||||
"babel-plugin-transform-object-assign": "~6.22.0",
|
"babel-cli": "6.26.0",
|
||||||
"babel-preset-es2015": "~6.24.1",
|
"babel-core": "6.26.0",
|
||||||
"brace": "~0.10.0",
|
"babel-jest": "21.2.0",
|
||||||
"colors": "~1.1.2",
|
"babel-preset-env": "1.6.0",
|
||||||
"consolidate": "~0.14.5",
|
"babel-preset-es2015": "6.24.1",
|
||||||
"eslint": "~4.5.0",
|
"babel-preset-stage-2": "6.24.1",
|
||||||
"eslint-config-standard": "~10.2.1",
|
"brace": "0.10.0",
|
||||||
"eslint-plugin-import": "~2.7.0",
|
"colors": "1.1.2",
|
||||||
"eslint-plugin-node": "~5.1.0",
|
"consolidate": "0.14.5",
|
||||||
"eslint-plugin-promise": "~3.5.0",
|
"eslint": "4.8.0",
|
||||||
"eslint-plugin-standard": "~3.0.1",
|
"eslint-config-requarks": "1.0.7",
|
||||||
"fuse-box": "~2.2.2",
|
"eslint-config-standard": "10.2.1",
|
||||||
"i18next-xhr-backend": "~1.4.2",
|
"eslint-plugin-import": "2.7.0",
|
||||||
"jest": "~20.0.4",
|
"eslint-plugin-node": "5.2.0",
|
||||||
"jest-junit": "~3.1.0",
|
"eslint-plugin-promise": "3.5.0",
|
||||||
"jquery": "~3.2.1",
|
"eslint-plugin-standard": "3.0.1",
|
||||||
"jquery-contextmenu": "~2.5.0",
|
"fuse-box": "2.3.3",
|
||||||
"jquery-simple-upload": "~1.0.0",
|
"graphql-tag": "^2.4.2",
|
||||||
"jquery-smooth-scroll": "~2.2.0",
|
"i18next-xhr-backend": "1.4.3",
|
||||||
"jquery-sticky": "~1.0.4",
|
"jest": "21.2.1",
|
||||||
"lodash-cli": "~4.17.4",
|
"jquery": "3.2.1",
|
||||||
"lodash-es": "~4.17.4",
|
"jquery-contextmenu": "2.6.2",
|
||||||
"node-sass": "~4.5.3",
|
"jquery-simple-upload": "1.0.0",
|
||||||
"nodemon": "~1.11.0",
|
"js-cookie": "2.1.4",
|
||||||
"pug-lint": "~2.4.0",
|
"node-sass": "4.5.3",
|
||||||
"twemoji-awesome": "~1.0.6",
|
"nodemon": "1.12.1",
|
||||||
"typescript": "~2.5.2",
|
"postcss-selector-parser": "2.2.3",
|
||||||
"uglify-es": "~3.0.28",
|
"pug-lint": "2.5.0",
|
||||||
"vee-validate": "~2.0.0-rc.14",
|
"twemoji-awesome": "1.0.6",
|
||||||
"vue": "~2.4.2",
|
"typescript": "2.5.3",
|
||||||
"vue-clipboards": "~1.1.0",
|
"uglify-es": "3.1.3",
|
||||||
"vue-lodash": "~1.0.3",
|
"vee-validate": "2.0.0-rc.18",
|
||||||
"vue-resource": "~1.3.4",
|
"vue": "2.4.4",
|
||||||
"vue-template-compiler": "~2.4.2",
|
"vue-clipboards": "1.1.0",
|
||||||
"vue-template-es2015-compiler": "~1.5.3",
|
"vue-hot-reload-api": "2.1.1",
|
||||||
"vuex": "~2.4.0"
|
"vue-lodash": "1.0.4",
|
||||||
|
"vue-material": "^0.7.5",
|
||||||
|
"vue-resource": "1.3.4",
|
||||||
|
"vue-simple-breakpoints": "1.0.2",
|
||||||
|
"vue-template-compiler": "2.4.4",
|
||||||
|
"vue-template-es2015-compiler": "1.5.3",
|
||||||
|
"vuex": "2.4.1",
|
||||||
|
"vuex-persistedstate": "2.0.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"testResultsProcessor": "./node_modules/jest-junit",
|
"testResultsProcessor": "./node_modules/jest-junit",
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
// ===========================================
|
|
||||||
// Wiki.js - Background Agent
|
|
||||||
// 1.0.0
|
|
||||||
// Licensed under AGPLv3
|
|
||||||
// ===========================================
|
|
||||||
|
|
||||||
const path = require('path')
|
|
||||||
const ROOTPATH = process.cwd()
|
|
||||||
const SERVERPATH = path.join(ROOTPATH, 'server')
|
|
||||||
|
|
||||||
global.ROOTPATH = ROOTPATH
|
|
||||||
global.SERVERPATH = SERVERPATH
|
|
||||||
const IS_DEBUG = process.env.NODE_ENV === 'development'
|
|
||||||
|
|
||||||
let appconf = require('./libs/config')()
|
|
||||||
global.appconfig = appconf.config
|
|
||||||
global.appdata = appconf.data
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Load Winston
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
global.winston = require('./libs/logger')(IS_DEBUG, 'AGENT')
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Load global modules
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
global.winston.info('Background Agent is initializing...')
|
|
||||||
|
|
||||||
global.db = require('./libs/db').init()
|
|
||||||
global.upl = require('./libs/uploads-agent').init()
|
|
||||||
global.git = require('./libs/git').init()
|
|
||||||
global.entries = require('./libs/entries').init()
|
|
||||||
global.lang = require('i18next')
|
|
||||||
global.mark = require('./libs/markdown')
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Load modules
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
const moment = require('moment')
|
|
||||||
const Promise = require('bluebird')
|
|
||||||
const fs = Promise.promisifyAll(require('fs-extra'))
|
|
||||||
const klaw = require('klaw')
|
|
||||||
const Cron = require('cron').CronJob
|
|
||||||
const i18nBackend = require('i18next-node-fs-backend')
|
|
||||||
|
|
||||||
const entryHelper = require('./helpers/entry')
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Localization Engine
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
global.lang
|
|
||||||
.use(i18nBackend)
|
|
||||||
.init({
|
|
||||||
load: 'languageOnly',
|
|
||||||
ns: ['common', 'admin', 'auth', 'errors', 'git'],
|
|
||||||
defaultNS: 'common',
|
|
||||||
saveMissing: false,
|
|
||||||
preload: [appconfig.lang],
|
|
||||||
lng: appconfig.lang,
|
|
||||||
fallbackLng: 'en',
|
|
||||||
backend: {
|
|
||||||
loadPath: path.join(SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Start Cron
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
let job
|
|
||||||
let jobIsBusy = false
|
|
||||||
let jobUplWatchStarted = false
|
|
||||||
|
|
||||||
global.db.onReady.then(() => {
|
|
||||||
return global.db.Entry.remove({})
|
|
||||||
}).then(() => {
|
|
||||||
job = new Cron({
|
|
||||||
cronTime: '0 */5 * * * *',
|
|
||||||
onTick: () => {
|
|
||||||
// Make sure we don't start two concurrent jobs
|
|
||||||
|
|
||||||
if (jobIsBusy) {
|
|
||||||
global.winston.warn('Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
global.winston.info('Running all jobs...')
|
|
||||||
jobIsBusy = true
|
|
||||||
|
|
||||||
// Prepare async job collector
|
|
||||||
|
|
||||||
let jobs = []
|
|
||||||
let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
|
|
||||||
let dataPath = path.resolve(ROOTPATH, appconfig.paths.data)
|
|
||||||
let uploadsTempPath = path.join(dataPath, 'temp-upload')
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// REGULAR JOBS
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
//* ****************************************
|
|
||||||
// -> Sync with Git remote
|
|
||||||
//* ****************************************
|
|
||||||
|
|
||||||
jobs.push(global.git.resync().then(() => {
|
|
||||||
// -> Stream all documents
|
|
||||||
|
|
||||||
let cacheJobs = []
|
|
||||||
let jobCbStreamDocsResolve = null
|
|
||||||
let jobCbStreamDocs = new Promise((resolve, reject) => {
|
|
||||||
jobCbStreamDocsResolve = resolve
|
|
||||||
})
|
|
||||||
|
|
||||||
klaw(repoPath).on('data', function (item) {
|
|
||||||
if (path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
|
|
||||||
let entryPath = entryHelper.parsePath(entryHelper.getEntryPathFromFullPath(item.path))
|
|
||||||
let cachePath = entryHelper.getCachePath(entryPath)
|
|
||||||
|
|
||||||
// -> Purge outdated cache
|
|
||||||
|
|
||||||
cacheJobs.push(
|
|
||||||
fs.statAsync(cachePath).then((st) => {
|
|
||||||
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'
|
|
||||||
}).catch((err) => {
|
|
||||||
return (err.code !== 'EEXIST') ? err : 'new'
|
|
||||||
}).then((fileStatus) => {
|
|
||||||
// -> Delete expired cache file
|
|
||||||
|
|
||||||
if (fileStatus === 'expired') {
|
|
||||||
return fs.unlinkAsync(cachePath).return(fileStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileStatus
|
|
||||||
}).then((fileStatus) => {
|
|
||||||
// -> Update cache and search index
|
|
||||||
|
|
||||||
if (fileStatus !== 'active') {
|
|
||||||
return global.entries.updateCache(entryPath).then(entry => {
|
|
||||||
process.send({
|
|
||||||
action: 'searchAdd',
|
|
||||||
content: entry
|
|
||||||
})
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}).on('end', () => {
|
|
||||||
jobCbStreamDocsResolve(Promise.all(cacheJobs))
|
|
||||||
})
|
|
||||||
|
|
||||||
return jobCbStreamDocs
|
|
||||||
}))
|
|
||||||
|
|
||||||
//* ****************************************
|
|
||||||
// -> Clear failed temporary upload files
|
|
||||||
//* ****************************************
|
|
||||||
|
|
||||||
jobs.push(
|
|
||||||
fs.readdirAsync(uploadsTempPath).then((ls) => {
|
|
||||||
let fifteenAgo = moment().subtract(15, 'minutes')
|
|
||||||
|
|
||||||
return Promise.map(ls, (f) => {
|
|
||||||
return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s } })
|
|
||||||
}).filter((s) => { return s.stat.isFile() }).then((arrFiles) => {
|
|
||||||
return Promise.map(arrFiles, (f) => {
|
|
||||||
if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
|
|
||||||
return fs.unlinkAsync(path.join(uploadsTempPath, f.filename))
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Run
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
Promise.all(jobs).then(() => {
|
|
||||||
global.winston.info('All jobs completed successfully! Going to sleep for now.')
|
|
||||||
|
|
||||||
if (!jobUplWatchStarted) {
|
|
||||||
jobUplWatchStarted = true
|
|
||||||
global.upl.initialScan().then(() => {
|
|
||||||
job.start()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}).catch((err) => {
|
|
||||||
global.winston.error('One or more jobs have failed: ', err)
|
|
||||||
}).finally(() => {
|
|
||||||
jobIsBusy = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
start: false,
|
|
||||||
timeZone: 'UTC',
|
|
||||||
runOnInit: true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Shutdown gracefully
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
process.on('disconnect', () => {
|
|
||||||
global.winston.warn('Lost connection to main server. Exiting...')
|
|
||||||
job.stop()
|
|
||||||
process.exit()
|
|
||||||
})
|
|
||||||
|
|
||||||
process.on('exit', () => {
|
|
||||||
job.stop()
|
|
||||||
})
|
|
||||||
@@ -5,67 +5,49 @@
|
|||||||
name: Wiki.js
|
name: Wiki.js
|
||||||
defaults:
|
defaults:
|
||||||
config:
|
config:
|
||||||
title: Wiki
|
|
||||||
host: http://localhost
|
|
||||||
port: 80
|
port: 80
|
||||||
paths:
|
paths:
|
||||||
repo: ./repo
|
repo: ./repo
|
||||||
data: ./data
|
data: ./data
|
||||||
uploads:
|
db:
|
||||||
maxImageFileSize: 3,
|
host: localhost
|
||||||
maxOtherFileSize: 100
|
port: 5432
|
||||||
lang: en
|
user: wikijs
|
||||||
langRtl: false
|
pass: wikijsrocks
|
||||||
public: false
|
db: wiki
|
||||||
auth:
|
redis:
|
||||||
defaultReadAccess: false
|
host: localhost
|
||||||
local:
|
port: 6379
|
||||||
enabled: true
|
db: 0
|
||||||
microsoft:
|
|
||||||
enabled: false
|
|
||||||
google:
|
|
||||||
enabled: false
|
|
||||||
facebook:
|
|
||||||
enabled: false
|
|
||||||
github:
|
|
||||||
enabled: false
|
|
||||||
slack:
|
|
||||||
enabled: false
|
|
||||||
ldap:
|
|
||||||
enabled: false
|
|
||||||
azure:
|
|
||||||
enabled: false
|
|
||||||
db: mongodb://localhost/wiki
|
|
||||||
sessionSecret: null
|
|
||||||
admin: null
|
|
||||||
git:
|
|
||||||
url: null
|
|
||||||
branch: master
|
|
||||||
auth:
|
|
||||||
type: basic
|
|
||||||
username: null
|
|
||||||
password: null
|
password: null
|
||||||
privateKey: null
|
workers: 0
|
||||||
sslVerify: true
|
ha:
|
||||||
serverEmail: wiki@example.com
|
nodeuid: primary
|
||||||
showUserEmail: true
|
readonly: false
|
||||||
features:
|
site:
|
||||||
linebreaks: true
|
path: ''
|
||||||
mathjax: true
|
lang: en
|
||||||
externalLogging:
|
title: Wiki.js
|
||||||
bugsnap: false
|
configNamespaces:
|
||||||
loggly: false
|
- auth
|
||||||
papertrail: false
|
- features
|
||||||
rollbar: false
|
- git
|
||||||
sentry: false
|
- logging
|
||||||
theme:
|
- site
|
||||||
primary: indigo
|
- theme
|
||||||
alt: blue-grey
|
- uploads
|
||||||
footer: blue-grey
|
queues:
|
||||||
viewSource: false
|
- gitSync
|
||||||
code:
|
- uplClearTemp
|
||||||
dark: true
|
authProviders:
|
||||||
colorize: true
|
- local
|
||||||
|
- microsoft
|
||||||
|
- google
|
||||||
|
- facebook
|
||||||
|
- github
|
||||||
|
- slack
|
||||||
|
- ldap
|
||||||
|
- azure
|
||||||
colors:
|
colors:
|
||||||
- red
|
- red
|
||||||
- pink
|
- pink
|
||||||
@@ -108,6 +90,12 @@ langs:
|
|||||||
-
|
-
|
||||||
id: ko
|
id: ko
|
||||||
name: Korean - 한국어
|
name: Korean - 한국어
|
||||||
|
-
|
||||||
|
id: fa
|
||||||
|
name: Persian (Fārsi) - فارسی
|
||||||
|
-
|
||||||
|
id: pt
|
||||||
|
name: Portuguese - Português
|
||||||
-
|
-
|
||||||
id: ru
|
id: ru
|
||||||
name: Russian - Русский
|
name: Russian - Русский
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// Azure AD Account
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'azure',
|
||||||
|
title: 'Azure Active Directory',
|
||||||
|
useForm: false,
|
||||||
|
props: ['clientId', 'clientSecret', 'callbackURL', 'resource', 'tenant'],
|
||||||
|
init (passport, conf) {
|
||||||
|
const jwt = require('jsonwebtoken')
|
||||||
|
passport.use('azure_ad_oauth2',
|
||||||
|
new AzureAdOAuth2Strategy({
|
||||||
|
clientID: conf.clientId,
|
||||||
|
clientSecret: conf.clientSecret,
|
||||||
|
callbackURL: conf.callbackURL,
|
||||||
|
resource: conf.resource,
|
||||||
|
tenant: conf.tenant
|
||||||
|
}, (accessToken, refreshToken, params, profile, cb) => {
|
||||||
|
let waadProfile = jwt.decode(params.id_token)
|
||||||
|
waadProfile.id = waadProfile.oid
|
||||||
|
waadProfile.provider = 'azure'
|
||||||
|
wiki.db.User.processProfile(waadProfile).then((user) => {
|
||||||
|
return cb(null, user) || true
|
||||||
|
}).catch((err) => {
|
||||||
|
return cb(err, null) || true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// Facebook Account
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
const FacebookStrategy = require('passport-facebook').Strategy
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'facebook',
|
||||||
|
title: 'Facebook',
|
||||||
|
useForm: false,
|
||||||
|
props: ['clientId', 'clientSecret', 'callbackURL'],
|
||||||
|
init (passport, conf) {
|
||||||
|
passport.use('facebook',
|
||||||
|
new FacebookStrategy({
|
||||||
|
clientID: conf.clientId,
|
||||||
|
clientSecret: conf.clientSecret,
|
||||||
|
callbackURL: conf.callbackURL,
|
||||||
|
profileFields: ['id', 'displayName', 'email']
|
||||||
|
}, function (accessToken, refreshToken, profile, cb) {
|
||||||
|
wiki.db.User.processProfile(profile).then((user) => {
|
||||||
|
return cb(null, user) || true
|
||||||
|
}).catch((err) => {
|
||||||
|
return cb(err, null) || true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// GitHub Account
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
const GitHubStrategy = require('passport-github2').Strategy
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'github',
|
||||||
|
title: 'GitHub',
|
||||||
|
useForm: false,
|
||||||
|
props: ['clientId', 'clientSecret', 'callbackURL'],
|
||||||
|
init (passport, conf) {
|
||||||
|
passport.use('github',
|
||||||
|
new GitHubStrategy({
|
||||||
|
clientID: conf.clientId,
|
||||||
|
clientSecret: conf.clientSecret,
|
||||||
|
callbackURL: conf.callbackURL,
|
||||||
|
scope: ['user:email']
|
||||||
|
}, (accessToken, refreshToken, profile, cb) => {
|
||||||
|
wiki.db.User.processProfile(profile).then((user) => {
|
||||||
|
return cb(null, user) || true
|
||||||
|
}).catch((err) => {
|
||||||
|
return cb(err, null) || true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// Google ID Account
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
const GoogleStrategy = require('passport-google-oauth20').Strategy
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'google',
|
||||||
|
title: 'Google ID',
|
||||||
|
useForm: false,
|
||||||
|
props: ['clientId', 'clientSecret', 'callbackURL'],
|
||||||
|
init (passport, conf) {
|
||||||
|
passport.use('google',
|
||||||
|
new GoogleStrategy({
|
||||||
|
clientID: conf.clientId,
|
||||||
|
clientSecret: conf.clientSecret,
|
||||||
|
callbackURL: conf.callbackURL
|
||||||
|
}, (accessToken, refreshToken, profile, cb) => {
|
||||||
|
wiki.db.User.processProfile(profile).then((user) => {
|
||||||
|
return cb(null, user) || true
|
||||||
|
}).catch((err) => {
|
||||||
|
return cb(err, null) || true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// LDAP Account
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
const LdapStrategy = require('passport-ldapauth').Strategy
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'ldap',
|
||||||
|
title: 'LDAP / Active Directory',
|
||||||
|
useForm: true,
|
||||||
|
props: ['url', 'bindDn', 'bindCredentials', 'searchBase', 'searchFilter', 'tlsEnabled', 'tlsCertPath'],
|
||||||
|
init (passport, conf) {
|
||||||
|
passport.use('ldapauth',
|
||||||
|
new LdapStrategy({
|
||||||
|
server: {
|
||||||
|
url: conf.url,
|
||||||
|
bindDn: conf.bindDn,
|
||||||
|
bindCredentials: conf.bindCredentials,
|
||||||
|
searchBase: conf.searchBase,
|
||||||
|
searchFilter: conf.searchFilter,
|
||||||
|
searchAttributes: ['displayName', 'name', 'cn', 'mail'],
|
||||||
|
tlsOptions: (conf.tlsEnabled) ? {
|
||||||
|
ca: [
|
||||||
|
fs.readFileSync(conf.tlsCertPath)
|
||||||
|
]
|
||||||
|
} : {}
|
||||||
|
},
|
||||||
|
usernameField: 'email',
|
||||||
|
passReqToCallback: false
|
||||||
|
}, (profile, cb) => {
|
||||||
|
profile.provider = 'ldap'
|
||||||
|
profile.id = profile.dn
|
||||||
|
wiki.db.User.processProfile(profile).then((user) => {
|
||||||
|
return cb(null, user) || true
|
||||||
|
}).catch((err) => {
|
||||||
|
return cb(err, null) || true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// Local Account
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
const LocalStrategy = require('passport-local').Strategy
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'local',
|
||||||
|
title: 'Local',
|
||||||
|
useForm: true,
|
||||||
|
props: [],
|
||||||
|
init (passport, conf) {
|
||||||
|
passport.use('local',
|
||||||
|
new LocalStrategy({
|
||||||
|
usernameField: 'email',
|
||||||
|
passwordField: 'password'
|
||||||
|
}, (uEmail, uPassword, done) => {
|
||||||
|
wiki.db.User.findOne({ email: uEmail, provider: 'local' }).then((user) => {
|
||||||
|
if (user) {
|
||||||
|
return user.validatePassword(uPassword).then(() => {
|
||||||
|
return done(null, user) || true
|
||||||
|
}).catch((err) => {
|
||||||
|
return done(err, null)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return done(new Error('INVALID_LOGIN'), null)
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
done(err, null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// Microsoft Account
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
const WindowsLiveStrategy = require('passport-windowslive').Strategy
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'microsoft',
|
||||||
|
title: 'Microsoft Account',
|
||||||
|
useForm: false,
|
||||||
|
props: ['clientId', 'clientSecret', 'callbackURL'],
|
||||||
|
init (passport, conf) {
|
||||||
|
passport.use('microsoft',
|
||||||
|
new WindowsLiveStrategy({
|
||||||
|
clientID: conf.clientId,
|
||||||
|
clientSecret: conf.clientSecret,
|
||||||
|
callbackURL: conf.callbackURL
|
||||||
|
}, function (accessToken, refreshToken, profile, cb) {
|
||||||
|
wiki.db.User.processProfile(profile).then((user) => {
|
||||||
|
return cb(null, user) || true
|
||||||
|
}).catch((err) => {
|
||||||
|
return cb(err, null) || true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// Slack Account
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
const SlackStrategy = require('passport-slack').Strategy
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'slack',
|
||||||
|
title: 'Slack',
|
||||||
|
useForm: false,
|
||||||
|
props: ['clientId', 'clientSecret', 'callbackURL'],
|
||||||
|
init (passport, conf) {
|
||||||
|
passport.use('slack',
|
||||||
|
new SlackStrategy({
|
||||||
|
clientID: conf.clientId,
|
||||||
|
clientSecret: conf.clientSecret,
|
||||||
|
callbackURL: conf.callbackURL
|
||||||
|
}, (accessToken, refreshToken, profile, cb) => {
|
||||||
|
wiki.db.User.processProfile(profile).then((user) => {
|
||||||
|
return cb(null, user) || true
|
||||||
|
}).catch((err) => {
|
||||||
|
return cb(err, null) || true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
module.exports = (port, spinner) => {
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const ROOTPATH = process.cwd()
|
/* global wiki */
|
||||||
const SERVERPATH = path.join(ROOTPATH, 'server')
|
|
||||||
const IS_DEBUG = process.env.NODE_ENV === 'development'
|
module.exports = () => {
|
||||||
|
wiki.config.site = {
|
||||||
|
path: '',
|
||||||
|
title: 'Wiki.js'
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load modules
|
// Load modules
|
||||||
@@ -26,28 +27,30 @@ module.exports = (port, spinner) => {
|
|||||||
// Define Express App
|
// Define Express App
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
var app = express()
|
let app = express()
|
||||||
app.use(compression())
|
app.use(compression())
|
||||||
|
|
||||||
var server
|
let server
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Public Assets
|
// Public Assets
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico')))
|
app.use(favicon(path.join(wiki.ROOTPATH, 'assets', 'favicon.ico')))
|
||||||
app.use(express.static(path.join(ROOTPATH, 'assets')))
|
app.use(express.static(path.join(wiki.ROOTPATH, 'assets')))
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// View Engine Setup
|
// View Engine Setup
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.set('views', path.join(SERVERPATH, 'views'))
|
app.set('views', path.join(wiki.SERVERPATH, 'views'))
|
||||||
app.set('view engine', 'pug')
|
app.set('view engine', 'pug')
|
||||||
|
|
||||||
app.use(bodyParser.json())
|
app.use(bodyParser.json())
|
||||||
app.use(bodyParser.urlencoded({ extended: false }))
|
app.use(bodyParser.urlencoded({ extended: false }))
|
||||||
|
|
||||||
|
app.locals.config = wiki.config
|
||||||
|
app.locals.data = wiki.data
|
||||||
app.locals._ = require('lodash')
|
app.locals._ = require('lodash')
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
@@ -55,21 +58,8 @@ module.exports = (port, spinner) => {
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.get('*', (req, res) => {
|
app.get('*', (req, res) => {
|
||||||
let langs = []
|
fs.readJsonAsync(path.join(wiki.ROOTPATH, 'package.json')).then(packageObj => {
|
||||||
let conf = {}
|
res.render('configure/index', { packageObj })
|
||||||
try {
|
|
||||||
langs = yaml.safeLoad(fs.readFileSync(path.join(SERVERPATH, 'app/data.yml'), 'utf8')).langs
|
|
||||||
conf = yaml.safeLoad(fs.readFileSync(path.join(ROOTPATH, 'config.yml'), 'utf8'))
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
res.render('configure/index', {
|
|
||||||
langs,
|
|
||||||
conf,
|
|
||||||
runmode: {
|
|
||||||
staticPort: (process.env.WIKI_JS_HEROKU || process.env.WIKI_JS_DOCKER),
|
|
||||||
staticMongo: (!_.isNil(process.env.WIKI_JS_HEROKU))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -80,15 +70,15 @@ module.exports = (port, spinner) => {
|
|||||||
Promise.mapSeries([
|
Promise.mapSeries([
|
||||||
() => {
|
() => {
|
||||||
const semver = require('semver')
|
const semver = require('semver')
|
||||||
if (!semver.satisfies(semver.clean(process.version), '>=6.9.0')) {
|
if (!semver.satisfies(semver.clean(process.version), '>=6.11.1')) {
|
||||||
throw new Error('Node.js version is too old. Minimum is v6.6.0.')
|
throw new Error('Node.js version is too old. Minimum is 6.11.1.')
|
||||||
}
|
}
|
||||||
return 'Node.js ' + process.version + ' detected. Minimum is v6.9.0.'
|
return 'Node.js ' + process.version + ' detected. Minimum is 6.11.1.'
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
return Promise.try(() => {
|
return Promise.try(() => {
|
||||||
require('crypto')
|
require('crypto')
|
||||||
}).catch(err => { // eslint-disable-line handle-callback-err
|
}).catch(err => {
|
||||||
throw new Error('Crypto Node.js module is not available.')
|
throw new Error('Crypto Node.js module is not available.')
|
||||||
}).return('Node.js Crypto module is available.')
|
}).return('Node.js Crypto module is available.')
|
||||||
},
|
},
|
||||||
@@ -102,24 +92,24 @@ module.exports = (port, spinner) => {
|
|||||||
}
|
}
|
||||||
let gitver = _.head(stdout.match(/[\d]+\.[\d]+(\.[\d]+)?/gi))
|
let gitver = _.head(stdout.match(/[\d]+\.[\d]+(\.[\d]+)?/gi))
|
||||||
if (!gitver || !semver.satisfies(semver.clean(gitver), '>=2.7.4')) {
|
if (!gitver || !semver.satisfies(semver.clean(gitver), '>=2.7.4')) {
|
||||||
reject(new Error('Git version is too old. Minimum is v2.7.4.'))
|
reject(new Error('Git version is too old. Minimum is 2.7.4.'))
|
||||||
}
|
}
|
||||||
resolve('Git v' + gitver + ' detected. Minimum is v2.7.4.')
|
resolve('Git ' + gitver + ' detected. Minimum is 2.7.4.')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
if (os.totalmem() < 1000 * 1000 * 768) {
|
if (os.totalmem() < 1000 * 1000 * 512) {
|
||||||
throw new Error('Not enough memory. Minimum is 768 MB.')
|
throw new Error('Not enough memory. Minimum is 512 MB.')
|
||||||
}
|
}
|
||||||
return _.round(os.totalmem() / (1024 * 1024)) + ' MB of system memory available. Minimum is 768 MB.'
|
return _.round(os.totalmem() / (1024 * 1024)) + ' MB of system memory available. Minimum is 512 MB.'
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
let fs = require('fs')
|
let fs = require('fs')
|
||||||
return Promise.try(() => {
|
return Promise.try(() => {
|
||||||
fs.accessSync(path.join(ROOTPATH, 'config.yml'), (fs.constants || fs).W_OK)
|
fs.accessSync(path.join(wiki.ROOTPATH, 'config.yml'), (fs.constants || fs).W_OK)
|
||||||
}).catch(err => { // eslint-disable-line handle-callback-err
|
}).catch(err => {
|
||||||
throw new Error('config.yml file is not writable by Node.js process or was not created properly.')
|
throw new Error('config.yml file is not writable by Node.js process or was not created properly.')
|
||||||
}).return('config.yml is writable by the setup process.')
|
}).return('config.yml is writable by the setup process.')
|
||||||
}
|
}
|
||||||
@@ -130,43 +120,6 @@ module.exports = (port, spinner) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the DB connection
|
|
||||||
*/
|
|
||||||
app.post('/dbcheck', (req, res) => {
|
|
||||||
let mongo = require('mongodb').MongoClient
|
|
||||||
let mongoURI = cfgHelper.parseConfigValue(req.body.db)
|
|
||||||
mongo.connect(mongoURI, {
|
|
||||||
autoReconnect: false,
|
|
||||||
reconnectTries: 2,
|
|
||||||
reconnectInterval: 1000,
|
|
||||||
connectTimeoutMS: 5000,
|
|
||||||
socketTimeoutMS: 5000
|
|
||||||
}, (err, db) => {
|
|
||||||
if (err === null) {
|
|
||||||
// Try to create a test collection
|
|
||||||
db.createCollection('test', (err, results) => {
|
|
||||||
if (err === null) {
|
|
||||||
// Try to drop test collection
|
|
||||||
db.dropCollection('test', (err, results) => {
|
|
||||||
if (err === null) {
|
|
||||||
res.json({ ok: true })
|
|
||||||
} else {
|
|
||||||
res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message })
|
|
||||||
}
|
|
||||||
db.close()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message })
|
|
||||||
db.close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
res.json({ ok: false, error: err.message })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the Git connection
|
* Check the Git connection
|
||||||
*/
|
*/
|
||||||
@@ -174,8 +127,8 @@ module.exports = (port, spinner) => {
|
|||||||
const exec = require('execa')
|
const exec = require('execa')
|
||||||
const url = require('url')
|
const url = require('url')
|
||||||
|
|
||||||
const dataDir = path.resolve(ROOTPATH, cfgHelper.parseConfigValue(req.body.pathData))
|
const dataDir = path.resolve(wiki.ROOTPATH, cfgHelper.parseConfigValue(req.body.pathData))
|
||||||
const gitDir = path.resolve(ROOTPATH, cfgHelper.parseConfigValue(req.body.pathRepo))
|
const gitDir = path.resolve(wiki.ROOTPATH, cfgHelper.parseConfigValue(req.body.pathRepo))
|
||||||
|
|
||||||
let gitRemoteUrl = ''
|
let gitRemoteUrl = ''
|
||||||
|
|
||||||
@@ -315,7 +268,7 @@ module.exports = (port, spinner) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
fs.readFileAsync(path.join(ROOTPATH, 'config.yml'), 'utf8').then(confRaw => {
|
fs.readFileAsync(path.join(wiki.ROOTPATH, 'config.yml'), 'utf8').then(confRaw => {
|
||||||
let conf = yaml.safeLoad(confRaw)
|
let conf = yaml.safeLoad(confRaw)
|
||||||
conf.title = req.body.title
|
conf.title = req.body.title
|
||||||
conf.host = req.body.host
|
conf.host = req.body.host
|
||||||
@@ -356,12 +309,12 @@ module.exports = (port, spinner) => {
|
|||||||
return crypto.randomBytesAsync(32).then(buf => {
|
return crypto.randomBytesAsync(32).then(buf => {
|
||||||
conf.sessionSecret = buf.toString('hex')
|
conf.sessionSecret = buf.toString('hex')
|
||||||
confRaw = yaml.safeDump(conf)
|
confRaw = yaml.safeDump(conf)
|
||||||
return fs.writeFileAsync(path.join(ROOTPATH, 'config.yml'), confRaw)
|
return fs.writeFileAsync(path.join(wiki.ROOTPATH, 'config.yml'), confRaw)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
).then(() => {
|
).then(() => {
|
||||||
if (process.env.IS_HEROKU) {
|
if (process.env.IS_HEROKU) {
|
||||||
return fs.outputJsonAsync(path.join(SERVERPATH, 'app/heroku.json'), { configured: true })
|
return fs.outputJsonAsync(path.join(wiki.SERVERPATH, 'app/heroku.json'), { configured: true })
|
||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -377,7 +330,7 @@ module.exports = (port, spinner) => {
|
|||||||
*/
|
*/
|
||||||
app.post('/restart', (req, res) => {
|
app.post('/restart', (req, res) => {
|
||||||
res.status(204).end()
|
res.status(204).end()
|
||||||
server.destroy(() => {
|
/* server.destroy(() => {
|
||||||
spinner.text = 'Setup wizard terminated. Restarting in normal mode...'
|
spinner.text = 'Setup wizard terminated. Restarting in normal mode...'
|
||||||
_.delay(() => {
|
_.delay(() => {
|
||||||
const exec = require('execa')
|
const exec = require('execa')
|
||||||
@@ -386,7 +339,7 @@ module.exports = (port, spinner) => {
|
|||||||
process.exit(0)
|
process.exit(0)
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
})
|
}) */
|
||||||
})
|
})
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
@@ -403,21 +356,20 @@ module.exports = (port, spinner) => {
|
|||||||
res.status(err.status || 500)
|
res.status(err.status || 500)
|
||||||
res.send({
|
res.send({
|
||||||
message: err.message,
|
message: err.message,
|
||||||
error: IS_DEBUG ? err : {}
|
error: wiki.IS_DEBUG ? err : {}
|
||||||
})
|
})
|
||||||
spinner.fail(err.message)
|
wiki.logger.error(err.message)
|
||||||
process.exit(1)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Start HTTP server
|
// Start HTTP server
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
spinner.text = 'Starting HTTP server...'
|
wiki.logger.info(`HTTP Server on port: ${wiki.config.port}`)
|
||||||
|
|
||||||
app.set('port', port)
|
app.set('port', wiki.config.port)
|
||||||
server = http.createServer(app)
|
server = http.createServer(app)
|
||||||
server.listen(port)
|
server.listen(wiki.config.port)
|
||||||
|
|
||||||
var openConnections = []
|
var openConnections = []
|
||||||
|
|
||||||
@@ -443,10 +395,10 @@ module.exports = (port, spinner) => {
|
|||||||
|
|
||||||
switch (error.code) {
|
switch (error.code) {
|
||||||
case 'EACCES':
|
case 'EACCES':
|
||||||
spinner.fail('Listening on port ' + port + ' requires elevated privileges!')
|
wiki.logger.error('Listening on port ' + wiki.config.port + ' requires elevated privileges!')
|
||||||
return process.exit(1)
|
return process.exit(1)
|
||||||
case 'EADDRINUSE':
|
case 'EADDRINUSE':
|
||||||
spinner.fail('Port ' + port + ' is already in use!')
|
wiki.logger.error('Port ' + wiki.config.port + ' is already in use!')
|
||||||
return process.exit(1)
|
return process.exit(1)
|
||||||
default:
|
default:
|
||||||
throw error
|
throw error
|
||||||
@@ -454,6 +406,6 @@ module.exports = (port, spinner) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
server.on('listening', () => {
|
server.on('listening', () => {
|
||||||
spinner.text = 'Browse to http://localhost:' + port + ' to configure Wiki.js!'
|
wiki.logger.info('HTTP Server: RUNNING')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global db, lang, rights, winston */
|
/* global wiki */
|
||||||
|
|
||||||
var express = require('express')
|
var express = require('express')
|
||||||
var router = express.Router()
|
var router = express.Router()
|
||||||
@@ -33,14 +33,14 @@ router.post('/profile', (req, res) => {
|
|||||||
return res.render('error-forbidden')
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.User.findById(req.user.id).then((usr) => {
|
return wiki.db.User.findById(req.user.id).then((usr) => {
|
||||||
usr.name = _.trim(req.body.name)
|
usr.name = _.trim(req.body.name)
|
||||||
if (usr.provider === 'local' && req.body.password !== '********') {
|
if (usr.provider === 'local' && req.body.password !== '********') {
|
||||||
let nPwd = _.trim(req.body.password)
|
let nPwd = _.trim(req.body.password)
|
||||||
if (nPwd.length < 6) {
|
if (nPwd.length < 6) {
|
||||||
return Promise.reject(new Error('New Password too short!'))
|
return Promise.reject(new Error('New Password too short!'))
|
||||||
} else {
|
} else {
|
||||||
return db.User.hashPassword(nPwd).then((pwd) => {
|
return wiki.db.User.hashPassword(nPwd).then((pwd) => {
|
||||||
usr.password = pwd
|
usr.password = pwd
|
||||||
return usr.save()
|
return usr.save()
|
||||||
})
|
})
|
||||||
@@ -61,9 +61,9 @@ router.get('/stats', (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
db.Entry.count(),
|
wiki.db.Entry.count(),
|
||||||
db.UplFile.count(),
|
wiki.db.UplFile.count(),
|
||||||
db.User.count()
|
wiki.db.User.count()
|
||||||
]).spread((totalEntries, totalUploads, totalUsers) => {
|
]).spread((totalEntries, totalUploads, totalUsers) => {
|
||||||
return res.render('pages/admin/stats', {
|
return res.render('pages/admin/stats', {
|
||||||
totalEntries, totalUploads, totalUsers, adminTab: 'stats'
|
totalEntries, totalUploads, totalUsers, adminTab: 'stats'
|
||||||
@@ -78,7 +78,7 @@ router.get('/users', (req, res) => {
|
|||||||
return res.render('error-forbidden')
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
db.User.find({})
|
wiki.db.User.find({})
|
||||||
.select('-password -rights')
|
.select('-password -rights')
|
||||||
.sort('name email')
|
.sort('name email')
|
||||||
.exec().then((usrs) => {
|
.exec().then((usrs) => {
|
||||||
@@ -95,7 +95,7 @@ router.get('/users/:id', (req, res) => {
|
|||||||
return res.render('error-forbidden')
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
db.User.findById(req.params.id)
|
wiki.db.User.findById(req.params.id)
|
||||||
.select('-password -providerId')
|
.select('-password -providerId')
|
||||||
.exec().then((usr) => {
|
.exec().then((usr) => {
|
||||||
let usrOpts = {
|
let usrOpts = {
|
||||||
@@ -137,12 +137,12 @@ router.post('/users/create', (req, res) => {
|
|||||||
return res.status(400).json({ msg: 'Name is missing' })
|
return res.status(400).json({ msg: 'Name is missing' })
|
||||||
}
|
}
|
||||||
|
|
||||||
db.User.findOne({ email: nUsr.email, provider: nUsr.provider }).then(exUsr => {
|
wiki.db.User.findOne({ email: nUsr.email, provider: nUsr.provider }).then(exUsr => {
|
||||||
if (exUsr) {
|
if (exUsr) {
|
||||||
return res.status(400).json({ msg: 'User already exists!' }) || true
|
return res.status(400).json({ msg: 'User already exists!' }) || true
|
||||||
}
|
}
|
||||||
|
|
||||||
let pwdGen = (nUsr.provider === 'local') ? db.User.hashPassword(nUsr.password) : Promise.resolve(true)
|
let pwdGen = (nUsr.provider === 'local') ? wiki.db.User.hashPassword(nUsr.password) : Promise.resolve(true)
|
||||||
return pwdGen.then(nPwd => {
|
return pwdGen.then(nPwd => {
|
||||||
if (nUsr.provider !== 'local') {
|
if (nUsr.provider !== 'local') {
|
||||||
nUsr.password = ''
|
nUsr.password = ''
|
||||||
@@ -158,37 +158,37 @@ router.post('/users/create', (req, res) => {
|
|||||||
deny: false
|
deny: false
|
||||||
}]
|
}]
|
||||||
|
|
||||||
return db.User.create(nUsr).then(() => {
|
return wiki.db.User.create(nUsr).then(() => {
|
||||||
return res.json({ ok: true })
|
return res.json({ ok: true })
|
||||||
})
|
})
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
winston.warn(err)
|
wiki.logger.warn(err)
|
||||||
return res.status(500).json({ msg: err })
|
return res.status(500).json({ msg: err })
|
||||||
})
|
})
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
winston.warn(err)
|
wiki.logger.warn(err)
|
||||||
return res.status(500).json({ msg: err })
|
return res.status(500).json({ msg: err })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/users/:id', (req, res) => {
|
router.post('/users/:id', (req, res) => {
|
||||||
if (!res.locals.rights.manage) {
|
if (!res.locals.rights.manage) {
|
||||||
return res.status(401).json({ msg: lang.t('errors:unauthorized') })
|
return res.status(401).json({ msg: wiki.lang.t('errors:unauthorized') })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validator.isMongoId(req.params.id)) {
|
if (!validator.isMongoId(req.params.id)) {
|
||||||
return res.status(400).json({ msg: lang.t('errors:invaliduserid') })
|
return res.status(400).json({ msg: wiki.lang.t('errors:invaliduserid') })
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.User.findById(req.params.id).then((usr) => {
|
return wiki.db.User.findById(req.params.id).then((usr) => {
|
||||||
usr.name = _.trim(req.body.name)
|
usr.name = _.trim(req.body.name)
|
||||||
usr.rights = JSON.parse(req.body.rights)
|
usr.rights = JSON.parse(req.body.rights)
|
||||||
if (usr.provider === 'local' && req.body.password !== '********') {
|
if (usr.provider === 'local' && req.body.password !== '********') {
|
||||||
let nPwd = _.trim(req.body.password)
|
let nPwd = _.trim(req.body.password)
|
||||||
if (nPwd.length < 6) {
|
if (nPwd.length < 6) {
|
||||||
return Promise.reject(new Error(lang.t('errors:newpasswordtooshort')))
|
return Promise.reject(new Error(wiki.lang.t('errors:newpasswordtooshort')))
|
||||||
} else {
|
} else {
|
||||||
return db.User.hashPassword(nPwd).then((pwd) => {
|
return wiki.db.User.hashPassword(nPwd).then((pwd) => {
|
||||||
usr.password = pwd
|
usr.password = pwd
|
||||||
return usr.save()
|
return usr.save()
|
||||||
})
|
})
|
||||||
@@ -199,7 +199,7 @@ router.post('/users/:id', (req, res) => {
|
|||||||
}).then((usr) => {
|
}).then((usr) => {
|
||||||
// Update guest rights for future requests
|
// Update guest rights for future requests
|
||||||
if (usr.provider === 'local' && usr.email === 'guest') {
|
if (usr.provider === 'local' && usr.email === 'guest') {
|
||||||
rights.guest = usr
|
wiki.rights.guest = usr
|
||||||
}
|
}
|
||||||
return usr
|
return usr
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@@ -214,14 +214,14 @@ router.post('/users/:id', (req, res) => {
|
|||||||
*/
|
*/
|
||||||
router.delete('/users/:id', (req, res) => {
|
router.delete('/users/:id', (req, res) => {
|
||||||
if (!res.locals.rights.manage) {
|
if (!res.locals.rights.manage) {
|
||||||
return res.status(401).json({ msg: lang.t('errors:unauthorized') })
|
return res.status(401).json({ msg: wiki.lang.t('errors:unauthorized') })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validator.isMongoId(req.params.id)) {
|
if (!validator.isMongoId(req.params.id)) {
|
||||||
return res.status(400).json({ msg: lang.t('errors:invaliduserid') })
|
return res.status(400).json({ msg: wiki.lang.t('errors:invaliduserid') })
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.User.findByIdAndRemove(req.params.id).then(() => {
|
return wiki.db.User.findByIdAndRemove(req.params.id).then(() => {
|
||||||
return res.json({ ok: true })
|
return res.json({ ok: true })
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
res.status(500).json({ ok: false, msg: err.message })
|
res.status(500).json({ ok: false, msg: err.message })
|
||||||
@@ -249,7 +249,7 @@ router.get('/system', (req, res) => {
|
|||||||
cwd: process.cwd()
|
cwd: process.cwd()
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.readJsonAsync(path.join(ROOTPATH, 'package.json')).then(packageObj => {
|
fs.readJsonAsync(path.join(wiki.ROOTPATH, 'package.json')).then(packageObj => {
|
||||||
axios.get('https://api.github.com/repos/Requarks/wiki/releases/latest').then(resp => {
|
axios.get('https://api.github.com/repos/Requarks/wiki/releases/latest').then(resp => {
|
||||||
let sysversion = {
|
let sysversion = {
|
||||||
current: 'v' + packageObj.version,
|
current: 'v' + packageObj.version,
|
||||||
@@ -259,7 +259,7 @@ router.get('/system', (req, res) => {
|
|||||||
|
|
||||||
res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion })
|
res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion })
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
winston.warn(err)
|
wiki.logger.warn(err)
|
||||||
res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion: { current: 'v' + packageObj.version } })
|
res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion: { current: 'v' + packageObj.version } })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -287,19 +287,19 @@ router.post('/theme', (req, res) => {
|
|||||||
return res.render('error-forbidden')
|
return res.render('error-forbidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validator.isIn(req.body.primary, appdata.colors)) {
|
if (!validator.isIn(req.body.primary, wiki.data.colors)) {
|
||||||
return res.status(406).json({ msg: 'Primary color is invalid.' })
|
return res.status(406).json({ msg: 'Primary color is invalid.' })
|
||||||
} else if (!validator.isIn(req.body.alt, appdata.colors)) {
|
} else if (!validator.isIn(req.body.alt, wiki.data.colors)) {
|
||||||
return res.status(406).json({ msg: 'Alternate color is invalid.' })
|
return res.status(406).json({ msg: 'Alternate color is invalid.' })
|
||||||
} else if (!validator.isIn(req.body.footer, appdata.colors)) {
|
} else if (!validator.isIn(req.body.footer, wiki.data.colors)) {
|
||||||
return res.status(406).json({ msg: 'Footer color is invalid.' })
|
return res.status(406).json({ msg: 'Footer color is invalid.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
appconfig.theme.primary = req.body.primary
|
wiki.config.theme.primary = req.body.primary
|
||||||
appconfig.theme.alt = req.body.alt
|
wiki.config.theme.alt = req.body.alt
|
||||||
appconfig.theme.footer = req.body.footer
|
wiki.config.theme.footer = req.body.footer
|
||||||
appconfig.theme.code.dark = req.body.codedark === 'true'
|
wiki.config.theme.code.dark = req.body.codedark === 'true'
|
||||||
appconfig.theme.code.colorize = req.body.codecolorize === 'true'
|
wiki.config.theme.code.colorize = req.body.codecolorize === 'true'
|
||||||
|
|
||||||
return res.json({ msg: 'OK' })
|
return res.json({ msg: 'OK' })
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
'use strict'
|
/* global wiki */
|
||||||
|
|
||||||
/* global db, lang */
|
|
||||||
|
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const passport = require('passport')
|
|
||||||
const ExpressBrute = require('express-brute')
|
const ExpressBrute = require('express-brute')
|
||||||
const ExpressBruteMongooseStore = require('express-brute-mongoose')
|
const ExpressBruteRedisStore = require('express-brute-redis')
|
||||||
const moment = require('moment')
|
const moment = require('moment')
|
||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup Express-Brute
|
* Setup Express-Brute
|
||||||
*/
|
*/
|
||||||
const EBstore = new ExpressBruteMongooseStore(db.Bruteforce)
|
const EBstore = new ExpressBruteRedisStore({
|
||||||
|
client: wiki.redis
|
||||||
|
})
|
||||||
const bruteforce = new ExpressBrute(EBstore, {
|
const bruteforce = new ExpressBrute(EBstore, {
|
||||||
freeRetries: 5,
|
freeRetries: 5,
|
||||||
minWait: 60 * 1000,
|
minWait: 60 * 1000,
|
||||||
@@ -22,8 +22,8 @@ const bruteforce = new ExpressBrute(EBstore, {
|
|||||||
failCallback (req, res, next, nextValidRequestDate) {
|
failCallback (req, res, next, nextValidRequestDate) {
|
||||||
req.flash('alert', {
|
req.flash('alert', {
|
||||||
class: 'error',
|
class: 'error',
|
||||||
title: lang.t('auth:errors.toomanyattempts'),
|
title: wiki.lang.t('auth:errors.toomanyattempts'),
|
||||||
message: lang.t('auth:errors.toomanyattemptsmsg', { time: moment(nextValidRequestDate).fromNow() }),
|
message: wiki.lang.t('auth:errors.toomanyattemptsmsg', { time: moment(nextValidRequestDate).fromNow() }),
|
||||||
iconClass: 'fa-times'
|
iconClass: 'fa-times'
|
||||||
})
|
})
|
||||||
res.redirect('/login')
|
res.redirect('/login')
|
||||||
@@ -35,23 +35,24 @@ const bruteforce = new ExpressBrute(EBstore, {
|
|||||||
*/
|
*/
|
||||||
router.get('/login', function (req, res, next) {
|
router.get('/login', function (req, res, next) {
|
||||||
res.render('auth/login', {
|
res.render('auth/login', {
|
||||||
usr: res.locals.usr
|
authStrategies: _.reject(wiki.auth.strategies, { key: 'local' }),
|
||||||
|
hasMultipleStrategies: Object.keys(wiki.config.auth.strategies).length > 1
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/login', bruteforce.prevent, function (req, res, next) {
|
router.post('/login', bruteforce.prevent, function (req, res, next) {
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
// [1] LOCAL AUTHENTICATION
|
// [1] LOCAL AUTHENTICATION
|
||||||
passport.authenticate('local', function (err, user, info) {
|
wiki.auth.passport.authenticate('local', function (err, user, info) {
|
||||||
if (err) { return reject(err) }
|
if (err) { return reject(err) }
|
||||||
if (!user) { return reject(new Error('INVALID_LOGIN')) }
|
if (!user) { return reject(new Error('INVALID_LOGIN')) }
|
||||||
resolve(user)
|
resolve(user)
|
||||||
})(req, res, next)
|
})(req, res, next)
|
||||||
}).catch({ message: 'INVALID_LOGIN' }, err => {
|
}).catch({ message: 'INVALID_LOGIN' }, err => {
|
||||||
if (appconfig.auth.ldap && appconfig.auth.ldap.enabled) {
|
if (_.has(wiki.config.auth.strategy, 'ldap')) {
|
||||||
// [2] LDAP AUTHENTICATION
|
// [2] LDAP AUTHENTICATION
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
passport.authenticate('ldapauth', function (err, user, info) {
|
wiki.auth.passport.authenticate('ldapauth', function (err, user, info) {
|
||||||
if (err) { return reject(err) }
|
if (err) { return reject(err) }
|
||||||
if (info && info.message) { return reject(new Error(info.message)) }
|
if (info && info.message) { return reject(new Error(info.message)) }
|
||||||
if (!user) { return reject(new Error('INVALID_LOGIN')) }
|
if (!user) { return reject(new Error('INVALID_LOGIN')) }
|
||||||
@@ -73,13 +74,13 @@ router.post('/login', bruteforce.prevent, function (req, res, next) {
|
|||||||
// LOGIN FAIL
|
// LOGIN FAIL
|
||||||
if (err.message === 'INVALID_LOGIN') {
|
if (err.message === 'INVALID_LOGIN') {
|
||||||
req.flash('alert', {
|
req.flash('alert', {
|
||||||
title: lang.t('auth:errors.invalidlogin'),
|
title: wiki.lang.t('auth:errors.invalidlogin'),
|
||||||
message: lang.t('auth:errors.invalidloginmsg')
|
message: wiki.lang.t('auth:errors.invalidloginmsg')
|
||||||
})
|
})
|
||||||
return res.redirect('/login')
|
return res.redirect('/login')
|
||||||
} else {
|
} else {
|
||||||
req.flash('alert', {
|
req.flash('alert', {
|
||||||
title: lang.t('auth:errors.loginerror'),
|
title: wiki.lang.t('auth:errors.loginerror'),
|
||||||
message: err.message
|
message: err.message
|
||||||
})
|
})
|
||||||
return res.redirect('/login')
|
return res.redirect('/login')
|
||||||
@@ -91,19 +92,19 @@ router.post('/login', bruteforce.prevent, function (req, res, next) {
|
|||||||
* Social Login
|
* Social Login
|
||||||
*/
|
*/
|
||||||
|
|
||||||
router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }))
|
router.get('/login/ms', wiki.auth.passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }))
|
||||||
router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] }))
|
router.get('/login/google', wiki.auth.passport.authenticate('google', { scope: ['profile', 'email'] }))
|
||||||
router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
|
router.get('/login/facebook', wiki.auth.passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
|
||||||
router.get('/login/github', passport.authenticate('github', { scope: ['user:email'] }))
|
router.get('/login/github', wiki.auth.passport.authenticate('github', { scope: ['user:email'] }))
|
||||||
router.get('/login/slack', passport.authenticate('slack', { scope: ['identity.basic', 'identity.email'] }))
|
router.get('/login/slack', wiki.auth.passport.authenticate('slack', { scope: ['identity.basic', 'identity.email'] }))
|
||||||
router.get('/login/azure', passport.authenticate('azure_ad_oauth2'))
|
router.get('/login/azure', wiki.auth.passport.authenticate('azure_ad_oauth2'))
|
||||||
|
|
||||||
router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
|
router.get('/login/ms/callback', wiki.auth.passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }))
|
router.get('/login/google/callback', wiki.auth.passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
|
router.get('/login/facebook/callback', wiki.auth.passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
router.get('/login/github/callback', passport.authenticate('github', { failureRedirect: '/login', successRedirect: '/' }))
|
router.get('/login/github/callback', wiki.auth.passport.authenticate('github', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
router.get('/login/slack/callback', passport.authenticate('slack', { failureRedirect: '/login', successRedirect: '/' }))
|
router.get('/login/slack/callback', wiki.auth.passport.authenticate('slack', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
router.get('/login/azure/callback', passport.authenticate('azure_ad_oauth2', { failureRedirect: '/login', successRedirect: '/' }))
|
router.get('/login/azure/callback', wiki.auth.passport.authenticate('azure_ad_oauth2', { failureRedirect: '/login', successRedirect: '/' }))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout
|
* Logout
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global git, lang, lcdata, upl */
|
/* global wiki */
|
||||||
|
|
||||||
|
module.exports = false
|
||||||
|
return
|
||||||
|
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
@@ -12,7 +15,7 @@ const fs = Promise.promisifyAll(require('fs-extra'))
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
|
||||||
const validPathRe = new RegExp('^([a-z0-9/-' + appdata.regex.cjk + appdata.regex.arabic + ']+\\.[a-z0-9]+)$')
|
const validPathRe = new RegExp('^([a-z0-9/-' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']+\\.[a-z0-9]+)$')
|
||||||
const validPathThumbsRe = new RegExp('^([a-z0-9]+\\.png)$')
|
const validPathThumbsRe = new RegExp('^([a-z0-9]+\\.png)$')
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -28,7 +31,7 @@ router.get('/t/*', (req, res, next) => {
|
|||||||
// todo: Authentication-based access
|
// todo: Authentication-based access
|
||||||
|
|
||||||
res.sendFile(fileName, {
|
res.sendFile(fileName, {
|
||||||
root: lcdata.getThumbsPath(),
|
root: wiki.disk.getThumbsPath(),
|
||||||
dotfiles: 'deny'
|
dotfiles: 'deny'
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -37,12 +40,12 @@ router.get('/t/*', (req, res, next) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
router.post('/img', wiki.disk.uploadImgHandler, (req, res, next) => {
|
||||||
let destFolder = _.chain(req.body.folder).trim().toLower().value()
|
let destFolder = _.chain(req.body.folder).trim().toLower().value()
|
||||||
|
|
||||||
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
||||||
if (!destFolderPath) {
|
if (!destFolderPath) {
|
||||||
res.json({ ok: false, msg: lang.t('errors:invalidfolder') })
|
res.json({ ok: false, msg: wiki.lang.t('errors:invalidfolder') })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +53,7 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
|||||||
let destFilename = ''
|
let destFilename = ''
|
||||||
let destFilePath = ''
|
let destFilePath = ''
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
|
return wiki.disk.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
|
||||||
destFilename = fname
|
destFilename = fname
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename)
|
destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
|
|
||||||
@@ -60,7 +63,7 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
|||||||
|
|
||||||
let mimeInfo = fileType(buf)
|
let mimeInfo = fileType(buf)
|
||||||
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
||||||
return Promise.reject(new Error(lang.t('errors:invalidfiletype')))
|
return Promise.reject(new Error(wiki.lang.t('errors:invalidfiletype')))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@@ -94,12 +97,12 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
|
router.post('/file', wiki.disk.uploadFileHandler, (req, res, next) => {
|
||||||
let destFolder = _.chain(req.body.folder).trim().toLower().value()
|
let destFolder = _.chain(req.body.folder).trim().toLower().value()
|
||||||
|
|
||||||
upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
|
||||||
if (!destFolderPath) {
|
if (!destFolderPath) {
|
||||||
res.json({ ok: false, msg: lang.t('errors:invalidfolder') })
|
res.json({ ok: false, msg: wiki.lang.t('errors:invalidfolder') })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +110,7 @@ router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
|
|||||||
let destFilename = ''
|
let destFilename = ''
|
||||||
let destFilePath = ''
|
let destFilePath = ''
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
|
return wiki.disk.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
|
||||||
destFilename = fname
|
destFilename = fname
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename)
|
destFilePath = path.resolve(destFolderPath, destFilename)
|
||||||
|
|
||||||
@@ -150,7 +153,7 @@ router.get('/*', (req, res, next) => {
|
|||||||
// todo: Authentication-based access
|
// todo: Authentication-based access
|
||||||
|
|
||||||
res.sendFile(fileName, {
|
res.sendFile(fileName, {
|
||||||
root: git.getRepoPath() + '/uploads/',
|
root: wiki.git.getRepoPath() + '/uploads/',
|
||||||
dotfiles: 'deny'
|
dotfiles: 'deny'
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@@ -1,276 +1,43 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
// ===========================================
|
// ===========================================
|
||||||
// Wiki.js
|
// Wiki.js
|
||||||
// 1.0.0
|
|
||||||
// Licensed under AGPLv3
|
// Licensed under AGPLv3
|
||||||
// ===========================================
|
// ===========================================
|
||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const ROOTPATH = process.cwd()
|
const cluster = require('cluster')
|
||||||
const SERVERPATH = path.join(ROOTPATH, 'server')
|
|
||||||
|
|
||||||
global.ROOTPATH = ROOTPATH
|
let wiki = {
|
||||||
global.SERVERPATH = SERVERPATH
|
IS_DEBUG: process.env.NODE_ENV === 'development',
|
||||||
const IS_DEBUG = process.env.NODE_ENV === 'development'
|
IS_MASTER: cluster.isMaster,
|
||||||
|
ROOTPATH: process.cwd(),
|
||||||
|
SERVERPATH: path.join(process.cwd(), 'server'),
|
||||||
|
configSvc: require('./modules/config'),
|
||||||
|
kernel: require('./modules/kernel')
|
||||||
|
}
|
||||||
|
global.wiki = wiki
|
||||||
|
|
||||||
process.env.VIPS_WARNING = false
|
process.env.VIPS_WARNING = false
|
||||||
|
|
||||||
// if (IS_DEBUG) {
|
// if (wiki.IS_DEBUG) {
|
||||||
// require('@glimpse/glimpse').init()
|
// require('@glimpse/glimpse').init()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let appconf = require('./libs/config')()
|
wiki.configSvc.init()
|
||||||
global.appconfig = appconf.config
|
|
||||||
global.appdata = appconf.data
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load Winston
|
// Init Logger
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
global.winston = require('./libs/logger')(IS_DEBUG, 'SERVER')
|
wiki.logger = require('./modules/logger').init()
|
||||||
global.winston.info('Wiki.js is initializing...')
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load global modules
|
// Init DB
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
global.lcdata = require('./libs/local').init()
|
wiki.db = require('./modules/db').init()
|
||||||
global.db = require('./libs/db').init()
|
|
||||||
global.entries = require('./libs/entries').init()
|
|
||||||
global.git = require('./libs/git').init(false)
|
|
||||||
global.lang = require('i18next')
|
|
||||||
global.mark = require('./libs/markdown')
|
|
||||||
global.search = require('./libs/search').init()
|
|
||||||
global.upl = require('./libs/uploads').init()
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load modules
|
// Start Kernel
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
const autoload = require('auto-load')
|
wiki.kernel.init()
|
||||||
const bodyParser = require('body-parser')
|
|
||||||
const compression = require('compression')
|
|
||||||
const cookieParser = require('cookie-parser')
|
|
||||||
const express = require('express')
|
|
||||||
const favicon = require('serve-favicon')
|
|
||||||
const flash = require('connect-flash')
|
|
||||||
const fork = require('child_process').fork
|
|
||||||
const http = require('http')
|
|
||||||
const i18nBackend = require('i18next-node-fs-backend')
|
|
||||||
const passport = require('passport')
|
|
||||||
const passportSocketIo = require('passport.socketio')
|
|
||||||
const session = require('express-session')
|
|
||||||
const SessionMongoStore = require('connect-mongo')(session)
|
|
||||||
const graceful = require('node-graceful')
|
|
||||||
const socketio = require('socket.io')
|
|
||||||
|
|
||||||
var mw = autoload(path.join(SERVERPATH, '/middlewares'))
|
|
||||||
var ctrl = autoload(path.join(SERVERPATH, '/controllers'))
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Define Express App
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
const app = express()
|
|
||||||
global.app = app
|
|
||||||
app.use(compression())
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Security
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
app.use(mw.security)
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Public Assets
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico')))
|
|
||||||
app.use(express.static(path.join(ROOTPATH, 'assets'), {
|
|
||||||
index: false,
|
|
||||||
maxAge: '7d'
|
|
||||||
}))
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Passport Authentication
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
require('./libs/auth')(passport)
|
|
||||||
global.rights = require('./libs/rights')
|
|
||||||
global.rights.init()
|
|
||||||
|
|
||||||
let sessionStore = new SessionMongoStore({
|
|
||||||
mongooseConnection: global.db.connection,
|
|
||||||
touchAfter: 15
|
|
||||||
})
|
|
||||||
|
|
||||||
app.use(cookieParser())
|
|
||||||
app.use(session({
|
|
||||||
name: 'wikijs.sid',
|
|
||||||
store: sessionStore,
|
|
||||||
secret: appconfig.sessionSecret,
|
|
||||||
resave: false,
|
|
||||||
saveUninitialized: false
|
|
||||||
}))
|
|
||||||
app.use(flash())
|
|
||||||
app.use(passport.initialize())
|
|
||||||
app.use(passport.session())
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// SEO
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
app.use(mw.seo)
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Localization Engine
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
global.lang
|
|
||||||
.use(i18nBackend)
|
|
||||||
.init({
|
|
||||||
load: 'languageOnly',
|
|
||||||
ns: ['common', 'admin', 'auth', 'errors', 'git'],
|
|
||||||
defaultNS: 'common',
|
|
||||||
saveMissing: false,
|
|
||||||
preload: [appconfig.lang],
|
|
||||||
lng: appconfig.lang,
|
|
||||||
fallbackLng: 'en',
|
|
||||||
backend: {
|
|
||||||
loadPath: path.join(SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// View Engine Setup
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
app.set('views', path.join(SERVERPATH, 'views'))
|
|
||||||
app.set('view engine', 'pug')
|
|
||||||
|
|
||||||
app.use(bodyParser.json({ limit: '1mb' }))
|
|
||||||
app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }))
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// View accessible data
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
app.locals._ = require('lodash')
|
|
||||||
app.locals.t = global.lang.t.bind(global.lang)
|
|
||||||
app.locals.moment = require('moment')
|
|
||||||
app.locals.moment.locale(appconfig.lang)
|
|
||||||
app.locals.appconfig = appconfig
|
|
||||||
app.use(mw.flash)
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Controllers
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
app.use('/', ctrl.auth)
|
|
||||||
|
|
||||||
app.use('/uploads', mw.auth, ctrl.uploads)
|
|
||||||
app.use('/admin', mw.auth, ctrl.admin)
|
|
||||||
app.use('/', mw.auth, ctrl.pages)
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Error handling
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
app.use(function (req, res, next) {
|
|
||||||
var err = new Error('Not Found')
|
|
||||||
err.status = 404
|
|
||||||
next(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.use(function (err, req, res, next) {
|
|
||||||
res.status(err.status || 500)
|
|
||||||
res.render('error', {
|
|
||||||
message: err.message,
|
|
||||||
error: IS_DEBUG ? err : {}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Start HTTP server
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
global.winston.info('Starting HTTP/WS server on port ' + appconfig.port + '...')
|
|
||||||
|
|
||||||
app.set('port', appconfig.port)
|
|
||||||
var server = http.createServer(app)
|
|
||||||
var io = socketio(server)
|
|
||||||
|
|
||||||
server.listen(appconfig.port)
|
|
||||||
server.on('error', (error) => {
|
|
||||||
if (error.syscall !== 'listen') {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle specific listen errors with friendly messages
|
|
||||||
switch (error.code) {
|
|
||||||
case 'EACCES':
|
|
||||||
global.winston.error('Listening on port ' + appconfig.port + ' requires elevated privileges!')
|
|
||||||
return process.exit(1)
|
|
||||||
case 'EADDRINUSE':
|
|
||||||
global.winston.error('Port ' + appconfig.port + ' is already in use!')
|
|
||||||
return process.exit(1)
|
|
||||||
default:
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
server.on('listening', () => {
|
|
||||||
global.winston.info('HTTP/WS server started successfully! [RUNNING]')
|
|
||||||
})
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// WebSocket
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
io.use(passportSocketIo.authorize({
|
|
||||||
key: 'wikijs.sid',
|
|
||||||
store: sessionStore,
|
|
||||||
secret: appconfig.sessionSecret,
|
|
||||||
cookieParser,
|
|
||||||
success: (data, accept) => {
|
|
||||||
accept()
|
|
||||||
},
|
|
||||||
fail: (data, message, error, accept) => {
|
|
||||||
accept()
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
io.on('connection', ctrl.ws)
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Start child processes
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
let bgAgent = fork(path.join(SERVERPATH, 'agent.js'))
|
|
||||||
|
|
||||||
bgAgent.on('message', m => {
|
|
||||||
if (!m.action) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (m.action) {
|
|
||||||
case 'searchAdd':
|
|
||||||
global.search.add(m.content)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Graceful shutdown
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
graceful.on('exit', () => {
|
|
||||||
global.winston.info('- SHUTTING DOWN - Terminating Background Agent...')
|
|
||||||
bgAgent.kill()
|
|
||||||
global.winston.info('- SHUTTING DOWN - Performing git sync...')
|
|
||||||
return global.git.resync().then(() => {
|
|
||||||
global.winston.info('- SHUTTING DOWN - Git sync successful. Now safe to exit.')
|
|
||||||
process.exit()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const Promise = require('bluebird')
|
|
||||||
const fs = Promise.promisifyAll(require('fs-extra'))
|
|
||||||
const pm2 = Promise.promisifyAll(require('pm2'))
|
|
||||||
const ora = require('ora')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const ROOTPATH = process.cwd()
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/**
|
|
||||||
* Detect the most appropriate start mode
|
|
||||||
*/
|
|
||||||
startDetect: function () {
|
|
||||||
if (process.env.WIKI_JS_HEROKU) {
|
|
||||||
return this.startInHerokuMode()
|
|
||||||
} else {
|
|
||||||
return this.startInBackgroundMode()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Start in background mode
|
|
||||||
*/
|
|
||||||
startInBackgroundMode: function () {
|
|
||||||
let spinner = ora('Initializing...').start()
|
|
||||||
return fs.emptyDirAsync(path.join(ROOTPATH, './logs')).then(() => {
|
|
||||||
return pm2.connectAsync().then(() => {
|
|
||||||
return pm2.startAsync({
|
|
||||||
name: 'wiki',
|
|
||||||
script: 'server',
|
|
||||||
cwd: ROOTPATH,
|
|
||||||
output: path.join(ROOTPATH, './logs/wiki-output.log'),
|
|
||||||
error: path.join(ROOTPATH, './logs/wiki-error.log'),
|
|
||||||
minUptime: 5000,
|
|
||||||
maxRestarts: 5
|
|
||||||
}).then(() => {
|
|
||||||
spinner.succeed('Wiki.js has started successfully.')
|
|
||||||
}).finally(() => {
|
|
||||||
pm2.disconnect()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}).catch(err => {
|
|
||||||
spinner.fail(err)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Start in Heroku mode
|
|
||||||
*/
|
|
||||||
startInHerokuMode: function () {
|
|
||||||
console.warn('Incorrect command on Heroku, use instead: node server')
|
|
||||||
process.exit(1)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Stop Wiki.js process(es)
|
|
||||||
*/
|
|
||||||
stop () {
|
|
||||||
let spinner = ora('Shutting down Wiki.js...').start()
|
|
||||||
return pm2.connectAsync().then(() => {
|
|
||||||
return pm2.stopAsync('wiki').then(() => {
|
|
||||||
spinner.succeed('Wiki.js has stopped successfully.')
|
|
||||||
}).finally(() => {
|
|
||||||
pm2.disconnect()
|
|
||||||
})
|
|
||||||
}).catch(err => {
|
|
||||||
spinner.fail(err)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Restart Wiki.js process(es)
|
|
||||||
*/
|
|
||||||
restart: function () {
|
|
||||||
let self = this
|
|
||||||
return self.stop().delay(1000).then(() => {
|
|
||||||
self.startDetect()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Start the web-based configuration wizard
|
|
||||||
*
|
|
||||||
* @param {Number} port Port to bind the HTTP server on
|
|
||||||
*/
|
|
||||||
configure (port) {
|
|
||||||
port = port || 3000
|
|
||||||
let spinner = ora('Initializing interactive setup...').start()
|
|
||||||
require('./configure')(port, spinner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
/* global appconfig, appdata, db, lang, winston */
|
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
module.exports = function (passport) {
|
|
||||||
// Serialization user methods
|
|
||||||
|
|
||||||
passport.serializeUser(function (user, done) {
|
|
||||||
done(null, user._id)
|
|
||||||
})
|
|
||||||
|
|
||||||
passport.deserializeUser(function (id, done) {
|
|
||||||
db.User.findById(id).then((user) => {
|
|
||||||
if (user) {
|
|
||||||
done(null, user)
|
|
||||||
} else {
|
|
||||||
done(new Error(lang.t('auth:errors:usernotfound')), null)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}).catch((err) => {
|
|
||||||
done(err, null)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Local Account
|
|
||||||
|
|
||||||
if (appconfig.auth.local && appconfig.auth.local.enabled) {
|
|
||||||
const LocalStrategy = require('passport-local').Strategy
|
|
||||||
passport.use('local',
|
|
||||||
new LocalStrategy({
|
|
||||||
usernameField: 'email',
|
|
||||||
passwordField: 'password'
|
|
||||||
}, (uEmail, uPassword, done) => {
|
|
||||||
db.User.findOne({ email: uEmail, provider: 'local' }).then((user) => {
|
|
||||||
if (user) {
|
|
||||||
return user.validatePassword(uPassword).then(() => {
|
|
||||||
return done(null, user) || true
|
|
||||||
}).catch((err) => {
|
|
||||||
return done(err, null)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return done(new Error('INVALID_LOGIN'), null)
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
done(err, null)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Google ID
|
|
||||||
|
|
||||||
if (appconfig.auth.google && appconfig.auth.google.enabled) {
|
|
||||||
const GoogleStrategy = require('passport-google-oauth20').Strategy
|
|
||||||
passport.use('google',
|
|
||||||
new GoogleStrategy({
|
|
||||||
clientID: appconfig.auth.google.clientId,
|
|
||||||
clientSecret: appconfig.auth.google.clientSecret,
|
|
||||||
callbackURL: appconfig.host + '/login/google/callback'
|
|
||||||
}, (accessToken, refreshToken, profile, cb) => {
|
|
||||||
db.User.processProfile(profile).then((user) => {
|
|
||||||
return cb(null, user) || true
|
|
||||||
}).catch((err) => {
|
|
||||||
return cb(err, null) || true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Microsoft Accounts
|
|
||||||
|
|
||||||
if (appconfig.auth.microsoft && appconfig.auth.microsoft.enabled) {
|
|
||||||
const WindowsLiveStrategy = require('passport-windowslive').Strategy
|
|
||||||
passport.use('windowslive',
|
|
||||||
new WindowsLiveStrategy({
|
|
||||||
clientID: appconfig.auth.microsoft.clientId,
|
|
||||||
clientSecret: appconfig.auth.microsoft.clientSecret,
|
|
||||||
callbackURL: appconfig.host + '/login/ms/callback'
|
|
||||||
}, function (accessToken, refreshToken, profile, cb) {
|
|
||||||
db.User.processProfile(profile).then((user) => {
|
|
||||||
return cb(null, user) || true
|
|
||||||
}).catch((err) => {
|
|
||||||
return cb(err, null) || true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Facebook
|
|
||||||
|
|
||||||
if (appconfig.auth.facebook && appconfig.auth.facebook.enabled) {
|
|
||||||
const FacebookStrategy = require('passport-facebook').Strategy
|
|
||||||
passport.use('facebook',
|
|
||||||
new FacebookStrategy({
|
|
||||||
clientID: appconfig.auth.facebook.clientId,
|
|
||||||
clientSecret: appconfig.auth.facebook.clientSecret,
|
|
||||||
callbackURL: appconfig.host + '/login/facebook/callback',
|
|
||||||
profileFields: ['id', 'displayName', 'email']
|
|
||||||
}, function (accessToken, refreshToken, profile, cb) {
|
|
||||||
db.User.processProfile(profile).then((user) => {
|
|
||||||
return cb(null, user) || true
|
|
||||||
}).catch((err) => {
|
|
||||||
return cb(err, null) || true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GitHub
|
|
||||||
|
|
||||||
if (appconfig.auth.github && appconfig.auth.github.enabled) {
|
|
||||||
const GitHubStrategy = require('passport-github2').Strategy
|
|
||||||
passport.use('github',
|
|
||||||
new GitHubStrategy({
|
|
||||||
clientID: appconfig.auth.github.clientId,
|
|
||||||
clientSecret: appconfig.auth.github.clientSecret,
|
|
||||||
callbackURL: appconfig.host + '/login/github/callback',
|
|
||||||
scope: ['user:email']
|
|
||||||
}, (accessToken, refreshToken, profile, cb) => {
|
|
||||||
db.User.processProfile(profile).then((user) => {
|
|
||||||
return cb(null, user) || true
|
|
||||||
}).catch((err) => {
|
|
||||||
return cb(err, null) || true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slack
|
|
||||||
|
|
||||||
if (appconfig.auth.slack && appconfig.auth.slack.enabled) {
|
|
||||||
const SlackStrategy = require('passport-slack').Strategy
|
|
||||||
passport.use('slack',
|
|
||||||
new SlackStrategy({
|
|
||||||
clientID: appconfig.auth.slack.clientId,
|
|
||||||
clientSecret: appconfig.auth.slack.clientSecret,
|
|
||||||
callbackURL: appconfig.host + '/login/slack/callback'
|
|
||||||
}, (accessToken, refreshToken, profile, cb) => {
|
|
||||||
db.User.processProfile(profile).then((user) => {
|
|
||||||
return cb(null, user) || true
|
|
||||||
}).catch((err) => {
|
|
||||||
return cb(err, null) || true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LDAP
|
|
||||||
|
|
||||||
if (appconfig.auth.ldap && appconfig.auth.ldap.enabled) {
|
|
||||||
const LdapStrategy = require('passport-ldapauth').Strategy
|
|
||||||
passport.use('ldapauth',
|
|
||||||
new LdapStrategy({
|
|
||||||
server: {
|
|
||||||
url: appconfig.auth.ldap.url,
|
|
||||||
bindDn: appconfig.auth.ldap.bindDn,
|
|
||||||
bindCredentials: appconfig.auth.ldap.bindCredentials,
|
|
||||||
searchBase: appconfig.auth.ldap.searchBase,
|
|
||||||
searchFilter: appconfig.auth.ldap.searchFilter,
|
|
||||||
searchAttributes: ['displayName', 'name', 'cn', 'mail'],
|
|
||||||
tlsOptions: (appconfig.auth.ldap.tlsEnabled) ? {
|
|
||||||
ca: [
|
|
||||||
fs.readFileSync(appconfig.auth.ldap.tlsCertPath)
|
|
||||||
]
|
|
||||||
} : {}
|
|
||||||
},
|
|
||||||
usernameField: 'email',
|
|
||||||
passReqToCallback: false
|
|
||||||
}, (profile, cb) => {
|
|
||||||
profile.provider = 'ldap'
|
|
||||||
profile.id = profile.dn
|
|
||||||
db.User.processProfile(profile).then((user) => {
|
|
||||||
return cb(null, user) || true
|
|
||||||
}).catch((err) => {
|
|
||||||
return cb(err, null) || true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AZURE AD
|
|
||||||
|
|
||||||
if (appconfig.auth.azure && appconfig.auth.azure.enabled) {
|
|
||||||
const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy
|
|
||||||
const jwt = require('jsonwebtoken')
|
|
||||||
passport.use('azure_ad_oauth2',
|
|
||||||
new AzureAdOAuth2Strategy({
|
|
||||||
clientID: appconfig.auth.azure.clientId,
|
|
||||||
clientSecret: appconfig.auth.azure.clientSecret,
|
|
||||||
callbackURL: appconfig.host + '/login/azure/callback',
|
|
||||||
resource: appconfig.auth.azure.resource,
|
|
||||||
tenant: appconfig.auth.azure.tenant
|
|
||||||
}, (accessToken, refreshToken, params, profile, cb) => {
|
|
||||||
let waadProfile = jwt.decode(params.id_token)
|
|
||||||
waadProfile.id = waadProfile.oid
|
|
||||||
waadProfile.provider = 'azure'
|
|
||||||
db.User.processProfile(waadProfile).then((user) => {
|
|
||||||
return cb(null, user) || true
|
|
||||||
}).catch((err) => {
|
|
||||||
return cb(err, null) || true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create users for first-time
|
|
||||||
|
|
||||||
db.onReady.then(() => {
|
|
||||||
return db.User.findOne({ provider: 'local', email: 'guest' }).then((c) => {
|
|
||||||
if (c < 1) {
|
|
||||||
// Create guest account
|
|
||||||
|
|
||||||
return db.User.create({
|
|
||||||
provider: 'local',
|
|
||||||
email: 'guest',
|
|
||||||
name: 'Guest',
|
|
||||||
password: '',
|
|
||||||
rights: [{
|
|
||||||
role: 'read',
|
|
||||||
path: '/',
|
|
||||||
exact: false,
|
|
||||||
deny: !appconfig.public
|
|
||||||
}]
|
|
||||||
}).then(() => {
|
|
||||||
winston.info('[AUTH] Guest account created successfully!')
|
|
||||||
}).catch((err) => {
|
|
||||||
winston.error('[AUTH] An error occured while creating guest account:')
|
|
||||||
winston.error(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).then(() => {
|
|
||||||
if (process.env.WIKI_JS_HEROKU) {
|
|
||||||
return db.User.findOne({ provider: 'local', email: process.env.WIKI_ADMIN_EMAIL }).then((c) => {
|
|
||||||
if (c < 1) {
|
|
||||||
// Create root admin account (HEROKU ONLY)
|
|
||||||
|
|
||||||
return db.User.create({
|
|
||||||
provider: 'local',
|
|
||||||
email: process.env.WIKI_ADMIN_EMAIL,
|
|
||||||
name: 'Administrator',
|
|
||||||
password: '$2a$04$MAHRw785Xe/Jd5kcKzr3D.VRZDeomFZu2lius4gGpZZ9cJw7B7Mna', // admin123 (default)
|
|
||||||
rights: [{
|
|
||||||
role: 'admin',
|
|
||||||
path: '/',
|
|
||||||
exact: false,
|
|
||||||
deny: false
|
|
||||||
}]
|
|
||||||
}).then(() => {
|
|
||||||
winston.info('[AUTH] Root admin account created successfully!')
|
|
||||||
}).catch((err) => {
|
|
||||||
winston.error('[AUTH] An error occured while creating root admin account:')
|
|
||||||
winston.error(err)
|
|
||||||
})
|
|
||||||
} else { return true }
|
|
||||||
})
|
|
||||||
} else { return true }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
const yaml = require('js-yaml')
|
|
||||||
const _ = require('lodash')
|
|
||||||
const path = require('path')
|
|
||||||
const cfgHelper = require('../helpers/config')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load Application Configuration
|
|
||||||
*
|
|
||||||
* @param {Object} confPaths Path to the configuration files
|
|
||||||
* @return {Object} Application Configuration
|
|
||||||
*/
|
|
||||||
module.exports = (confPaths) => {
|
|
||||||
confPaths = _.defaults(confPaths, {
|
|
||||||
config: path.join(ROOTPATH, 'config.yml'),
|
|
||||||
data: path.join(SERVERPATH, 'app/data.yml'),
|
|
||||||
dataRegex: path.join(SERVERPATH, 'app/regex.js')
|
|
||||||
})
|
|
||||||
|
|
||||||
let appconfig = {}
|
|
||||||
let appdata = {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
appconfig = yaml.safeLoad(
|
|
||||||
cfgHelper.parseConfigValue(
|
|
||||||
fs.readFileSync(confPaths.config, 'utf8')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
appdata = yaml.safeLoad(fs.readFileSync(confPaths.data, 'utf8'))
|
|
||||||
appdata.regex = require(confPaths.dataRegex)
|
|
||||||
} catch (ex) {
|
|
||||||
console.error(ex)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge with defaults
|
|
||||||
|
|
||||||
appconfig = _.defaultsDeep(appconfig, appdata.defaults.config)
|
|
||||||
|
|
||||||
// Check port
|
|
||||||
|
|
||||||
if (appconfig.port < 1) {
|
|
||||||
appconfig.port = process.env.PORT || 80
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert booleans
|
|
||||||
|
|
||||||
appconfig.public = (appconfig.public === true || _.toLower(appconfig.public) === 'true')
|
|
||||||
|
|
||||||
// List authentication strategies
|
|
||||||
|
|
||||||
appconfig.authStrategies = {
|
|
||||||
list: _.filter(appconfig.auth, ['enabled', true]),
|
|
||||||
socialEnabled: (_.chain(appconfig.auth).omit(['local', 'ldap']).filter(['enabled', true]).value().length > 0)
|
|
||||||
}
|
|
||||||
if (appconfig.authStrategies.list.length < 1) {
|
|
||||||
console.error(new Error('You must enable at least 1 authentication strategy!'))
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
config: appconfig,
|
|
||||||
data: appdata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
/* global ROOTPATH, appconfig, winston */
|
|
||||||
|
|
||||||
const modb = require('mongoose')
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const _ = require('lodash')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MongoDB module
|
|
||||||
*
|
|
||||||
* @return {Object} MongoDB wrapper instance
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize DB
|
|
||||||
*
|
|
||||||
* @return {Object} DB instance
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
let self = this
|
|
||||||
|
|
||||||
let dbModelsPath = path.join(SERVERPATH, 'models')
|
|
||||||
|
|
||||||
modb.Promise = require('bluebird')
|
|
||||||
|
|
||||||
// Event handlers
|
|
||||||
|
|
||||||
modb.connection.on('error', err => {
|
|
||||||
winston.error('Failed to connect to MongoDB instance.')
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
modb.connection.once('open', function () {
|
|
||||||
winston.log('Connected to MongoDB instance.')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Store connection handle
|
|
||||||
|
|
||||||
self.connection = modb.connection
|
|
||||||
self.ObjectId = modb.Types.ObjectId
|
|
||||||
|
|
||||||
// Load DB Models
|
|
||||||
|
|
||||||
fs
|
|
||||||
.readdirSync(dbModelsPath)
|
|
||||||
.filter(function (file) {
|
|
||||||
return (file.indexOf('.') !== 0)
|
|
||||||
})
|
|
||||||
.forEach(function (file) {
|
|
||||||
let modelName = _.upperFirst(_.camelCase(_.split(file, '.')[0]))
|
|
||||||
self[modelName] = require(path.join(dbModelsPath, file))
|
|
||||||
})
|
|
||||||
|
|
||||||
// Connect
|
|
||||||
|
|
||||||
self.onReady = modb.connect(appconfig.db, { useMongoClient: true })
|
|
||||||
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
module.exports = (isDebug, processName) => {
|
|
||||||
let winston = require('winston')
|
|
||||||
|
|
||||||
if (typeof processName === 'undefined') {
|
|
||||||
processName = 'SERVER'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Console
|
|
||||||
|
|
||||||
let logger = new (winston.Logger)({
|
|
||||||
level: (isDebug) ? 'debug' : 'info',
|
|
||||||
transports: [
|
|
||||||
new (winston.transports.Console)({
|
|
||||||
level: (isDebug) ? 'debug' : 'info',
|
|
||||||
prettyPrint: true,
|
|
||||||
colorize: true,
|
|
||||||
silent: false,
|
|
||||||
timestamp: true
|
|
||||||
})
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
logger.filters.push((level, msg) => {
|
|
||||||
return '[' + processName + '] ' + msg
|
|
||||||
})
|
|
||||||
|
|
||||||
// External services
|
|
||||||
|
|
||||||
if (appconfig.externalLogging.bugsnag) {
|
|
||||||
const bugsnagTransport = require('./winston-transports/bugsnag')
|
|
||||||
logger.add(bugsnagTransport, {
|
|
||||||
level: 'warn',
|
|
||||||
key: appconfig.externalLogging.bugsnag
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appconfig.externalLogging.loggly) {
|
|
||||||
require('winston-loggly-bulk')
|
|
||||||
logger.add(winston.transports.Loggly, {
|
|
||||||
token: appconfig.externalLogging.loggly.token,
|
|
||||||
subdomain: appconfig.externalLogging.loggly.subdomain,
|
|
||||||
tags: ['wiki-js'],
|
|
||||||
level: 'warn',
|
|
||||||
json: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appconfig.externalLogging.papertrail) {
|
|
||||||
require('winston-papertrail').Papertrail // eslint-disable-line no-unused-expressions
|
|
||||||
logger.add(winston.transports.Papertrail, {
|
|
||||||
host: appconfig.externalLogging.papertrail.host,
|
|
||||||
port: appconfig.externalLogging.papertrail.port,
|
|
||||||
level: 'warn',
|
|
||||||
program: 'wiki.js'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appconfig.externalLogging.rollbar) {
|
|
||||||
const rollbarTransport = require('./winston-transports/rollbar')
|
|
||||||
logger.add(rollbarTransport, {
|
|
||||||
level: 'warn',
|
|
||||||
key: appconfig.externalLogging.rollbar
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appconfig.externalLogging.sentry) {
|
|
||||||
const sentryTransport = require('./winston-transports/sentry')
|
|
||||||
logger.add(sentryTransport, {
|
|
||||||
level: 'warn',
|
|
||||||
key: appconfig.externalLogging.sentry
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return logger
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
const bunyan = require('bunyan')
|
|
||||||
const level = require('levelup')
|
|
||||||
const down = require('memdown')
|
|
||||||
const SearchIndexAdder = require('search-index-adder')
|
|
||||||
const SearchIndexSearcher = require('search-index-searcher')
|
|
||||||
|
|
||||||
module.exports = function (givenOptions, moduleReady) {
|
|
||||||
const optionsLoaded = function (err, SearchIndex) {
|
|
||||||
const siUtil = require('./siUtil.js')(SearchIndex.options)
|
|
||||||
if (err) return moduleReady(err)
|
|
||||||
SearchIndex.close = siUtil.close
|
|
||||||
SearchIndex.countDocs = siUtil.countDocs
|
|
||||||
getAdder(SearchIndex, adderLoaded)
|
|
||||||
}
|
|
||||||
|
|
||||||
const adderLoaded = function (err, SearchIndex) {
|
|
||||||
if (err) return moduleReady(err)
|
|
||||||
getSearcher(SearchIndex, searcherLoaded)
|
|
||||||
}
|
|
||||||
|
|
||||||
const searcherLoaded = function (err, SearchIndex) {
|
|
||||||
if (err) return moduleReady(err)
|
|
||||||
return moduleReady(err, SearchIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
getOptions(givenOptions, optionsLoaded)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAdder = function (SearchIndex, done) {
|
|
||||||
SearchIndexAdder(SearchIndex.options, function (err, searchIndexAdder) {
|
|
||||||
SearchIndex.add = searchIndexAdder.add
|
|
||||||
SearchIndex.callbackyAdd = searchIndexAdder.concurrentAdd // deprecated
|
|
||||||
SearchIndex.concurrentAdd = searchIndexAdder.concurrentAdd
|
|
||||||
SearchIndex.createWriteStream = searchIndexAdder.createWriteStream
|
|
||||||
SearchIndex.dbWriteStream = searchIndexAdder.dbWriteStream
|
|
||||||
SearchIndex.defaultPipeline = searchIndexAdder.defaultPipeline
|
|
||||||
SearchIndex.del = searchIndexAdder.deleter
|
|
||||||
SearchIndex.deleteStream = searchIndexAdder.deleteStream
|
|
||||||
SearchIndex.flush = searchIndexAdder.flush
|
|
||||||
done(err, SearchIndex)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSearcher = function (SearchIndex, done) {
|
|
||||||
SearchIndexSearcher(SearchIndex.options, function (err, searchIndexSearcher) {
|
|
||||||
SearchIndex.availableFields = searchIndexSearcher.availableFields
|
|
||||||
SearchIndex.buckets = searchIndexSearcher.bucketStream
|
|
||||||
SearchIndex.categorize = searchIndexSearcher.categoryStream
|
|
||||||
SearchIndex.dbReadStream = searchIndexSearcher.dbReadStream
|
|
||||||
SearchIndex.get = searchIndexSearcher.get
|
|
||||||
SearchIndex.match = searchIndexSearcher.match
|
|
||||||
SearchIndex.scan = searchIndexSearcher.scan
|
|
||||||
SearchIndex.search = searchIndexSearcher.search
|
|
||||||
SearchIndex.totalHits = searchIndexSearcher.totalHits
|
|
||||||
done(err, SearchIndex)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOptions = function (options, done) {
|
|
||||||
var SearchIndex = {}
|
|
||||||
SearchIndex.options = Object.assign({}, {
|
|
||||||
indexPath: 'si',
|
|
||||||
keySeparator: '○',
|
|
||||||
logLevel: 'error'
|
|
||||||
}, options)
|
|
||||||
options.log = bunyan.createLogger({
|
|
||||||
name: 'search-index',
|
|
||||||
level: options.logLevel
|
|
||||||
})
|
|
||||||
if (!options.indexes) {
|
|
||||||
level(SearchIndex.options.indexPath || 'si', {
|
|
||||||
valueEncoding: 'json',
|
|
||||||
db: down
|
|
||||||
}, function (err, db) {
|
|
||||||
SearchIndex.options.indexes = db
|
|
||||||
return done(err, SearchIndex)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return done(null, SearchIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
module.exports = function (siOptions) {
|
|
||||||
var siUtil = {}
|
|
||||||
|
|
||||||
siUtil.countDocs = function (callback) {
|
|
||||||
var count = 0
|
|
||||||
const gte = 'DOCUMENT' + siOptions.keySeparator
|
|
||||||
const lte = 'DOCUMENT' + siOptions.keySeparator + siOptions.keySeparator
|
|
||||||
siOptions.indexes.createReadStream({gte: gte, lte: lte})
|
|
||||||
.on('data', function (data) {
|
|
||||||
count++
|
|
||||||
})
|
|
||||||
.on('error', function (err) {
|
|
||||||
return callback(err, null)
|
|
||||||
})
|
|
||||||
.on('end', function () {
|
|
||||||
return callback(null, count)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
siUtil.close = function (callback) {
|
|
||||||
siOptions.indexes.close(function (err) {
|
|
||||||
while (!siOptions.indexes.isClosed()) {
|
|
||||||
// log not always working here- investigate
|
|
||||||
if (siOptions.log) siOptions.log.info('closing...')
|
|
||||||
}
|
|
||||||
if (siOptions.indexes.isClosed()) {
|
|
||||||
if (siOptions.log) siOptions.log.info('closed...')
|
|
||||||
callback(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return siUtil
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
"invalidlogin": "不正なログイン",
|
"invalidlogin": "不正なログイン",
|
||||||
"invalidloginmsg": "Eメール又はパスワードが無効です。",
|
"invalidloginmsg": "Eメール又はパスワードが無効です。",
|
||||||
"invaliduseremail": "無効なユーザーEメール",
|
"invaliduseremail": "無効なユーザーEメール",
|
||||||
"lognerror": "ログインエラー",
|
"loginerror": "ログインエラー",
|
||||||
"notyetauthorized": "まだこのサイトにログインする権限がありません。",
|
"notyetauthorized": "まだこのサイトにログインする権限がありません。",
|
||||||
"toomanyattempts": "試行回数が多すぎます",
|
"toomanyattempts": "試行回数が多すぎます",
|
||||||
"toomanyattemptsmsg": "短期間に失敗した試行回数が多すぎます。{{time}}にもう一度お試しください。",
|
"toomanyattemptsmsg": "短期間に失敗した試行回数が多すぎます。{{time}}にもう一度お試しください。",
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
// ----------------------------------------
|
||||||
|
// Load global modules
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
wiki.auth = require('./modules/auth').init()
|
||||||
|
wiki.disk = require('./modules/disk').init()
|
||||||
|
wiki.docs = require('./modules/documents').init()
|
||||||
|
wiki.git = require('./modules/git').init(false)
|
||||||
|
wiki.lang = require('./modules/localization').init()
|
||||||
|
wiki.mark = require('./modules/markdown')
|
||||||
|
wiki.search = require('./modules/search').init()
|
||||||
|
wiki.upl = require('./modules/uploads').init()
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Load modules
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
const autoload = require('auto-load')
|
||||||
|
const bodyParser = require('body-parser')
|
||||||
|
const compression = require('compression')
|
||||||
|
const cookieParser = require('cookie-parser')
|
||||||
|
const express = require('express')
|
||||||
|
const favicon = require('serve-favicon')
|
||||||
|
const flash = require('connect-flash')
|
||||||
|
const http = require('http')
|
||||||
|
const path = require('path')
|
||||||
|
const session = require('express-session')
|
||||||
|
const SessionRedisStore = require('connect-redis')(session)
|
||||||
|
const graceful = require('node-graceful')
|
||||||
|
const graphqlApollo = require('apollo-server-express')
|
||||||
|
const graphqlSchema = require('./modules/graphql')
|
||||||
|
|
||||||
|
var mw = autoload(path.join(wiki.SERVERPATH, '/middlewares'))
|
||||||
|
var ctrl = autoload(path.join(wiki.SERVERPATH, '/controllers'))
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Define Express App
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
wiki.app = app
|
||||||
|
app.use(compression())
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Security
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
app.use(mw.security)
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Public Assets
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
app.use(favicon(path.join(wiki.ROOTPATH, 'assets', 'favicon.ico')))
|
||||||
|
app.use(express.static(path.join(wiki.ROOTPATH, 'assets'), {
|
||||||
|
index: false,
|
||||||
|
maxAge: '7d'
|
||||||
|
}))
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Passport Authentication
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
let sessionStore = new SessionRedisStore({
|
||||||
|
client: wiki.redis
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use(cookieParser())
|
||||||
|
app.use(session({
|
||||||
|
name: 'wikijs.sid',
|
||||||
|
store: sessionStore,
|
||||||
|
secret: wiki.config.site.sessionSecret,
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: false
|
||||||
|
}))
|
||||||
|
app.use(flash())
|
||||||
|
app.use(wiki.auth.passport.initialize())
|
||||||
|
app.use(wiki.auth.passport.session())
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// SEO
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
app.use(mw.seo)
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// View Engine Setup
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
app.set('views', path.join(wiki.SERVERPATH, 'views'))
|
||||||
|
app.set('view engine', 'pug')
|
||||||
|
|
||||||
|
app.use(bodyParser.json({ limit: '1mb' }))
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }))
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// View accessible data
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
app.locals.basedir = wiki.ROOTPATH
|
||||||
|
app.locals._ = require('lodash')
|
||||||
|
app.locals.t = wiki.lang.engine.t.bind(wiki.lang)
|
||||||
|
app.locals.moment = require('moment')
|
||||||
|
app.locals.moment.locale(wiki.config.site.lang)
|
||||||
|
app.locals.config = wiki.config
|
||||||
|
app.use(mw.flash)
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Controllers
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
app.use('/', ctrl.auth)
|
||||||
|
|
||||||
|
app.use('/graphql', graphqlApollo.graphqlExpress({ schema: graphqlSchema }))
|
||||||
|
app.use('/graphiql', graphqlApollo.graphiqlExpress({ endpointURL: '/graphql' }))
|
||||||
|
// app.use('/uploads', mw.auth, ctrl.uploads)
|
||||||
|
app.use('/admin', mw.auth, ctrl.admin)
|
||||||
|
app.use('/', mw.auth, ctrl.pages)
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Error handling
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
app.use(function (req, res, next) {
|
||||||
|
var err = new Error('Not Found')
|
||||||
|
err.status = 404
|
||||||
|
next(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use(function (err, req, res, next) {
|
||||||
|
res.status(err.status || 500)
|
||||||
|
res.render('error', {
|
||||||
|
message: err.message,
|
||||||
|
error: wiki.IS_DEBUG ? err : {}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Start HTTP server
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
wiki.logger.info(`HTTP Server on port: ${wiki.config.port}`)
|
||||||
|
|
||||||
|
app.set('port', wiki.config.port)
|
||||||
|
let server = http.createServer(app)
|
||||||
|
|
||||||
|
server.listen(wiki.config.port)
|
||||||
|
server.on('error', (error) => {
|
||||||
|
if (error.syscall !== 'listen') {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle specific listen errors with friendly messages
|
||||||
|
switch (error.code) {
|
||||||
|
case 'EACCES':
|
||||||
|
wiki.logger.error('Listening on port ' + wiki.config.port + ' requires elevated privileges!')
|
||||||
|
return process.exit(1)
|
||||||
|
case 'EADDRINUSE':
|
||||||
|
wiki.logger.error('Port ' + wiki.config.port + ' is already in use!')
|
||||||
|
return process.exit(1)
|
||||||
|
default:
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
server.on('listening', () => {
|
||||||
|
wiki.logger.info('HTTP Server: RUNNING')
|
||||||
|
})
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Graceful shutdown
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
graceful.on('exit', () => {
|
||||||
|
wiki.logger.info('- SHUTTING DOWN - Performing git sync...')
|
||||||
|
return global.git.resync().then(() => {
|
||||||
|
wiki.logger.info('- SHUTTING DOWN - Git sync successful. Now safe to exit.')
|
||||||
|
process.exit()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
* @return {any} void
|
* @return {any} void
|
||||||
*/
|
*/
|
||||||
module.exports = (req, res, next) => {
|
module.exports = (req, res, next) => {
|
||||||
res.locals.appflash = req.flash('alert')
|
res.locals.flash = req.flash('alert')
|
||||||
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global app */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Security Middleware
|
* Security Middleware
|
||||||
*
|
*
|
||||||
@@ -12,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
module.exports = function (req, res, next) {
|
module.exports = function (req, res, next) {
|
||||||
// -> Disable X-Powered-By
|
// -> Disable X-Powered-By
|
||||||
app.disable('x-powered-by')
|
req.app.disable('x-powered-by')
|
||||||
|
|
||||||
// -> Disable Frame Embedding
|
// -> Disable Frame Embedding
|
||||||
res.set('X-Frame-Options', 'deny')
|
res.set('X-Frame-Options', 'deny')
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Associate DB Model relations
|
||||||
|
*/
|
||||||
|
module.exports = db => {
|
||||||
|
db.User.belongsToMany(db.Group, { through: 'userGroups' })
|
||||||
|
db.Group.belongsToMany(db.User, { through: 'userGroups' })
|
||||||
|
db.Group.hasMany(db.Right)
|
||||||
|
db.Right.belongsTo(db.Group)
|
||||||
|
db.Document.belongsToMany(db.Tag, { through: 'documentTags' })
|
||||||
|
db.Document.hasMany(db.Comment)
|
||||||
|
db.Tag.belongsToMany(db.Document, { through: 'documentTags' })
|
||||||
|
db.File.belongsTo(db.Folder)
|
||||||
|
db.Folder.hasMany(db.File)
|
||||||
|
db.Comment.belongsTo(db.Document)
|
||||||
|
db.Comment.belongsTo(db.User, { as: 'author' })
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const Mongoose = require('mongoose')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BruteForce schema
|
|
||||||
*
|
|
||||||
* @type {<Mongoose.Schema>}
|
|
||||||
*/
|
|
||||||
var bruteForceSchema = Mongoose.Schema({
|
|
||||||
_id: { type: String, index: 1 },
|
|
||||||
data: {
|
|
||||||
count: Number,
|
|
||||||
lastRequest: Date,
|
|
||||||
firstRequest: Date
|
|
||||||
},
|
|
||||||
expires: { type: Date, index: { expires: '1d' } }
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = Mongoose.model('Bruteforce', bruteForceSchema)
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Comment schema
|
||||||
|
*/
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let commentSchema = sequelize.define('comment', {
|
||||||
|
content: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true
|
||||||
|
})
|
||||||
|
|
||||||
|
return commentSchema
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Document schema
|
||||||
|
*/
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let documentSchema = sequelize.define('setting', {
|
||||||
|
path: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
validate: {
|
||||||
|
len: [2, 255]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
|
parentPath: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
|
parentTitle: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
|
isDirectory: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
isEntry: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
isDraft: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
searchContent: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: ''
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true,
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
fields: ['path']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
return documentSchema
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const Mongoose = require('mongoose')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entry schema
|
|
||||||
*
|
|
||||||
* @type {<Mongoose.Schema>}
|
|
||||||
*/
|
|
||||||
var entrySchema = Mongoose.Schema({
|
|
||||||
_id: String,
|
|
||||||
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
minlength: 2
|
|
||||||
},
|
|
||||||
subtitle: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
parentTitle: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
parentPath: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
isDirectory: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
isEntry: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
timestamps: {}
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = Mongoose.model('Entry', entrySchema)
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* File schema
|
||||||
|
*/
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let fileSchema = sequelize.define('file', {
|
||||||
|
category: {
|
||||||
|
type: DataTypes.ENUM('binary', 'image'),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 'binary'
|
||||||
|
},
|
||||||
|
mime: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 'application/octet-stream'
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
type: DataTypes.JSONB,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
filename: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
basename: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
filesize: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
validate: {
|
||||||
|
isInt: true,
|
||||||
|
min: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true
|
||||||
|
})
|
||||||
|
|
||||||
|
return fileSchema
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Folder schema
|
||||||
|
*/
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let folderSchema = sequelize.define('folder', {
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true,
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
fields: ['name']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
return folderSchema
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Group schema
|
||||||
|
*/
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let groupSchema = sequelize.define('group', {
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true
|
||||||
|
})
|
||||||
|
|
||||||
|
return groupSchema
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Right schema
|
||||||
|
*/
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let rightSchema = sequelize.define('right', {
|
||||||
|
path: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: DataTypes.ENUM('read', 'write', 'manage'),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 'read'
|
||||||
|
},
|
||||||
|
exact: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
allow: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true,
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
fields: ['path']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
return rightSchema
|
||||||
|
}
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const Mongoose = require('mongoose')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings schema
|
* Settings schema
|
||||||
*
|
|
||||||
* @type {<Mongoose.Schema>}
|
|
||||||
*/
|
*/
|
||||||
var settingSchema = Mongoose.Schema({
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let settingSchema = sequelize.define('setting', {
|
||||||
key: {
|
key: {
|
||||||
type: String,
|
type: DataTypes.STRING,
|
||||||
required: true,
|
allowNull: false
|
||||||
index: true
|
|
||||||
},
|
},
|
||||||
value: {
|
config: {
|
||||||
type: String,
|
type: DataTypes.JSONB,
|
||||||
required: true
|
allowNull: false
|
||||||
}
|
}
|
||||||
}, { timestamps: {} })
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true,
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
fields: ['key']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
module.exports = Mongoose.model('Setting', settingSchema)
|
return settingSchema
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Tags schema
|
||||||
|
*/
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let tagSchema = sequelize.define('tag', {
|
||||||
|
key: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true,
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
fields: ['key']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
return tagSchema
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const Mongoose = require('mongoose')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload File schema
|
|
||||||
*
|
|
||||||
* @type {<Mongoose.Schema>}
|
|
||||||
*/
|
|
||||||
var uplFileSchema = Mongoose.Schema({
|
|
||||||
|
|
||||||
_id: String,
|
|
||||||
|
|
||||||
category: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: 'binary'
|
|
||||||
},
|
|
||||||
mime: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
default: 'application/octet-stream'
|
|
||||||
},
|
|
||||||
extra: {
|
|
||||||
type: Object
|
|
||||||
},
|
|
||||||
folder: {
|
|
||||||
type: String,
|
|
||||||
ref: 'UplFolder'
|
|
||||||
},
|
|
||||||
filename: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
basename: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
filesize: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}, { timestamps: {} })
|
|
||||||
|
|
||||||
module.exports = Mongoose.model('UplFile', uplFileSchema)
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const Mongoose = require('mongoose')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload Folder schema
|
|
||||||
*
|
|
||||||
* @type {<Mongoose.Schema>}
|
|
||||||
*/
|
|
||||||
var uplFolderSchema = Mongoose.Schema({
|
|
||||||
|
|
||||||
_id: String,
|
|
||||||
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}
|
|
||||||
|
|
||||||
}, { timestamps: {} })
|
|
||||||
|
|
||||||
module.exports = Mongoose.model('UplFolder', uplFolderSchema)
|
|
||||||
@@ -1,52 +1,68 @@
|
|||||||
'use strict'
|
/* global wiki */
|
||||||
|
|
||||||
/* global db, lang */
|
|
||||||
|
|
||||||
const Mongoose = require('mongoose')
|
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
const bcrypt = require('bcryptjs-then')
|
const bcrypt = require('bcryptjs-then')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Users schema
|
* Users schema
|
||||||
*
|
|
||||||
* @type {<Mongoose.Schema>}
|
|
||||||
*/
|
*/
|
||||||
var userSchema = Mongoose.Schema({
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let userSchema = sequelize.define('user', {
|
||||||
email: {
|
email: {
|
||||||
type: String,
|
type: DataTypes.STRING,
|
||||||
required: true,
|
allowNull: false,
|
||||||
index: true
|
validate: {
|
||||||
|
isEmail: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
provider: {
|
provider: {
|
||||||
type: String,
|
type: DataTypes.STRING,
|
||||||
required: true
|
allowNull: false
|
||||||
},
|
},
|
||||||
|
|
||||||
providerId: {
|
providerId: {
|
||||||
type: String
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
},
|
},
|
||||||
|
|
||||||
password: {
|
password: {
|
||||||
type: String
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
},
|
},
|
||||||
|
|
||||||
name: {
|
name: {
|
||||||
type: String
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
},
|
},
|
||||||
|
role: {
|
||||||
|
type: DataTypes.ENUM('admin', 'user', 'guest'),
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
tfaIsActive: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
tfaSecret: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true,
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
fields: ['provider', 'email']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
rights: [{
|
userSchema.prototype.validatePassword = function (rawPwd) {
|
||||||
role: String,
|
return bcrypt.compare(rawPwd, this.password).then((isValid) => {
|
||||||
path: String,
|
return (isValid) ? true : Promise.reject(new Error(wiki.lang.t('auth:errors:invalidlogin')))
|
||||||
exact: Boolean,
|
})
|
||||||
deny: Boolean
|
}
|
||||||
}]
|
|
||||||
|
|
||||||
}, { timestamps: {} })
|
userSchema.processProfile = (profile) => {
|
||||||
|
|
||||||
userSchema.statics.processProfile = (profile) => {
|
|
||||||
let primaryEmail = ''
|
let primaryEmail = ''
|
||||||
if (_.isArray(profile.emails)) {
|
if (_.isArray(profile.emails)) {
|
||||||
let e = _.find(profile.emails, ['primary', true])
|
let e = _.find(profile.emails, ['primary', true])
|
||||||
@@ -58,20 +74,20 @@ userSchema.statics.processProfile = (profile) => {
|
|||||||
} else if (profile.user && profile.user.email && profile.user.email.length > 5) {
|
} else if (profile.user && profile.user.email && profile.user.email.length > 5) {
|
||||||
primaryEmail = profile.user.email
|
primaryEmail = profile.user.email
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new Error(lang.t('auth:errors.invaliduseremail')))
|
return Promise.reject(new Error(wiki.lang.t('auth:errors.invaliduseremail')))
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.provider = _.lowerCase(profile.provider)
|
profile.provider = _.lowerCase(profile.provider)
|
||||||
primaryEmail = _.toLower(primaryEmail)
|
primaryEmail = _.toLower(primaryEmail)
|
||||||
|
|
||||||
return db.User.findOneAndUpdate({
|
return wiki.db.User.findOneAndUpdate({
|
||||||
email: primaryEmail,
|
email: primaryEmail,
|
||||||
provider: profile.provider
|
provider: profile.provider
|
||||||
}, {
|
}, {
|
||||||
email: primaryEmail,
|
email: primaryEmail,
|
||||||
provider: profile.provider,
|
provider: profile.provider,
|
||||||
providerId: profile.id,
|
providerId: profile.id,
|
||||||
name: profile.displayName || profile.cn || _.split(primaryEmail, '@')[0]
|
name: profile.displayName || _.split(primaryEmail, '@')[0]
|
||||||
}, {
|
}, {
|
||||||
new: true
|
new: true
|
||||||
}).then((user) => {
|
}).then((user) => {
|
||||||
@@ -90,20 +106,15 @@ userSchema.statics.processProfile = (profile) => {
|
|||||||
deny: false
|
deny: false
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
return db.User.create(nUsr)
|
return wiki.db.User.create(nUsr)
|
||||||
}
|
}
|
||||||
return user || Promise.reject(new Error(lang.t('auth:errors:notyetauthorized')))
|
return user || Promise.reject(new Error(wiki.lang.t('auth:errors:notyetauthorized')))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
userSchema.statics.hashPassword = (rawPwd) => {
|
userSchema.hashPassword = (rawPwd) => {
|
||||||
return bcrypt.hash(rawPwd)
|
return bcrypt.hash(rawPwd)
|
||||||
}
|
}
|
||||||
|
|
||||||
userSchema.methods.validatePassword = function (rawPwd) {
|
return userSchema
|
||||||
return bcrypt.compare(rawPwd, this.password).then((isValid) => {
|
|
||||||
return (isValid) ? true : Promise.reject(new Error(lang.t('auth:errors:invalidlogin')))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Mongoose.model('User', userSchema)
|
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
const _ = require('lodash')
|
||||||
|
const passport = require('passport')
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
strategies: {},
|
||||||
|
init() {
|
||||||
|
this.passport = passport
|
||||||
|
|
||||||
|
// Serialization user methods
|
||||||
|
|
||||||
|
passport.serializeUser(function (user, done) {
|
||||||
|
done(null, user._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
passport.deserializeUser(function (id, done) {
|
||||||
|
wiki.db.User.findById(id).then((user) => {
|
||||||
|
if (user) {
|
||||||
|
done(null, user)
|
||||||
|
} else {
|
||||||
|
done(new Error(wiki.lang.t('auth:errors:usernotfound')), null)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}).catch((err) => {
|
||||||
|
done(err, null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load authentication strategies
|
||||||
|
|
||||||
|
wiki.config.auth.strategies.local = {}
|
||||||
|
|
||||||
|
_.forOwn(wiki.config.auth.strategies, (strategyConfig, strategyKey) => {
|
||||||
|
strategyConfig.callbackURL = `${wiki.config.site.host}${wiki.config.site.path}/login/${strategyKey}/callback`
|
||||||
|
let strategy = require(`../authentication/${strategyKey}`)
|
||||||
|
strategy.init(passport, strategyConfig)
|
||||||
|
fs.readFile(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${strategyKey}.svg`), 'utf8').then(iconData => {
|
||||||
|
strategy.icon = iconData
|
||||||
|
}).catch(err => {
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
strategy.icon = '[missing icon]'
|
||||||
|
} else {
|
||||||
|
wiki.logger.error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.strategies[strategy.key] = strategy
|
||||||
|
wiki.logger.info(`Authentication Provider ${strategyKey}: OK`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create Guest account for first-time
|
||||||
|
|
||||||
|
wiki.db.User.findOne({
|
||||||
|
where: {
|
||||||
|
provider: 'local',
|
||||||
|
email: 'guest@example.com'
|
||||||
|
}
|
||||||
|
}).then((c) => {
|
||||||
|
if (c < 1) {
|
||||||
|
return wiki.db.User.create({
|
||||||
|
provider: 'local',
|
||||||
|
email: 'guest@example.com',
|
||||||
|
name: 'Guest',
|
||||||
|
password: '',
|
||||||
|
role: 'guest'
|
||||||
|
}).then(() => {
|
||||||
|
wiki.logger.info('[AUTH] Guest account created successfully!')
|
||||||
|
return true
|
||||||
|
}).catch((err) => {
|
||||||
|
wiki.logger.error('[AUTH] An error occured while creating guest account:')
|
||||||
|
wiki.logger.error(err)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// .then(() => {
|
||||||
|
// if (process.env.WIKI_JS_HEROKU) {
|
||||||
|
// return wiki.db.User.findOne({ provider: 'local', email: process.env.WIKI_ADMIN_EMAIL }).then((c) => {
|
||||||
|
// if (c < 1) {
|
||||||
|
// // Create root admin account (HEROKU ONLY)
|
||||||
|
|
||||||
|
// return wiki.db.User.create({
|
||||||
|
// provider: 'local',
|
||||||
|
// email: process.env.WIKI_ADMIN_EMAIL,
|
||||||
|
// name: 'Administrator',
|
||||||
|
// password: '$2a$04$MAHRw785Xe/Jd5kcKzr3D.VRZDeomFZu2lius4gGpZZ9cJw7B7Mna', // admin123 (default)
|
||||||
|
// role: 'admin'
|
||||||
|
// }).then(() => {
|
||||||
|
// wiki.logger.info('[AUTH] Root admin account created successfully!')
|
||||||
|
// return true
|
||||||
|
// }).catch((err) => {
|
||||||
|
// wiki.logger.error('[AUTH] An error occured while creating root admin account:')
|
||||||
|
// wiki.logger.error(err)
|
||||||
|
// return err
|
||||||
|
// })
|
||||||
|
// } else { return true }
|
||||||
|
// })
|
||||||
|
// } else { return true }
|
||||||
|
// })
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const yaml = require('js-yaml')
|
||||||
|
const _ = require('lodash')
|
||||||
|
const path = require('path')
|
||||||
|
const cfgHelper = require('../helpers/config')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* Load root config from disk
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
let confPaths = {
|
||||||
|
config: path.join(wiki.ROOTPATH, 'config.yml'),
|
||||||
|
data: path.join(wiki.SERVERPATH, 'app/data.yml'),
|
||||||
|
dataRegex: path.join(wiki.SERVERPATH, 'app/regex.js')
|
||||||
|
}
|
||||||
|
|
||||||
|
let appconfig = {}
|
||||||
|
let appdata = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
appconfig = yaml.safeLoad(
|
||||||
|
cfgHelper.parseConfigValue(
|
||||||
|
fs.readFileSync(confPaths.config, 'utf8')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
appdata = yaml.safeLoad(fs.readFileSync(confPaths.data, 'utf8'))
|
||||||
|
appdata.regex = require(confPaths.dataRegex)
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge with defaults
|
||||||
|
|
||||||
|
appconfig = _.defaultsDeep(appconfig, appdata.defaults.config)
|
||||||
|
|
||||||
|
// Check port
|
||||||
|
|
||||||
|
if (appconfig.port < 1) {
|
||||||
|
appconfig.port = process.env.PORT || 80
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert booleans
|
||||||
|
|
||||||
|
appconfig.public = (appconfig.public === true || _.toLower(appconfig.public) === 'true')
|
||||||
|
|
||||||
|
// List authentication strategies
|
||||||
|
wiki.config = appconfig
|
||||||
|
wiki.data = appdata
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load config from DB
|
||||||
|
*
|
||||||
|
* @param {Array} subsets Array of subsets to load
|
||||||
|
* @returns Promise
|
||||||
|
*/
|
||||||
|
loadFromDb(subsets) {
|
||||||
|
if (!_.isArray(subsets) || subsets.length === 0) {
|
||||||
|
subsets = wiki.data.configNamespaces
|
||||||
|
}
|
||||||
|
|
||||||
|
return wiki.db.Setting.findAll({
|
||||||
|
attributes: ['key', 'config'],
|
||||||
|
where: {
|
||||||
|
key: {
|
||||||
|
$in: subsets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).then(results => {
|
||||||
|
if (_.isArray(results) && results.length === subsets.length) {
|
||||||
|
results.forEach(result => {
|
||||||
|
wiki.config[result.key] = result.config
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
wiki.logger.warn('DB Configuration is empty or incomplete.')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const _ = require('lodash')
|
||||||
|
const Promise = require('bluebird')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const Op = Sequelize.Op
|
||||||
|
|
||||||
|
const operatorsAliases = {
|
||||||
|
$eq: Op.eq,
|
||||||
|
$ne: Op.ne,
|
||||||
|
$gte: Op.gte,
|
||||||
|
$gt: Op.gt,
|
||||||
|
$lte: Op.lte,
|
||||||
|
$lt: Op.lt,
|
||||||
|
$not: Op.not,
|
||||||
|
$in: Op.in,
|
||||||
|
$notIn: Op.notIn,
|
||||||
|
$is: Op.is,
|
||||||
|
$like: Op.like,
|
||||||
|
$notLike: Op.notLike,
|
||||||
|
$iLike: Op.iLike,
|
||||||
|
$notILike: Op.notILike,
|
||||||
|
$regexp: Op.regexp,
|
||||||
|
$notRegexp: Op.notRegexp,
|
||||||
|
$iRegexp: Op.iRegexp,
|
||||||
|
$notIRegexp: Op.notIRegexp,
|
||||||
|
$between: Op.between,
|
||||||
|
$notBetween: Op.notBetween,
|
||||||
|
$overlap: Op.overlap,
|
||||||
|
$contains: Op.contains,
|
||||||
|
$contained: Op.contained,
|
||||||
|
$adjacent: Op.adjacent,
|
||||||
|
$strictLeft: Op.strictLeft,
|
||||||
|
$strictRight: Op.strictRight,
|
||||||
|
$noExtendRight: Op.noExtendRight,
|
||||||
|
$noExtendLeft: Op.noExtendLeft,
|
||||||
|
$and: Op.and,
|
||||||
|
$or: Op.or,
|
||||||
|
$any: Op.any,
|
||||||
|
$all: Op.all,
|
||||||
|
$values: Op.values,
|
||||||
|
$col: Op.col
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL DB module
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
Sequelize,
|
||||||
|
Op: Sequelize.Op,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize DB
|
||||||
|
*
|
||||||
|
* @return {Object} DB instance
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
let self = this
|
||||||
|
|
||||||
|
let dbModelsPath = path.join(wiki.SERVERPATH, 'models')
|
||||||
|
|
||||||
|
// Define Sequelize instance
|
||||||
|
|
||||||
|
self.inst = new self.Sequelize(wiki.config.db.db, wiki.config.db.user, wiki.config.db.pass, {
|
||||||
|
host: wiki.config.db.host,
|
||||||
|
port: wiki.config.db.port,
|
||||||
|
dialect: 'postgres',
|
||||||
|
pool: {
|
||||||
|
max: 10,
|
||||||
|
min: 0,
|
||||||
|
idle: 10000
|
||||||
|
},
|
||||||
|
logging: log => { wiki.logger.log('verbose', log) },
|
||||||
|
operatorsAliases
|
||||||
|
})
|
||||||
|
|
||||||
|
// Attempt to connect and authenticate to DB
|
||||||
|
|
||||||
|
self.inst.authenticate().then(() => {
|
||||||
|
wiki.logger.info('Database (PostgreSQL) connection: OK')
|
||||||
|
}).catch(err => {
|
||||||
|
wiki.logger.error('Failed to connect to PostgreSQL instance.')
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load DB Models
|
||||||
|
|
||||||
|
fs
|
||||||
|
.readdirSync(dbModelsPath)
|
||||||
|
.filter(file => {
|
||||||
|
return (file.indexOf('.') !== 0 && file.indexOf('_') !== 0)
|
||||||
|
})
|
||||||
|
.forEach(file => {
|
||||||
|
let modelName = _.upperFirst(_.camelCase(_.split(file, '.')[0]))
|
||||||
|
self[modelName] = self.inst.import(path.join(dbModelsPath, file))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Associate DB Models
|
||||||
|
|
||||||
|
require(path.join(dbModelsPath, '_relations.js'))(self)
|
||||||
|
|
||||||
|
// Set init tasks
|
||||||
|
|
||||||
|
let initTasks = {
|
||||||
|
// -> Sync DB Schemas
|
||||||
|
syncSchemas() {
|
||||||
|
return self.inst.sync({
|
||||||
|
force: false,
|
||||||
|
logging: log => { wiki.logger.log('verbose', log) }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// -> Set Connection App Name
|
||||||
|
setAppName() {
|
||||||
|
return self.inst.query(`set application_name = 'Wiki.js'`, { raw: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let initTasksQueue = (wiki.IS_MASTER) ? [
|
||||||
|
initTasks.syncSchemas,
|
||||||
|
initTasks.setAppName
|
||||||
|
] : [
|
||||||
|
initTasks.setAppName
|
||||||
|
]
|
||||||
|
|
||||||
|
// Perform init tasks
|
||||||
|
|
||||||
|
self.onReady = Promise.each(initTasksQueue, t => t()).return(true)
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global lang, winston */
|
/* global wiki */
|
||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
@@ -10,7 +10,7 @@ const os = require('os')
|
|||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Local Data Storage
|
* Local Disk Storage
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
@@ -21,29 +21,26 @@ module.exports = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Local Data Storage model
|
* Initialize Local Data Storage model
|
||||||
*
|
|
||||||
* @return {Object} Local Data Storage model instance
|
|
||||||
*/
|
*/
|
||||||
init () {
|
init () {
|
||||||
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
|
this._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads')
|
||||||
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
|
this._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs')
|
||||||
|
|
||||||
this.createBaseDirectories(appconfig)
|
this.createBaseDirectories()
|
||||||
this.initMulter(appconfig)
|
// this.initMulter()
|
||||||
|
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init Multer upload handlers
|
* Init Multer upload handlers
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
|
||||||
* @return {boolean} Void
|
|
||||||
*/
|
*/
|
||||||
initMulter (appconfig) {
|
initMulter () {
|
||||||
let maxFileSizes = {
|
let maxFileSizes = {
|
||||||
img: appconfig.uploads.maxImageFileSize * 1024 * 1024,
|
// img: wiki.config.uploads.maxImageFileSize * 1024 * 1024,
|
||||||
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024
|
// file: wiki.config.uploads.maxOtherFileSize * 1024 * 1024
|
||||||
|
img: 3 * 1024 * 1024,
|
||||||
|
file: 10 * 1024 * 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
// -> IMAGES
|
// -> IMAGES
|
||||||
@@ -51,7 +48,7 @@ module.exports = {
|
|||||||
this.uploadImgHandler = multer({
|
this.uploadImgHandler = multer({
|
||||||
storage: multer.diskStorage({
|
storage: multer.diskStorage({
|
||||||
destination: (req, f, cb) => {
|
destination: (req, f, cb) => {
|
||||||
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
|
cb(null, path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'temp-upload'))
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
fileFilter: (req, f, cb) => {
|
fileFilter: (req, f, cb) => {
|
||||||
@@ -76,7 +73,7 @@ module.exports = {
|
|||||||
this.uploadFileHandler = multer({
|
this.uploadFileHandler = multer({
|
||||||
storage: multer.diskStorage({
|
storage: multer.diskStorage({
|
||||||
destination: (req, f, cb) => {
|
destination: (req, f, cb) => {
|
||||||
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
|
cb(null, path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'temp-upload'))
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
fileFilter: (req, f, cb) => {
|
fileFilter: (req, f, cb) => {
|
||||||
@@ -95,35 +92,30 @@ module.exports = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a base directories (Synchronous).
|
* Creates a base directories (Synchronous).
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
|
||||||
* @return {Void} Void
|
|
||||||
*/
|
*/
|
||||||
createBaseDirectories (appconfig) {
|
createBaseDirectories () {
|
||||||
winston.info('Checking data directories...')
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data))
|
fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data))
|
||||||
fs.emptyDirSync(path.resolve(ROOTPATH, appconfig.paths.data))
|
fs.emptyDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data))
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'))
|
fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './cache'))
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'))
|
fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './thumbs'))
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'))
|
fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './temp-upload'))
|
||||||
|
|
||||||
if (os.type() !== 'Windows_NT') {
|
if (os.type() !== 'Windows_NT') {
|
||||||
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '755')
|
fs.chmodSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './temp-upload'), '755')
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo))
|
fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo))
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'))
|
fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, './uploads'))
|
||||||
|
|
||||||
if (os.type() !== 'Windows_NT') {
|
if (os.type() !== 'Windows_NT') {
|
||||||
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'), '755')
|
fs.chmodSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, './uploads'), '755')
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
winston.error(err)
|
wiki.logger.error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
winston.info('Data and Repository directories are OK.')
|
wiki.logger.info('Disk Data Paths: OK')
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,7 +146,7 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
validateUploadsFilename (f, fld, isImage) {
|
validateUploadsFilename (f, fld, isImage) {
|
||||||
let fObj = path.parse(f)
|
let fObj = path.parse(f)
|
||||||
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(new RegExp('[^a-z0-9-' + appdata.regex.cjk + appdata.regex.arabic + ']', 'g'), '')
|
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(new RegExp('[^a-z0-9-' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']', 'g'), '')
|
||||||
let fext = _.toLower(fObj.ext)
|
let fext = _.toLower(fObj.ext)
|
||||||
|
|
||||||
if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
|
if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
|
||||||
@@ -165,7 +157,7 @@ module.exports = {
|
|||||||
let fpath = path.resolve(this._uploadsPath, fld, f)
|
let fpath = path.resolve(this._uploadsPath, fld, f)
|
||||||
|
|
||||||
return fs.statAsync(fpath).then((s) => {
|
return fs.statAsync(fpath).then((s) => {
|
||||||
throw new Error(lang.t('errors:fileexists', { path: f }))
|
throw new Error(wiki.lang.t('errors:fileexists', { path: f }))
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
return f
|
return f
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global db, git, lang, mark, rights, search, winston */
|
/* global wiki */
|
||||||
|
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
@@ -10,7 +10,7 @@ const _ = require('lodash')
|
|||||||
const entryHelper = require('../helpers/entry')
|
const entryHelper = require('../helpers/entry')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entries Model
|
* Documents Model
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
@@ -25,10 +25,10 @@ module.exports = {
|
|||||||
init() {
|
init() {
|
||||||
let self = this
|
let self = this
|
||||||
|
|
||||||
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
|
self._repoPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo)
|
||||||
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache')
|
self._cachePath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'cache')
|
||||||
appdata.repoPath = self._repoPath
|
wiki.data.repoPath = self._repoPath
|
||||||
appdata.cachePath = self._cachePath
|
wiki.data.cachePath = self._cachePath
|
||||||
|
|
||||||
return self
|
return self
|
||||||
},
|
},
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global lang, winston */
|
/* global wiki */
|
||||||
|
|
||||||
const Git = require('git-wrapper2-promise')
|
const Git = require('git-wrapper2-promise')
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
@@ -43,21 +43,19 @@ module.exports = {
|
|||||||
|
|
||||||
// -> Build repository path
|
// -> Build repository path
|
||||||
|
|
||||||
if (_.isEmpty(appconfig.paths.repo)) {
|
if (_.isEmpty(wiki.config.paths.repo)) {
|
||||||
self._repo.path = path.join(ROOTPATH, 'repo')
|
self._repo.path = path.join(wiki.ROOTPATH, 'repo')
|
||||||
} else {
|
} else {
|
||||||
self._repo.path = appconfig.paths.repo
|
self._repo.path = wiki.config.paths.repo
|
||||||
}
|
}
|
||||||
|
|
||||||
// -> Initialize repository
|
// -> Initialize repository
|
||||||
|
|
||||||
self.onReady = self._initRepo(appconfig)
|
self.onReady = (wiki.IS_MASTER) ? self._initRepo() : Promise.resolve()
|
||||||
|
|
||||||
if (appconfig.git) {
|
if (wiki.config.git) {
|
||||||
// Set repo branch
|
self._repo.branch = wiki.config.git.branch || 'master'
|
||||||
self._repo.branch = appconfig.git.branch || 'master'
|
self._signature.email = wiki.config.git.serverEmail || 'wiki@example.com'
|
||||||
// Define signature
|
|
||||||
self._signature.email = appconfig.git.serverEmail || 'wiki@example.com'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return self
|
return self
|
||||||
@@ -66,19 +64,17 @@ module.exports = {
|
|||||||
/**
|
/**
|
||||||
* Initialize Git repository
|
* Initialize Git repository
|
||||||
*
|
*
|
||||||
* @param {Object} appconfig The application config
|
* @param {Object} wiki.config The application config
|
||||||
* @return {Object} Promise
|
* @return {Object} Promise
|
||||||
*/
|
*/
|
||||||
_initRepo(appconfig) {
|
_initRepo() {
|
||||||
let self = this
|
let self = this
|
||||||
|
|
||||||
winston.info('Checking Git repository...')
|
|
||||||
|
|
||||||
// -> Check if path is accessible
|
// -> Check if path is accessible
|
||||||
|
|
||||||
return fs.mkdirAsync(self._repo.path).catch((err) => {
|
return fs.mkdirAsync(self._repo.path).catch((err) => {
|
||||||
if (err.code !== 'EEXIST') {
|
if (err.code !== 'EEXIST') {
|
||||||
winston.error('Invalid Git repository path or missing permissions.')
|
wiki.logger.error('Invalid Git repository path or missing permissions.')
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
self._git = new Git({ 'git-dir': self._repo.path })
|
self._git = new Git({ 'git-dir': self._repo.path })
|
||||||
@@ -92,28 +88,28 @@ module.exports = {
|
|||||||
self._repo.exists = false
|
self._repo.exists = false
|
||||||
})
|
})
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (appconfig.git === false) {
|
if (wiki.config.git === false) {
|
||||||
winston.info('Remote Git syncing is disabled. Not recommended!')
|
wiki.logger.warn('Remote Git syncing is disabled. Not recommended!')
|
||||||
return Promise.resolve(true)
|
return Promise.resolve(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize remote
|
// Initialize remote
|
||||||
|
|
||||||
let urlObj = URL.parse(appconfig.git.url)
|
let urlObj = URL.parse(wiki.config.git.url)
|
||||||
if (appconfig.git.auth.type !== 'ssh') {
|
if (wiki.config.git.auth.type !== 'ssh') {
|
||||||
urlObj.auth = appconfig.git.auth.username + ':' + appconfig.git.auth.password
|
urlObj.auth = wiki.config.git.auth.username + ':' + wiki.config.git.auth.password
|
||||||
}
|
}
|
||||||
self._url = URL.format(urlObj)
|
self._url = URL.format(urlObj)
|
||||||
|
|
||||||
let gitConfigs = [
|
let gitConfigs = [
|
||||||
() => { return self._git.exec('config', ['--local', 'user.name', 'Wiki']) },
|
() => { return self._git.exec('config', ['--local', 'user.name', 'Wiki']) },
|
||||||
() => { return self._git.exec('config', ['--local', 'user.email', self._signature.email]) },
|
() => { return self._git.exec('config', ['--local', 'user.email', self._signature.email]) },
|
||||||
() => { return self._git.exec('config', ['--local', '--bool', 'http.sslVerify', _.toString(appconfig.git.auth.sslVerify)]) }
|
() => { return self._git.exec('config', ['--local', '--bool', 'http.sslVerify', _.toString(wiki.config.git.auth.sslVerify)]) }
|
||||||
]
|
]
|
||||||
|
|
||||||
if (appconfig.git.auth.type === 'ssh') {
|
if (wiki.config.git.auth.type === 'ssh') {
|
||||||
gitConfigs.push(() => {
|
gitConfigs.push(() => {
|
||||||
return self._git.exec('config', ['--local', 'core.sshCommand', 'ssh -i "' + appconfig.git.auth.privateKey + '" -o StrictHostKeyChecking=no'])
|
return self._git.exec('config', ['--local', 'core.sshCommand', 'ssh -i "' + wiki.config.git.auth.privateKey + '" -o StrictHostKeyChecking=no'])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,14 +122,14 @@ module.exports = {
|
|||||||
return self._git.exec('remote', ['set-url', 'origin', self._url])
|
return self._git.exec('remote', ['set-url', 'origin', self._url])
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
winston.error(err)
|
wiki.logger.error(err)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
winston.error('Git remote error!')
|
wiki.logger.error('Git remote error!')
|
||||||
throw err
|
throw err
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
winston.info('Git repository is OK.')
|
wiki.logger.info('Git Repository: OK')
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -144,7 +140,7 @@ module.exports = {
|
|||||||
* @return {String} The repo path.
|
* @return {String} The repo path.
|
||||||
*/
|
*/
|
||||||
getRepoPath() {
|
getRepoPath() {
|
||||||
return this._repo.path || path.join(ROOTPATH, 'repo')
|
return this._repo.path || path.join(wiki.ROOTPATH, 'repo')
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,18 +153,18 @@ module.exports = {
|
|||||||
|
|
||||||
// Is git remote disabled?
|
// Is git remote disabled?
|
||||||
|
|
||||||
if (appconfig.git === false) {
|
if (wiki.config.git === false) {
|
||||||
return Promise.resolve(true)
|
return Promise.resolve(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch
|
// Fetch
|
||||||
|
|
||||||
winston.info('Performing pull from remote Git repository...')
|
wiki.logger.info('Performing pull from remote Git repository...')
|
||||||
return self._git.pull('origin', self._repo.branch).then((cProc) => {
|
return self._git.pull('origin', self._repo.branch).then((cProc) => {
|
||||||
winston.info('Git Pull completed.')
|
wiki.logger.info('Git Pull completed.')
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
winston.error('Unable to fetch from git origin!')
|
wiki.logger.error('Unable to fetch from git origin!')
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -178,19 +174,19 @@ module.exports = {
|
|||||||
let out = cProc.stdout.toString()
|
let out = cProc.stdout.toString()
|
||||||
|
|
||||||
if (_.includes(out, 'commit')) {
|
if (_.includes(out, 'commit')) {
|
||||||
winston.info('Performing push to remote Git repository...')
|
wiki.logger.info('Performing push to remote Git repository...')
|
||||||
return self._git.push('origin', self._repo.branch).then(() => {
|
return self._git.push('origin', self._repo.branch).then(() => {
|
||||||
return winston.info('Git Push completed.')
|
return wiki.logger.info('Git Push completed.')
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
winston.info('Git Push skipped. Repository is already in sync.')
|
wiki.logger.info('Git Push skipped. Repository is already in sync.')
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
winston.error('Unable to push changes to remote Git repository!')
|
wiki.logger.error('Unable to push changes to remote Git repository!')
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -210,7 +206,7 @@ module.exports = {
|
|||||||
let out = cProc.stdout.toString()
|
let out = cProc.stdout.toString()
|
||||||
return _.includes(out, gitFilePath)
|
return _.includes(out, gitFilePath)
|
||||||
}).then((isTracked) => {
|
}).then((isTracked) => {
|
||||||
commitMsg = (isTracked) ? lang.t('git:updated', { path: gitFilePath }) : lang.t('git:added', { path: gitFilePath })
|
commitMsg = (isTracked) ? wiki.lang.t('git:updated', { path: gitFilePath }) : wiki.lang.t('git:added', { path: gitFilePath })
|
||||||
return self._git.add(gitFilePath)
|
return self._git.add(gitFilePath)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
let commitUsr = securityHelper.sanitizeCommitUser(author)
|
let commitUsr = securityHelper.sanitizeCommitUser(author)
|
||||||
@@ -245,29 +241,6 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a document.
|
|
||||||
*
|
|
||||||
* @param {String} entryPath The entry path
|
|
||||||
* @return {Promise<Boolean>} Resolve on success
|
|
||||||
*/
|
|
||||||
deleteDocument(entryPath, author) {
|
|
||||||
let self = this
|
|
||||||
let gitFilePath = entryPath + '.md'
|
|
||||||
|
|
||||||
return this._git.exec('rm', [gitFilePath]).then((cProc) => {
|
|
||||||
let out = cProc.stdout.toString()
|
|
||||||
if (_.includes(out, 'fatal')) {
|
|
||||||
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
|
|
||||||
throw new Error(errorMsg)
|
|
||||||
}
|
|
||||||
let commitUsr = securityHelper.sanitizeCommitUser(author)
|
|
||||||
return self._git.exec('commit', ['-m', lang.t('git:deleted', { path: gitFilePath }), '--author="' + commitUsr.name + ' <' + commitUsr.email + '>"']).catch((err) => {
|
|
||||||
if (_.includes(err.stdout, 'nothing to commit')) { return true }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commits uploads changes.
|
* Commits uploads changes.
|
||||||
*
|
*
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
const gqlTools = require('graphql-tools')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
|
const typeDefs = fs.readFileSync(path.join(wiki.SERVERPATH, 'schemas/types.graphql'), 'utf8')
|
||||||
|
|
||||||
|
const DateScalar = require('../schemas/scalar-date')
|
||||||
|
const AuthenticationResolvers = require('../schemas/resolvers-authentication')
|
||||||
|
const CommentResolvers = require('../schemas/resolvers-comment')
|
||||||
|
const DocumentResolvers = require('../schemas/resolvers-document')
|
||||||
|
const FileResolvers = require('../schemas/resolvers-file')
|
||||||
|
const FolderResolvers = require('../schemas/resolvers-folder')
|
||||||
|
const GroupResolvers = require('../schemas/resolvers-group')
|
||||||
|
const SettingResolvers = require('../schemas/resolvers-setting')
|
||||||
|
const TagResolvers = require('../schemas/resolvers-tag')
|
||||||
|
const TranslationResolvers = require('../schemas/resolvers-translation')
|
||||||
|
const UserResolvers = require('../schemas/resolvers-user')
|
||||||
|
|
||||||
|
const resolvers = _.merge(
|
||||||
|
AuthenticationResolvers,
|
||||||
|
CommentResolvers,
|
||||||
|
DocumentResolvers,
|
||||||
|
FileResolvers,
|
||||||
|
FolderResolvers,
|
||||||
|
GroupResolvers,
|
||||||
|
SettingResolvers,
|
||||||
|
TagResolvers,
|
||||||
|
TranslationResolvers,
|
||||||
|
UserResolvers,
|
||||||
|
DateScalar
|
||||||
|
)
|
||||||
|
|
||||||
|
const Schema = gqlTools.makeExecutableSchema({
|
||||||
|
typeDefs,
|
||||||
|
resolvers
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = Schema
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
const cluster = require('cluster')
|
||||||
|
const Promise = require('bluebird')
|
||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
numWorkers: 1,
|
||||||
|
workers: [],
|
||||||
|
init() {
|
||||||
|
if (cluster.isMaster) {
|
||||||
|
wiki.logger.info('=======================================')
|
||||||
|
wiki.logger.info('= Wiki.js =============================')
|
||||||
|
wiki.logger.info('=======================================')
|
||||||
|
|
||||||
|
wiki.redis = require('./redis').init()
|
||||||
|
wiki.queue = require('./queue').init()
|
||||||
|
|
||||||
|
this.setWorkerLimit()
|
||||||
|
this.bootMaster()
|
||||||
|
} else {
|
||||||
|
this.bootWorker()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Pre-Master Boot Sequence
|
||||||
|
*/
|
||||||
|
preBootMaster() {
|
||||||
|
return Promise.mapSeries([
|
||||||
|
() => { return wiki.db.onReady },
|
||||||
|
() => { return wiki.configSvc.loadFromDb() },
|
||||||
|
() => { return wiki.queue.clean() }
|
||||||
|
], fn => { return fn() })
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Boot Master Process
|
||||||
|
*/
|
||||||
|
bootMaster() {
|
||||||
|
this.preBootMaster().then(sequenceResults => {
|
||||||
|
if (_.every(sequenceResults, rs => rs === true)) {
|
||||||
|
this.postBootMaster()
|
||||||
|
} else {
|
||||||
|
wiki.logger.info('Starting configuration manager...')
|
||||||
|
require('../configure')()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}).catch(err => {
|
||||||
|
wiki.logger.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Post-Master Boot Sequence
|
||||||
|
*/
|
||||||
|
postBootMaster() {
|
||||||
|
require('../master')().then(() => {
|
||||||
|
_.times(this.numWorker, this.spawnWorker)
|
||||||
|
|
||||||
|
wiki.queue.uplClearTemp.add({}, {
|
||||||
|
repeat: { cron: '*/15 * * * *' }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cluster.on('exit', (worker, code, signal) => {
|
||||||
|
wiki.logger.info(`Background Worker #${worker.id} was terminated.`)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Boot Worker Process
|
||||||
|
*/
|
||||||
|
bootWorker() {
|
||||||
|
wiki.logger.info(`Background Worker #${cluster.worker.id} is initializing...`)
|
||||||
|
require('../worker')
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Spawn new Worker process
|
||||||
|
*/
|
||||||
|
spawnWorker() {
|
||||||
|
this.workers.push(cluster.fork())
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Set Worker count based on config + system capabilities
|
||||||
|
*/
|
||||||
|
setWorkerLimit() {
|
||||||
|
const numCPUs = require('os').cpus().length
|
||||||
|
this.numWorkers = (wiki.config.workers > 0) ? wiki.config.workers : numCPUs
|
||||||
|
if (this.numWorkers > numCPUs) {
|
||||||
|
this.numWorkers = numCPUs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
const _ = require('lodash')
|
||||||
|
const dotize = require('dotize')
|
||||||
|
const i18nBackend = require('i18next-node-fs-backend')
|
||||||
|
const i18next = require('i18next')
|
||||||
|
const path = require('path')
|
||||||
|
const Promise = require('bluebird')
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
engine: null,
|
||||||
|
namespaces: ['common', 'admin', 'auth', 'errors', 'git'],
|
||||||
|
init() {
|
||||||
|
this.engine = i18next
|
||||||
|
this.engine.use(i18nBackend).init({
|
||||||
|
load: 'languageOnly',
|
||||||
|
ns: this.namespaces,
|
||||||
|
defaultNS: 'common',
|
||||||
|
saveMissing: false,
|
||||||
|
preload: [wiki.config.site.lang],
|
||||||
|
lng: wiki.config.site.lang,
|
||||||
|
fallbackLng: 'en',
|
||||||
|
backend: {
|
||||||
|
loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
getByNamespace(locale, namespace) {
|
||||||
|
if (this.engine.hasResourceBundle(locale, namespace)) {
|
||||||
|
let data = this.engine.getResourceBundle(locale, namespace)
|
||||||
|
return _.map(dotize.convert(data), (value, key) => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid locale or namespace')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadLocale(locale) {
|
||||||
|
return Promise.fromCallback(cb => {
|
||||||
|
return this.engine.loadLanguages(locale, cb)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setCurrentLocale(locale) {
|
||||||
|
return Promise.fromCallback(cb => {
|
||||||
|
return this.engine.changeLanguage(locale, cb)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
const cluster = require('cluster')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init() {
|
||||||
|
let winston = require('winston')
|
||||||
|
|
||||||
|
// Console
|
||||||
|
|
||||||
|
let logger = new (winston.Logger)({
|
||||||
|
level: (wiki.IS_DEBUG) ? 'debug' : 'info',
|
||||||
|
transports: [
|
||||||
|
new (winston.transports.Console)({
|
||||||
|
level: (wiki.IS_DEBUG) ? 'debug' : 'info',
|
||||||
|
prettyPrint: true,
|
||||||
|
colorize: true,
|
||||||
|
silent: false,
|
||||||
|
timestamp: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.filters.push((level, msg) => {
|
||||||
|
let processName = (cluster.isMaster) ? 'MASTER' : `WORKER-${cluster.worker.id}`
|
||||||
|
return '[' + processName + '] ' + msg
|
||||||
|
})
|
||||||
|
|
||||||
|
// External services
|
||||||
|
|
||||||
|
// if (wiki.config.externalLogging.bugsnag) {
|
||||||
|
// const bugsnagTransport = require('./winston-transports/bugsnag')
|
||||||
|
// logger.add(bugsnagTransport, {
|
||||||
|
// level: 'warn',
|
||||||
|
// key: wiki.config.externalLogging.bugsnag
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (wiki.config.externalLogging.loggly) {
|
||||||
|
// require('winston-loggly-bulk')
|
||||||
|
// logger.add(winston.transports.Loggly, {
|
||||||
|
// token: wiki.config.externalLogging.loggly.token,
|
||||||
|
// subdomain: wiki.config.externalLogging.loggly.subdomain,
|
||||||
|
// tags: ['wiki-js'],
|
||||||
|
// level: 'warn',
|
||||||
|
// json: true
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (wiki.config.externalLogging.papertrail) {
|
||||||
|
// require('winston-papertrail').Papertrail // eslint-disable-line no-unused-expressions
|
||||||
|
// logger.add(winston.transports.Papertrail, {
|
||||||
|
// host: wiki.config.externalLogging.papertrail.host,
|
||||||
|
// port: wiki.config.externalLogging.papertrail.port,
|
||||||
|
// level: 'warn',
|
||||||
|
// program: 'wiki.js'
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (wiki.config.externalLogging.rollbar) {
|
||||||
|
// const rollbarTransport = require('./winston-transports/rollbar')
|
||||||
|
// logger.add(rollbarTransport, {
|
||||||
|
// level: 'warn',
|
||||||
|
// key: wiki.config.externalLogging.rollbar
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (wiki.config.externalLogging.sentry) {
|
||||||
|
// const sentryTransport = require('./winston-transports/sentry')
|
||||||
|
// logger.add(sentryTransport, {
|
||||||
|
// level: 'warn',
|
||||||
|
// key: wiki.config.externalLogging.sentry
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global winston */
|
/* global wiki */
|
||||||
|
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
const md = require('markdown-it')
|
const md = require('markdown-it')
|
||||||
@@ -23,11 +23,12 @@ const mdRemove = require('remove-markdown')
|
|||||||
|
|
||||||
var mkdown = md({
|
var mkdown = md({
|
||||||
html: true,
|
html: true,
|
||||||
breaks: appconfig.features.linebreaks,
|
// breaks: wiki.config.features.linebreaks,
|
||||||
|
breaks: true,
|
||||||
linkify: true,
|
linkify: true,
|
||||||
typography: true,
|
typography: true,
|
||||||
highlight(str, lang) {
|
highlight(str, lang) {
|
||||||
if (appconfig.theme.code.colorize && lang && hljs.getLanguage(lang)) {
|
if (wiki.config.theme.code.colorize && lang && hljs.getLanguage(lang)) {
|
||||||
try {
|
try {
|
||||||
return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>'
|
return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>'
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -57,7 +58,8 @@ var mkdown = md({
|
|||||||
})
|
})
|
||||||
.use(mdAttrs)
|
.use(mdAttrs)
|
||||||
|
|
||||||
if (appconfig.features.mathjax) {
|
// if (wiki.config.features.mathjax) {
|
||||||
|
if (true) {
|
||||||
mkdown.use(mdMathjax)
|
mkdown.use(mdMathjax)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@ const videoRules = [
|
|||||||
|
|
||||||
// Regex
|
// Regex
|
||||||
|
|
||||||
const textRegex = new RegExp('\\b[a-z0-9-.,' + appdata.regex.cjk + appdata.regex.arabic + ']+\\b', 'g')
|
const textRegex = new RegExp('\\b[a-z0-9-.,' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']+\\b', 'g')
|
||||||
const mathRegex = [
|
const mathRegex = [
|
||||||
{
|
{
|
||||||
format: 'TeX',
|
format: 'TeX',
|
||||||
@@ -301,7 +303,7 @@ const parseContent = (content) => {
|
|||||||
|
|
||||||
// Mathjax Post-processor
|
// Mathjax Post-processor
|
||||||
|
|
||||||
if (appconfig.features.mathjax) {
|
if (wiki.config.features.mathjax) {
|
||||||
return processMathjax(cr.html())
|
return processMathjax(cr.html())
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve(cr.html())
|
return Promise.resolve(cr.html())
|
||||||
@@ -339,7 +341,7 @@ const processMathjax = (content) => {
|
|||||||
resolve(result.svg)
|
resolve(result.svg)
|
||||||
} else {
|
} else {
|
||||||
resolve(currentMatch[0])
|
resolve(currentMatch[0])
|
||||||
winston.warn(result.errors.join(', '))
|
wiki.logger.warn(result.errors.join(', '))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
const Bull = require('bull')
|
||||||
|
const Promise = require('bluebird')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init() {
|
||||||
|
wiki.data.queues.forEach(queueName => {
|
||||||
|
this[queueName] = new Bull(queueName, {
|
||||||
|
prefix: `q-${wiki.config.ha.nodeuid}`,
|
||||||
|
redis: wiki.config.redis
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
clean() {
|
||||||
|
return Promise.each(wiki.data.queues, queueName => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let keyStream = wiki.redis.scanStream({
|
||||||
|
match: `q-${wiki.config.ha.nodeuid}:${queueName}:*`
|
||||||
|
})
|
||||||
|
keyStream.on('data', resultKeys => {
|
||||||
|
if (resultKeys.length > 0) {
|
||||||
|
wiki.redis.del(resultKeys)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
keyStream.on('end', resolve)
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
wiki.logger.info('Purging old queue jobs: OK')
|
||||||
|
}).return(true).catch(err => {
|
||||||
|
wiki.logger.error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
const Redis = require('ioredis')
|
||||||
|
const { isPlainObject } = require('lodash')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis module
|
||||||
|
*
|
||||||
|
* @return {Object} Redis client wrapper instance
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Redis client
|
||||||
|
*
|
||||||
|
* @return {Object} Redis client instance
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
if (isPlainObject(wiki.config.redis)) {
|
||||||
|
let red = new Redis(wiki.config.redis)
|
||||||
|
red.on('ready', () => {
|
||||||
|
wiki.logger.info('Redis connection: OK')
|
||||||
|
})
|
||||||
|
return red
|
||||||
|
} else {
|
||||||
|
wiki.logger.error('Invalid Redis configuration!')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global db */
|
/* global wiki */
|
||||||
|
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ module.exports = {
|
|||||||
init () {
|
init () {
|
||||||
let self = this
|
let self = this
|
||||||
|
|
||||||
db.onReady.then(() => {
|
wiki.db.onReady.then(() => {
|
||||||
db.User.findOne({ provider: 'local', email: 'guest' }).then((u) => {
|
wiki.db.User.findOne({ provider: 'local', email: 'guest' }).then((u) => {
|
||||||
if (u) {
|
if (u) {
|
||||||
self.guest = u
|
self.guest = u
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global winston */
|
/* global wiki */
|
||||||
|
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const searchIndex = require('./search-index')
|
// const searchIndex = require('./search-index')
|
||||||
const stopWord = require('stopword')
|
const stopWord = require('stopword')
|
||||||
const streamToPromise = require('stream-to-promise')
|
const streamToPromise = require('stream-to-promise')
|
||||||
const searchAllowedChars = new RegExp('[^a-z0-9' + appdata.regex.cjk + appdata.regex.arabic + ' ]', 'g')
|
const searchAllowedChars = new RegExp('[^a-z0-9' + wiki.data.regex.cjk + wiki.data.regex.arabic + ' ]', 'g')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
@@ -22,24 +22,24 @@ module.exports = {
|
|||||||
init () {
|
init () {
|
||||||
let self = this
|
let self = this
|
||||||
self._isReady = new Promise((resolve, reject) => {
|
self._isReady = new Promise((resolve, reject) => {
|
||||||
searchIndex({
|
/*searchIndex({
|
||||||
deletable: true,
|
deletable: true,
|
||||||
fieldedSearch: true,
|
fieldedSearch: true,
|
||||||
indexPath: 'wiki',
|
indexPath: 'wiki',
|
||||||
logLevel: 'error',
|
logLevel: 'error',
|
||||||
stopwords: _.get(stopWord, appconfig.lang, [])
|
stopwords: _.get(stopWord, wiki.config.lang, [])
|
||||||
}, (err, si) => {
|
}, (err, si) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
winston.error('Failed to initialize search index.', err)
|
wiki.logger.error('Failed to initialize search index.', err)
|
||||||
reject(err)
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
self._si = Promise.promisifyAll(si)
|
self._si = Promise.promisifyAll(si)
|
||||||
self._si.flushAsync().then(() => {
|
self._si.flushAsync().then(() => {
|
||||||
winston.info('Search index flushed and ready.')
|
wiki.logger.info('Search index flushed and ready.')
|
||||||
resolve(true)
|
resolve(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
}) */
|
||||||
})
|
})
|
||||||
|
|
||||||
return self
|
return self
|
||||||
@@ -95,13 +95,13 @@ module.exports = {
|
|||||||
parent: content.parent || '',
|
parent: content.parent || '',
|
||||||
content: content.text || ''
|
content: content.text || ''
|
||||||
}]).then(() => {
|
}]).then(() => {
|
||||||
winston.log('verbose', 'Entry ' + content._id + ' added/updated to search index.')
|
wiki.logger.log('verbose', 'Entry ' + content._id + ' added/updated to search index.')
|
||||||
return true
|
return true
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
winston.error(err)
|
wiki.logger.error(err)
|
||||||
})
|
})
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
winston.error(err)
|
wiki.logger.error(err)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -131,7 +131,7 @@ module.exports = {
|
|||||||
if (err.type === 'NotFoundError') {
|
if (err.type === 'NotFoundError') {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
winston.error(err)
|
wiki.logger.error(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -204,7 +204,7 @@ module.exports = {
|
|||||||
suggest: []
|
suggest: []
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
winston.error(err)
|
wiki.logger.error(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
/* global db, git, lang, upl */
|
/* global wiki */
|
||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const Promise = require('bluebird')
|
const Promise = require('bluebird')
|
||||||
@@ -32,8 +32,8 @@ module.exports = {
|
|||||||
init () {
|
init () {
|
||||||
let self = this
|
let self = this
|
||||||
|
|
||||||
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads')
|
self._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads')
|
||||||
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs')
|
self._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs')
|
||||||
|
|
||||||
return self
|
return self
|
||||||
},
|
},
|
||||||
@@ -59,16 +59,16 @@ module.exports = {
|
|||||||
self._watcher.on('add', (p) => {
|
self._watcher.on('add', (p) => {
|
||||||
let pInfo = self.parseUploadsRelPath(p)
|
let pInfo = self.parseUploadsRelPath(p)
|
||||||
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
|
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
|
||||||
return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true })
|
return wiki.db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true })
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return git.commitUploads(lang.t('git:uploaded', { path: p }))
|
return wiki.git.commitUploads(wiki.lang.t('git:uploaded', { path: p }))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// -> Remove upload file
|
// -> Remove upload file
|
||||||
|
|
||||||
self._watcher.on('unlink', (p) => {
|
self._watcher.on('unlink', (p) => {
|
||||||
return git.commitUploads(lang.t('git:deleted', { path: p }))
|
return wiki.git.commitUploads(wiki.lang.t('git:deleted', { path: p }))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -91,8 +91,8 @@ module.exports = {
|
|||||||
|
|
||||||
// Add folders to DB
|
// Add folders to DB
|
||||||
|
|
||||||
return db.UplFolder.remove({}).then(() => {
|
return wiki.db.UplFolder.remove({}).then(() => {
|
||||||
return db.UplFolder.insertMany(_.map(folderNames, (f) => {
|
return wiki.db.UplFolder.insertMany(_.map(folderNames, (f) => {
|
||||||
return {
|
return {
|
||||||
_id: 'f:' + f,
|
_id: 'f:' + f,
|
||||||
name: f
|
name: f
|
||||||
@@ -107,7 +107,7 @@ module.exports = {
|
|||||||
let fldPath = path.join(self._uploadsPath, fldName)
|
let fldPath = path.join(self._uploadsPath, fldName)
|
||||||
return fs.readdirAsync(fldPath).then((fList) => {
|
return fs.readdirAsync(fldPath).then((fList) => {
|
||||||
return Promise.map(fList, (f) => {
|
return Promise.map(fList, (f) => {
|
||||||
return upl.processFile(fldName, f).then((mData) => {
|
return wiki.upl.processFile(fldName, f).then((mData) => {
|
||||||
if (mData) {
|
if (mData) {
|
||||||
allFiles.push(mData)
|
allFiles.push(mData)
|
||||||
}
|
}
|
||||||
@@ -118,9 +118,9 @@ module.exports = {
|
|||||||
}, {concurrency: 1}).finally(() => {
|
}, {concurrency: 1}).finally(() => {
|
||||||
// Add files to DB
|
// Add files to DB
|
||||||
|
|
||||||
return db.UplFile.remove({}).then(() => {
|
return wiki.db.UplFile.remove({}).then(() => {
|
||||||
if (_.isArray(allFiles) && allFiles.length > 0) {
|
if (_.isArray(allFiles) && allFiles.length > 0) {
|
||||||
return db.UplFile.insertMany(allFiles)
|
return wiki.db.UplFile.insertMany(allFiles)
|
||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ module.exports = {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Watch for new changes
|
// Watch for new changes
|
||||||
|
|
||||||
return upl.watch()
|
return wiki.upl.watch()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||