mirror of
https://github.com/documize/community.git
synced 2025-08-08 15:05:28 +02:00
Compare commits
276 commits
Author | SHA1 | Date | |
---|---|---|---|
|
efb092ef8f | ||
|
3fc0a15f87 | ||
|
c841c85478 | ||
|
2dae03332b | ||
|
44b1f263cd | ||
|
982e16737e | ||
|
f641e42434 | ||
|
8895db56af | ||
|
acb59e1b43 | ||
|
f2ba294be8 | ||
|
69940cb7f1 | ||
|
6bfdda7178 | ||
|
027fdf108c | ||
|
1f12df76aa | ||
|
d811b88896 | ||
|
20fb853907 | ||
|
599c53a971 | ||
|
1f462ed4f7 | ||
|
9f122fa79b | ||
|
4210caca48 | ||
|
c62fa4612b | ||
|
510e1bd0bd | ||
|
a32510b8e6 | ||
|
589f3f581f | ||
|
20bba4cd7e | ||
|
cbf5f4be7d | ||
|
dc63639c99 | ||
|
26f435bdc9 | ||
|
a8a82963fa | ||
|
ab8582e807 | ||
|
4fa0566274 | ||
|
f4b45d2aa7 | ||
|
1abc5d3e52 | ||
|
6e463ff2f4 | ||
|
f80b3f3d10 | ||
|
6c218cf087 | ||
|
3d1c8a6c54 | ||
|
576fd5e604 | ||
|
62407a28b4 | ||
|
0adf6d5dc8 | ||
|
15f8a64c86 | ||
|
95c67acaa0 | ||
|
d8f66b5ffb | ||
|
c051e81a99 | ||
|
1d86b98949 | ||
|
0a1cc86907 | ||
|
a49869d35d | ||
|
848afd3263 | ||
|
b9cb99e3bb | ||
|
64261ffcf5 | ||
|
0030418707 | ||
|
0f91ee518e | ||
|
5de1b7a92e | ||
|
a2524f785e | ||
|
f16b9f3810 | ||
|
1c09771c33 | ||
|
13fc5b5015 | ||
|
76c777acc1 | ||
|
ea9ff78411 | ||
|
4a9dd47894 | ||
|
7565779ef1 | ||
|
c07e7b6afc | ||
|
88bdafcb1b | ||
|
5a3cb1b226 | ||
|
6ee8e6c7b4 | ||
|
599c464d2d | ||
|
ae77fa2275 | ||
|
610367aac5 | ||
|
be2c2a7a2c | ||
|
0d28b7ee79 | ||
|
aa8b473018 | ||
|
6993dc678f | ||
|
e0e3f0c141 | ||
|
4c031fe7e4 | ||
|
e4025bee42 | ||
|
876775b395 | ||
|
828c01d189 | ||
|
a69bcc0af6 | ||
|
5ec911dce2 | ||
|
ce07d4d147 | ||
|
f3ef83162e | ||
|
f1a01ec195 | ||
|
01e53c3d27 | ||
|
2cf21a7bea | ||
|
d4c606760c | ||
|
9343d77b26 | ||
|
30aa8aadb6 | ||
|
29bc2677a8 | ||
|
d9827df440 | ||
|
cfd7ebd2bf | ||
|
b510615691 | ||
|
e8641405cf | ||
|
209f1b667e | ||
|
e70019d73b | ||
|
dc26f063c8 | ||
|
68d067ef7b | ||
|
0d52f434d5 | ||
|
ce22c78dac | ||
|
f976ea36f6 | ||
|
1734963693 | ||
|
247a2b2c03 | ||
|
38a790dd04 | ||
|
b77b4abdc2 | ||
|
6b498a74c6 | ||
|
f6dd872782 | ||
|
9473ecba9a | ||
|
1a909dd046 | ||
|
607a2d5797 | ||
|
037dfc40cd | ||
|
65348eee28 | ||
|
78932fb8c7 | ||
|
6c8b10753d | ||
|
e56263564c | ||
|
22b6a4fb78 | ||
|
7e26c003d6 | ||
|
e81cbad385 | ||
|
4494ace0a2 | ||
|
23abcf1585 | ||
|
67070c3bfc | ||
|
77c767a351 | ||
|
17162ce336 | ||
|
7255eb4f56 | ||
|
df534f72fa | ||
|
f4a1350a41 | ||
|
cd15c393fe | ||
|
7f66977ac1 | ||
|
33a9cbb5b0 | ||
|
716343680a | ||
|
5db5f4d63b | ||
|
3d3d50762e | ||
|
20c9168140 | ||
|
ce9c635fb4 | ||
|
f735ae1278 | ||
|
bca7794c00 | ||
|
371706fb49 | ||
|
a236cbb01c | ||
|
93b6f26365 | ||
|
5e687f5ef4 | ||
|
97c4c927ac | ||
|
4885a1b380 | ||
|
e0805d7131 | ||
|
6d735e8579 | ||
|
073ef81e80 | ||
|
38c9a94a9c | ||
|
59dc6ea991 | ||
|
4ab48cc67d | ||
|
53297f7627 | ||
|
4ed2b3902c | ||
|
6968581e5b | ||
|
c09a116e56 | ||
|
7cf672646a | ||
|
29447a2784 | ||
|
479d03ba70 | ||
|
a7dac6911c | ||
|
08f21346c1 | ||
|
ce4f62d346 | ||
|
8a25509019 | ||
|
59c929d251 | ||
|
245c538990 | ||
|
32a9528e6d | ||
|
a15f0c8eb6 | ||
|
eb9fbd25b9 | ||
|
dbef758035 | ||
|
dea25a2b85 | ||
|
fcf38d8af9 | ||
|
ce93a5e623 | ||
|
8df1cc73b0 | ||
|
53ec7c9274 | ||
|
cfe85248ce | ||
|
30c31a1ba7 | ||
|
a97b6b22d9 | ||
|
e985c5f808 | ||
|
4b89f3b1c2 | ||
|
707dc1e052 | ||
|
88211739f0 | ||
|
6b3cdb5033 | ||
|
45f216b8a1 | ||
|
c31c130ffd | ||
|
5d5e212a6b | ||
|
8fa5569ae5 | ||
|
8976bf817b | ||
|
0c3fed2b18 | ||
|
60dfb54d54 | ||
|
c6863201b3 | ||
|
45567e274a | ||
|
dff4c6929b | ||
|
eea8db9288 | ||
|
e19c4ad18a | ||
|
989b7cd62c | ||
|
df8f650319 | ||
|
565a063231 | ||
|
cb46f34503 | ||
|
470e2d3ecf | ||
|
cddba799f8 | ||
|
05df22ed4a | ||
|
a5dfa6ee39 | ||
|
780ce2df61 | ||
|
9f28e1bff2 | ||
|
8ae94295a2 | ||
|
adb7b4d7bf | ||
|
66fcb77d8b | ||
|
972413110f | ||
|
a0a166136e | ||
|
30d12ba756 | ||
|
06bf9efcfc | ||
|
9ed8f79315 | ||
|
73e8c7a278 | ||
|
806efd7eac | ||
|
724f3c88b3 | ||
|
4a7d915ebb | ||
|
c7413da943 | ||
|
4e0218f5ea | ||
|
4fe022aa0c | ||
|
aaa8c3282d | ||
|
5e022dd0b8 | ||
|
bbca180298 | ||
|
cdc7489659 | ||
|
ab95fcc64d | ||
|
9bee58057e | ||
|
bda9719ecb | ||
|
fbd4b17c15 | ||
|
c689379f92 | ||
|
d1774b42bd | ||
|
8ac35a6b74 | ||
|
813f270a9d | ||
|
e014f5b5c1 | ||
|
2b66d0096a | ||
|
50f47f61a5 | ||
|
d26ecdc12f | ||
|
1a89201bd9 | ||
|
accf0a2c63 | ||
|
cafa3ceed0 | ||
|
2b3e9dfbc9 | ||
|
1c1ebee15a | ||
|
6ba4ca9c16 | ||
|
5aaa9f874d | ||
|
51a25adbdb | ||
|
9d025c3f71 | ||
|
a4384210d4 | ||
|
6882491201 | ||
|
d4edcb8b2c | ||
|
f117e91bcb | ||
|
5c1ad25dc9 | ||
|
8970a21b58 | ||
|
0e6f2f1f5e | ||
|
7fc74be7cd | ||
|
faeadb2bbb | ||
|
be50bf9f14 | ||
|
60ef205948 | ||
|
7ae801554d | ||
|
441efd42e9 | ||
|
a19ba46f7a | ||
|
ad361c22ba | ||
|
7954f4b976 | ||
|
2d105f2154 | ||
|
811e239baf | ||
|
c7e71173ea | ||
|
8fa8a3657c | ||
|
a64a219ce8 | ||
|
d7a484a936 | ||
|
017b19141c | ||
|
39f457e90e | ||
|
30d3e6f82e | ||
|
8c2bed283f | ||
|
a3867c617a | ||
|
28424e7e4b | ||
|
7c70274f5e | ||
|
ef5b5cdb32 | ||
|
ccd756aca0 | ||
|
444b89e425 | ||
|
3d0f17386b | ||
|
513fd9f994 | ||
|
5cef58eeba | ||
|
fad1de2e41 | ||
|
6b723568d3 | ||
|
00889f0e0e |
1402 changed files with 205253 additions and 164178 deletions
|
@ -3,4 +3,4 @@
|
||||||
bin
|
bin
|
||||||
.idea
|
.idea
|
||||||
selfcert
|
selfcert
|
||||||
gui
|
gui/dist-prod
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -18,6 +18,7 @@ _convert
|
||||||
bin/*
|
bin/*
|
||||||
dist/*
|
dist/*
|
||||||
embed/bindata/*
|
embed/bindata/*
|
||||||
|
edition/static/*
|
||||||
gui/dist/*
|
gui/dist/*
|
||||||
gui/dist-prod/*
|
gui/dist-prod/*
|
||||||
|
|
||||||
|
|
33
Dockerfile
33
Dockerfile
|
@ -1,11 +1,32 @@
|
||||||
FROM golang:1.12-alpine as builder
|
FROM node:16-alpine as frontbuilder
|
||||||
|
WORKDIR /go/src/github.com/documize/community/gui
|
||||||
|
COPY ./gui /go/src/github.com/documize/community/gui
|
||||||
|
RUN npm --network-timeout=100000 install
|
||||||
|
RUN npm run build -- --environment=production --output-path dist-prod --suppress-sizes true
|
||||||
|
|
||||||
|
FROM golang:1.21-alpine as builder
|
||||||
|
WORKDIR /go/src/github.com/documize/community
|
||||||
COPY . /go/src/github.com/documize/community
|
COPY . /go/src/github.com/documize/community
|
||||||
RUN cd /go/src/github.com/documize/community
|
COPY --from=frontbuilder /go/src/github.com/documize/community/gui/dist-prod/assets /go/src/github.com/documize/community/edition/static/public/assets
|
||||||
RUN env GOOS=linux GOARCH=amd64 GODEBUG=tls13=1 go build -o bin/documize-community-linux-amd64 ./edition/community.go
|
COPY --from=frontbuilder /go/src/github.com/documize/community/gui/dist-prod/codemirror /go/src/github.com/documize/community/edition/static/public/codemirror
|
||||||
|
COPY --from=frontbuilder /go/src/github.com/documize/community/gui/dist-prod/prism /go/src/github.com/documize/community/edition/static/public/prism
|
||||||
|
COPY --from=frontbuilder /go/src/github.com/documize/community/gui/dist-prod/sections /go/src/github.com/documize/community/edition/static/public/sections
|
||||||
|
COPY --from=frontbuilder /go/src/github.com/documize/community/gui/dist-prod/tinymce /go/src/github.com/documize/community/edition/static/public/tinymce
|
||||||
|
COPY --from=frontbuilder /go/src/github.com/documize/community/gui/dist-prod/pdfjs /go/src/github.com/documize/community/edition/static/public/pdfjs
|
||||||
|
COPY --from=frontbuilder /go/src/github.com/documize/community/gui/dist-prod/i18n /go/src/github.com/documize/community/edition/static/public/i18n
|
||||||
|
COPY --from=frontbuilder /go/src/github.com/documize/community/gui/dist-prod/*.* /go/src/github.com/documize/community/edition/static/
|
||||||
|
COPY --from=frontbuilder /go/src/github.com/documize/community/gui/dist-prod/i18n/*.json /go/src/github.com/documize/community/edition/static/i18n/
|
||||||
|
COPY domain/mail/*.html /go/src/github.com/documize/community/edition/static/mail/
|
||||||
|
COPY core/database/templates/*.html /go/src/github.com/documize/community/edition/static/
|
||||||
|
COPY core/database/scripts/mysql/*.sql /go/src/github.com/documize/community/edition/static/scripts/mysql/
|
||||||
|
COPY core/database/scripts/postgresql/*.sql /go/src/github.com/documize/community/edition/static/scripts/postgresql/
|
||||||
|
COPY core/database/scripts/sqlserver/*.sql /go/src/github.com/documize/community/edition/static/scripts/sqlserver/
|
||||||
|
COPY domain/onboard/*.json /go/src/github.com/documize/community/edition/static/onboard/
|
||||||
|
RUN env GODEBUG=tls13=1 go build -mod=vendor -o bin/documize-community ./edition/community.go
|
||||||
|
|
||||||
# build release image
|
# build release image
|
||||||
FROM alpine:3.10
|
FROM alpine:3.16
|
||||||
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
|
RUN apk add --no-cache ca-certificates
|
||||||
COPY --from=builder /go/src/github.com/documize/community/bin/documize-community-linux-amd64 /documize
|
COPY --from=builder /go/src/github.com/documize/community/bin/documize-community /documize
|
||||||
EXPOSE 5001
|
EXPOSE 5001
|
||||||
ENTRYPOINT [ "/documize" ]
|
ENTRYPOINT [ "/documize" ]
|
||||||
|
|
79
README.md
79
README.md
|
@ -1,57 +1,71 @@
|
||||||
Documize is an open source modern, lightweight and comprehensive alternative to Confluence.
|
Documize Community is an open source, modern, self-hosted, enterprise-grade knowledge management solution.
|
||||||
|
|
||||||
It's built with Golang + EmberJS and compiled down to a single executable binary for Linux, Windows and macOS.
|
|
||||||
|
|
||||||
All you need to provide is PostgreSQL, Microsoft SQL Server or any MySQL variant.
|
|
||||||
|
|
||||||
- Built for technical and non-technical users
|
- Built for technical and non-technical users
|
||||||
- Designed to unify both customer-facing and internal documentation
|
- Designed to unify both customer-facing and internal documentation
|
||||||
- Organization through labels, spaces and categories
|
- Organization through labels, spaces and categories
|
||||||
- No fee-charging marketplace
|
|
||||||
|
|
||||||

|
It's built with Golang + EmberJS and compiled down to a single executable binary that is available for Linux, Windows and Mac.
|
||||||
|
|
||||||
|
All you need to provide is your database -- PostgreSQL, Microsoft SQL Server or any MySQL variant.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Latest Release
|
## Latest Release
|
||||||
|
|
||||||
[Community Edition: v3.3.0](https://github.com/documize/community/releases)
|
[Community edition: v5.13.0](https://github.com/documize/community/releases)
|
||||||
|
|
||||||
[Enterprise Edition: v3.3.0](https://www.documize.com/downloads)
|
[Community+ edition: v5.13.0](https://www.documize.com/community/get-started)
|
||||||
|
|
||||||
> *We provide frequent product updates for both cloud and self-hosted customers.*
|
The Community+ edition is the "enterprise" offering with advanced capabilities and customer support:
|
||||||
>
|
|
||||||
> **Harvey Kandola, CEO/Founder @ Documize**
|
- content approval workflows
|
||||||
|
- content organization by label, space and category
|
||||||
|
- content version management
|
||||||
|
- content lifecycle management
|
||||||
|
- content feedback capture
|
||||||
|
- content PDF export
|
||||||
|
- analytics and reporting
|
||||||
|
- activity streams
|
||||||
|
- audit logs
|
||||||
|
- actions assignments
|
||||||
|
- product support
|
||||||
|
|
||||||
|
The Community+ edition is [free](https://www.documize.com/community/get-started) for the first five users -- thereafter pricing starts at just $900 annually for 100 users.
|
||||||
|
|
||||||
## OS Support
|
## OS Support
|
||||||
|
|
||||||
- Linux
|
- Linux
|
||||||
- Windows
|
- Windows
|
||||||
- macOS
|
- macOS
|
||||||
- Raspberry Pi (using the ARM builds)
|
- Raspberry Pi (ARM build)
|
||||||
|
|
||||||
|
Support for AMD and ARM 64 bit architectures.
|
||||||
|
|
||||||
## Database Support
|
## Database Support
|
||||||
|
|
||||||
For all database types, Full-Text Search support (FTS) is mandatory.
|
For all database types, Full-Text Search (FTS) support is mandatory.
|
||||||
|
|
||||||
- PostgreSQL (v9.6+)
|
- PostgreSQL (v9.6+)
|
||||||
- Microsoft SQL Server (2016+ with FTS)
|
- Microsoft SQL Server (2016+ with FTS)
|
||||||
|
- Microsoft SQL Azure (v12+)
|
||||||
- MySQL (v5.7.10+ and v8.0.12+)
|
- MySQL (v5.7.10+ and v8.0.12+)
|
||||||
- Percona (v5.7.16-10+)
|
- Percona (v5.7.16-10+)
|
||||||
- MariaDB (10.3.0+)
|
- MariaDB (10.3.0+)
|
||||||
|
|
||||||
## Browser Support
|
## Browser Support
|
||||||
|
|
||||||
- Chrome
|
|
||||||
- Firefox
|
- Firefox
|
||||||
|
- Chrome
|
||||||
- Safari
|
- Safari
|
||||||
|
- Microsoft Edge
|
||||||
- Brave
|
- Brave
|
||||||
- Vivaldi
|
- Vivaldi
|
||||||
- Opera
|
- Opera
|
||||||
- Microsoft Edge (v42+)
|
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
- Go (v1.13.0)
|
- Go (v1.23.4)
|
||||||
- EmberJS (v3.12.0)
|
- Ember JS (v3.12.0)
|
||||||
|
|
||||||
## Authentication Options
|
## Authentication Options
|
||||||
|
|
||||||
|
@ -64,10 +78,33 @@ Besides email/password login, you can also authenticate via:
|
||||||
|
|
||||||
When using LDAP/Active Directory, you can enable dual-authentication with email/password.
|
When using LDAP/Active Directory, you can enable dual-authentication with email/password.
|
||||||
|
|
||||||
|
## Localization
|
||||||
|
|
||||||
|
Languages supported out-of-the-box:
|
||||||
|
|
||||||
|
- English
|
||||||
|
- German
|
||||||
|
- French
|
||||||
|
- Chinese (中文)
|
||||||
|
- Portuguese (Brazil) (Português - Brasil)
|
||||||
|
- Japanese (日本語)
|
||||||
|
- Italian
|
||||||
|
- Spanish Argentinian
|
||||||
|
|
||||||
|
PR's welcome for additional languages.
|
||||||
|
|
||||||
|
## Product/Technical Support
|
||||||
|
|
||||||
|
For both Community and Community+ editions, please contact our help desk for product help, suggestions and other enquiries.
|
||||||
|
|
||||||
|
<support@documize.com>
|
||||||
|
|
||||||
|
We aim to respond within two working days.
|
||||||
|
|
||||||
## The Legal Bit
|
## The Legal Bit
|
||||||
|
|
||||||
<https://documize.com>
|
<https://www.documize.com>
|
||||||
|
|
||||||
This software (Documize Community Edition) is licensed under GNU AGPL v3 <http://www.gnu.org/licenses/agpl-3.0.en.html>. You can operate outside the AGPL restrictions by purchasing Documize Enterprise Edition and obtaining a commercial license by contacting <sales@documize.com>. Documize® is a registered trade mark of Documize Inc.
|
This software (Documize Community edition) is licensed under GNU AGPL v3 <http://www.gnu.org/licenses/agpl-3.0.en.html>.
|
||||||
|
|
||||||
Documize uses other open source components and we acknowledge them in [NOTICES](NOTICES.md)
|
Documize Community uses other open source components and we acknowledge them in [NOTICES](NOTICES.md)
|
||||||
|
|
85
build.bat
85
build.bat
|
@ -7,64 +7,61 @@ echo "Building Ember assets..."
|
||||||
cd gui
|
cd gui
|
||||||
call ember b -o dist-prod/ --environment=production
|
call ember b -o dist-prod/ --environment=production
|
||||||
::Call allows the rest of the file to run
|
::Call allows the rest of the file to run
|
||||||
|
|
||||||
echo "Copying Ember assets..."
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
rd /s /q embed\bindata\public
|
rd /s /q edition\static\public
|
||||||
mkdir embed\bindata\public
|
mkdir edition\static\public
|
||||||
echo "Copying Ember assets folder"
|
echo "Copying Ember assets folder"
|
||||||
robocopy /e /NFL /NDL /NJH gui\dist-prod\assets embed\bindata\public\assets
|
robocopy /e /NFL /NDL /NJH gui\dist-prod\assets edition\static\public\assets
|
||||||
echo "Copying Ember codemirror folder"
|
echo "Copying Ember codemirror folder"
|
||||||
robocopy /e /NFL /NDL /NJH gui\dist-prod\codemirror embed\bindata\public\codemirror
|
robocopy /e /NFL /NDL /NJH gui\dist-prod\codemirror edition\static\public\codemirror
|
||||||
echo "Copying Ember prism folder"
|
echo "Copying Ember prism folder"
|
||||||
robocopy /e /NFL /NDL /NJH gui\dist-prod\prism embed\bindata\public\prism
|
robocopy /e /NFL /NDL /NJH gui\dist-prod\prism edition\static\public\prism
|
||||||
echo "Copying Ember tinymce folder"
|
echo "Copying Ember tinymce folder"
|
||||||
robocopy /e /NFL /NDL /NJH gui\dist-prod\tinymce embed\bindata\public\tinymce
|
robocopy /e /NFL /NDL /NJH gui\dist-prod\tinymce edition\static\public\tinymce
|
||||||
echo "Copying Ember pdfjs folder"
|
echo "Copying Ember pdfjs folder"
|
||||||
robocopy /e /NFL /NDL /NJH gui\dist-prod\pdfjs embed\bindata\public\pdfjs
|
robocopy /e /NFL /NDL /NJH gui\dist-prod\pdfjs edition\static\public\pdfjs
|
||||||
echo "Copying Ember sections folder"
|
echo "Copying Ember sections folder"
|
||||||
robocopy /e /NFL /NDL /NJH gui\dist-prod\sections embed\bindata\public\sections
|
robocopy /e /NFL /NDL /NJH gui\dist-prod\sections edition\static\public\sections
|
||||||
|
echo "Copying i18n folder"
|
||||||
|
robocopy /e /NFL /NDL /NJH gui\dist-prod\i18n edition\static\public\i18n
|
||||||
|
|
||||||
copy gui\dist-prod\*.* embed\bindata
|
echo "Copying static files"
|
||||||
copy gui\dist-prod\favicon.ico embed\bindata\public
|
copy gui\dist-prod\*.* edition\static
|
||||||
copy gui\dist-prod\manifest.json embed\bindata\public
|
|
||||||
|
|
||||||
rd /s /q embed\bindata\mail
|
echo "Copying favicon.ico"
|
||||||
mkdir embed\bindata\mail
|
copy gui\dist-prod\favicon.ico edition\static\public
|
||||||
copy domain\mail\*.html embed\bindata\mail
|
|
||||||
copy core\database\templates\*.html embed\bindata
|
|
||||||
|
|
||||||
rd /s /q embed\bindata\scripts
|
echo "Copying manifest.json"
|
||||||
mkdir embed\bindata\scripts
|
copy gui\dist-prod\manifest.json edition\static\public
|
||||||
mkdir embed\bindata\scripts\mysql
|
|
||||||
mkdir embed\bindata\scripts\postgresql
|
echo "Copying mail templates"
|
||||||
mkdir embed\bindata\scripts\sqlserver
|
rd /s /q edition\static\mail
|
||||||
|
mkdir edition\static\mail
|
||||||
|
copy domain\mail\*.html edition\static\mail
|
||||||
|
|
||||||
|
echo "Copying database templates"
|
||||||
|
copy core\database\templates\*.html edition\static
|
||||||
|
|
||||||
|
rd /s /q edition\static\i18n
|
||||||
|
mkdir edition\static\i18n
|
||||||
|
robocopy /e /NFL /NDL /NJH gui\dist-prod\i18n edition\static\i18n *.json
|
||||||
|
|
||||||
|
rd /s /q edition\static\scripts
|
||||||
|
mkdir edition\static\scripts
|
||||||
|
mkdir edition\static\scripts\mysql
|
||||||
|
mkdir edition\static\scripts\postgresql
|
||||||
|
mkdir edition\static\scripts\sqlserver
|
||||||
|
|
||||||
echo "Copying database scripts folder"
|
echo "Copying database scripts folder"
|
||||||
robocopy /e /NFL /NDL /NJH core\database\scripts\mysql embed\bindata\scripts\mysql
|
robocopy /e /NFL /NDL /NJH core\database\scripts\mysql edition\static\scripts\mysql
|
||||||
robocopy /e /NFL /NDL /NJH core\database\scripts\postgresql embed\bindata\scripts\postgresql
|
robocopy /e /NFL /NDL /NJH core\database\scripts\postgresql edition\static\scripts\postgresql
|
||||||
robocopy /e /NFL /NDL /NJH core\database\scripts\sqlserver embed\bindata\scripts\sqlserver
|
robocopy /e /NFL /NDL /NJH core\database\scripts\sqlserver edition\static\scripts\sqlserver
|
||||||
|
|
||||||
rd /s /q embed\bindata\onboard
|
rd /s /q edition\static\onboard
|
||||||
mkdir embed\bindata\onboard
|
mkdir edition\static\onboard
|
||||||
robocopy /e /NFL /NDL /NJH domain\onboard\*.json embed\bindata\onboard
|
robocopy /e /NFL /NDL /NJH domain\onboard edition\static\onboard *.json
|
||||||
|
|
||||||
echo "Generating in-memory static assets..."
|
|
||||||
go get -u github.com/jteeuwen/go-bindata/...
|
|
||||||
go get -u github.com/elazarl/go-bindata-assetfs/...
|
|
||||||
cd embed
|
|
||||||
go generate
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
echo "Compiling Windows"
|
echo "Compiling Windows"
|
||||||
set GOOS=windows
|
set GOOS=windows
|
||||||
go build -gcflags="all=-trimpath=$GOPATH" -o bin/documize-community-windows-amd64.exe edition/community.go
|
go build -mod=vendor -trimpath -gcflags="all=-trimpath=$GOPATH" -o bin/documize-community-windows-amd64.exe edition/community.go
|
||||||
|
|
||||||
echo "Compiling Linux"
|
|
||||||
set GOOS=linux
|
|
||||||
go build -gcflags="all=-trimpath=$GOPATH" -o bin/documize-community-linux-amd64 edition/community.go
|
|
||||||
|
|
||||||
echo "Compiling Darwin"
|
|
||||||
set GOOS=darwin
|
|
||||||
go build -gcflags="all=-trimpath=$GOPATH" -o bin/documize-community-darwin-amd64 edition/community.go
|
|
||||||
|
|
91
build.sh
91
build.sh
|
@ -8,62 +8,65 @@ echo "Build process started $NOW"
|
||||||
|
|
||||||
echo "Building Ember assets..."
|
echo "Building Ember assets..."
|
||||||
cd gui
|
cd gui
|
||||||
|
# export NODE_OPTIONS=--openssl-legacy-provider
|
||||||
ember build ---environment=production --output-path dist-prod --suppress-sizes true
|
ember build ---environment=production --output-path dist-prod --suppress-sizes true
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
echo "Copying Ember assets..."
|
echo "Copying Ember assets..."
|
||||||
rm -rf embed/bindata/public
|
rm -rf edition/static/public
|
||||||
mkdir -p embed/bindata/public
|
mkdir -p edition/static/public
|
||||||
cp -r gui/dist-prod/assets embed/bindata/public
|
cp -r gui/dist-prod/assets edition/static/public
|
||||||
cp -r gui/dist-prod/codemirror embed/bindata/public/codemirror
|
cp -r gui/dist-prod/codemirror edition/static/public/codemirror
|
||||||
cp -r gui/dist-prod/prism embed/bindata/public/prism
|
cp -r gui/dist-prod/prism edition/static/public/prism
|
||||||
cp -r gui/dist-prod/sections embed/bindata/public/sections
|
cp -r gui/dist-prod/sections edition/static/public/sections
|
||||||
cp -r gui/dist-prod/tinymce embed/bindata/public/tinymce
|
cp -r gui/dist-prod/tinymce edition/static/public/tinymce
|
||||||
cp -r gui/dist-prod/pdfjs embed/bindata/public/pdfjs
|
cp -r gui/dist-prod/pdfjs edition/static/public/pdfjs
|
||||||
cp gui/dist-prod/*.* embed/bindata
|
cp -r gui/dist-prod/i18n edition/static/public/i18n
|
||||||
cp gui/dist-prod/favicon.ico embed/bindata/public
|
cp gui/dist-prod/*.* edition/static
|
||||||
cp gui/dist-prod/manifest.json embed/bindata/public
|
cp gui/dist-prod/favicon.ico edition/static/public
|
||||||
|
cp gui/dist-prod/manifest.json edition/static/public
|
||||||
|
|
||||||
rm -rf embed/bindata/mail
|
rm -rf edition/static/mail
|
||||||
mkdir -p embed/bindata/mail
|
mkdir -p edition/static/mail
|
||||||
cp domain/mail/*.html embed/bindata/mail
|
cp domain/mail/*.html edition/static/mail
|
||||||
cp core/database/templates/*.html embed/bindata
|
cp core/database/templates/*.html edition/static
|
||||||
|
|
||||||
rm -rf embed/bindata/scripts
|
rm -rf edition/static/i18n
|
||||||
mkdir -p embed/bindata/scripts
|
mkdir -p edition/static/i18n
|
||||||
mkdir -p embed/bindata/scripts/mysql
|
cp -r gui/dist-prod/i18n/*.json edition/static/i18n
|
||||||
mkdir -p embed/bindata/scripts/postgresql
|
|
||||||
mkdir -p embed/bindata/scripts/sqlserver
|
|
||||||
cp -r core/database/scripts/mysql/*.sql embed/bindata/scripts/mysql
|
|
||||||
cp -r core/database/scripts/postgresql/*.sql embed/bindata/scripts/postgresql
|
|
||||||
cp -r core/database/scripts/sqlserver/*.sql embed/bindata/scripts/sqlserver
|
|
||||||
|
|
||||||
rm -rf embed/bindata/onboard
|
rm -rf edition/static/scripts
|
||||||
mkdir -p embed/bindata/onboard
|
mkdir -p edition/static/scripts
|
||||||
cp -r domain/onboard/*.json embed/bindata/onboard
|
mkdir -p edition/static/scripts/mysql
|
||||||
|
mkdir -p edition/static/scripts/postgresql
|
||||||
|
mkdir -p edition/static/scripts/sqlserver
|
||||||
|
cp -r core/database/scripts/mysql/*.sql edition/static/scripts/mysql
|
||||||
|
cp -r core/database/scripts/postgresql/*.sql edition/static/scripts/postgresql
|
||||||
|
cp -r core/database/scripts/sqlserver/*.sql edition/static/scripts/sqlserver
|
||||||
|
|
||||||
echo "Generating in-memory static assets..."
|
rm -rf edition/static/onboard
|
||||||
# go get -u github.com/jteeuwen/go-bindata/...
|
mkdir -p edition/static/onboard
|
||||||
# go get -u github.com/elazarl/go-bindata-assetfs/...
|
cp -r domain/onboard/*.json edition/static/onboard
|
||||||
cd embed
|
|
||||||
go generate
|
|
||||||
|
|
||||||
cd ..
|
echo "Compiling for macOS Intel..."
|
||||||
echo "Compiling for Linux..."
|
env GOOS=darwin GOARCH=amd64 go build -mod=vendor -trimpath -o bin/documize-community-darwin-amd64 ./edition/community.go
|
||||||
env GOOS=linux GOARCH=amd64 go build -trimpath -o bin/documize-community-linux-amd64 ./edition/community.go
|
echo "Compiling for macOS ARM..."
|
||||||
echo "Compiling for macOS..."
|
env GOOS=darwin GOARCH=arm64 go build -mod=vendor -trimpath -o bin/documize-community-darwin-arm64 ./edition/community.go
|
||||||
env GOOS=darwin GOARCH=amd64 go build -trimpath -o bin/documize-community-darwin-amd64 ./edition/community.go
|
echo "Compiling for Windows AMD..."
|
||||||
echo "Compiling for Windows..."
|
env GOOS=windows GOARCH=amd64 go build -mod=vendor -trimpath -o bin/documize-community-windows-amd64.exe ./edition/community.go
|
||||||
env GOOS=windows GOARCH=amd64 go build -trimpath -o bin/documize-community-windows-amd64.exe ./edition/community.go
|
echo "Compiling for Linux AMD..."
|
||||||
echo "Compiling for ARM..."
|
env GOOS=linux GOARCH=amd64 go build -mod=vendor -trimpath -o bin/documize-community-linux-amd64 ./edition/community.go
|
||||||
env GOOS=linux GOARCH=arm go build -trimpath -o bin/documize-community-linux-arm ./edition/community.go
|
echo "Compiling for Linux ARM..."
|
||||||
echo "Compiling for ARM64..."
|
env GOOS=linux GOARCH=arm go build -mod=vendor -trimpath -o bin/documize-community-linux-arm ./edition/community.go
|
||||||
env GOOS=linux GOARCH=arm64 go build -trimpath -o bin/documize-community-linux-arm64 ./edition/community.go
|
echo "Compiling for Linux ARM64..."
|
||||||
|
env GOOS=linux GOARCH=arm64 go build -mod=vendor -trimpath -o bin/documize-community-linux-arm64 ./edition/community.go
|
||||||
|
echo "Compiling for FreeBSD ARM64..."
|
||||||
|
env GOOS=freebsd GOARCH=arm64 go build -mod=vendor -trimpath -o bin/documize-community-freebsd-arm64 ./edition/community.go
|
||||||
|
echo "Compiling for FreeBSD AMD64..."
|
||||||
|
env GOOS=freebsd GOARCH=amd64 go build -mod=vendor -trimpath -o bin/documize-community-freebsd-amd64 ./edition/community.go
|
||||||
|
|
||||||
echo "Finished."
|
echo "Finished."
|
||||||
|
|
||||||
# CGO_ENABLED=0 GOOS=linux go build -a -ldflags="-s -w" -installsuffix cgo
|
# CGO_ENABLED=0 GOOS=linux go build -a -ldflags="-s -w" -installsuffix cgo
|
||||||
# go build -ldflags '-d -s -w' -a -tags netgo -installsuffix netgo test.go
|
# go build -ldflags '-d -s -w' -a -tags netgo -installsuffix netgo test.go
|
||||||
# ldd test
|
# ldd test
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/55664630/how-do-i-migrate-from-dep-to-go-modules
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"context"
|
||||||
api "github.com/documize/community/core/convapi"
|
api "github.com/documize/community/core/convapi"
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Msword type provides a peg to hang the Convert method on.
|
// Msword type provides a peg to hang the Convert method on.
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"github.com/documize/community/core/api/plugins"
|
"github.com/documize/community/core/api/plugins"
|
||||||
api "github.com/documize/community/core/convapi"
|
api "github.com/documize/community/core/convapi"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Convert provides the entry-point into the document conversion process.
|
// Convert provides the entry-point into the document conversion process.
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
|
|
||||||
api "github.com/documize/community/core/convapi"
|
api "github.com/documize/community/core/convapi"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Convert provides the standard interface for conversion of a ".documizeapi" json document.
|
// Convert provides the standard interface for conversion of a ".documizeapi" json document.
|
||||||
|
|
|
@ -16,9 +16,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"context"
|
||||||
api "github.com/documize/community/core/convapi"
|
api "github.com/documize/community/core/convapi"
|
||||||
"github.com/documize/community/core/stringutil"
|
"github.com/documize/community/core/stringutil"
|
||||||
"golang.org/x/net/context"
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
"golang.org/x/net/html/atom"
|
"golang.org/x/net/html/atom"
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
|
|
||||||
"github.com/documize/blackfriday"
|
"github.com/documize/blackfriday"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Convert provides the standard interface for conversion of a Markdown document.
|
// Convert provides the standard interface for conversion of a Markdown document.
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize Community Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
package plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSetup(t *testing.T) {
|
|
||||||
err := LibSetup()
|
|
||||||
if err == nil {
|
|
||||||
//t.Error("should error on non-existent config file")
|
|
||||||
//t.Fail()
|
|
||||||
}
|
|
||||||
ssc, err := Lib.Actions("Convert")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(Elliott) review for empty database
|
|
||||||
//if len(ssc) > 3 {
|
|
||||||
// t.Errorf("extra convert formats:%v", ssc)
|
|
||||||
//}
|
|
||||||
|
|
||||||
/* this code leaves plugins still running */
|
|
||||||
err = os.Chdir("../../..")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = LibSetup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
ssc, err = Lib.Actions("Convert")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
if len(ssc) == 0 {
|
|
||||||
t.Error("no extra convert formats (defined)")
|
|
||||||
}
|
|
||||||
err = os.Chdir("documize/api/plugins")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Lib.KillSubProcs()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
70
core/asset/assets.go
Normal file
70
core/asset/assets.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package asset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPublicFileSystem
|
||||||
|
func GetPublicFileSystem(e embed.FS) (hfs http.FileSystem, err error) {
|
||||||
|
fsys, err := fs.Sub(e, "static/public")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed GetPublicFileSystem")
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.FS(fsys), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchStatic loads static asset from embed file system.
|
||||||
|
func FetchStatic(e embed.FS, filename string) (content, contentType string, err error) {
|
||||||
|
data, err := e.ReadFile("static/" + filename)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType = mime.TypeByExtension(filepath.Ext(filename))
|
||||||
|
content = string(data)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchStaticDir returns filenames within specified directory
|
||||||
|
func FetchStaticDir(fs embed.FS, directory string) (files []string, err error) {
|
||||||
|
entries, err := fs.ReadDir("static/" + directory)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range entries {
|
||||||
|
if !entries[i].Type().IsDir() {
|
||||||
|
files = append(files, entries[i].Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStatic loads static asset from embed file system and writes to HTTP.
|
||||||
|
func WriteStatic(fs embed.FS, prefix, requestedPath string, w http.ResponseWriter) error {
|
||||||
|
f, err := fs.Open(path.Join(prefix, requestedPath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
stat, _ := f.Stat()
|
||||||
|
if stat.IsDir() {
|
||||||
|
return errors.New("cannot write static file")
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := mime.TypeByExtension(filepath.Ext(requestedPath))
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
_, err = io.Copy(w, f)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -82,7 +82,7 @@ func Check(runtime *env.Runtime) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(flds) == 0 {
|
if len(flds) <= 5 {
|
||||||
runtime.Log.Info("Database: starting setup mode for empty database")
|
runtime.Log.Info("Database: starting setup mode for empty database")
|
||||||
runtime.Flags.SiteMode = env.SiteModeSetup
|
runtime.Flags.SiteMode = env.SiteModeSetup
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -26,7 +26,7 @@ func InstallUpgrade(runtime *env.Runtime, existingDB bool) (err error) {
|
||||||
// amLeader := false
|
// amLeader := false
|
||||||
|
|
||||||
// Get all SQL scripts.
|
// Get all SQL scripts.
|
||||||
scripts, err := LoadScripts()
|
scripts, err := LoadScripts(runtime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.Log.Error("Database: unable to load scripts", err)
|
runtime.Log.Error("Database: unable to load scripts", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize Community Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
package database
|
|
||||||
|
|
||||||
// import (
|
|
||||||
// "crypto/rand"
|
|
||||||
// "time"
|
|
||||||
|
|
||||||
// "github.com/documize/community/core/env"
|
|
||||||
// "github.com/jmoiron/sqlx"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// // Lock will try to lock the database instance to the running process.
|
|
||||||
// // Uses a "random" delay as a por man's database cluster-aware process.
|
|
||||||
// // We skip delay if there are no scripts to process.
|
|
||||||
// func Lock(runtime *env.Runtime, scriptsToProcess int) (bool, error) {
|
|
||||||
// // Wait for random period of time.
|
|
||||||
// b := make([]byte, 2)
|
|
||||||
// _, err := rand.Read(b)
|
|
||||||
// if err != nil {
|
|
||||||
// return false, err
|
|
||||||
// }
|
|
||||||
// wait := ((time.Duration(b[0]) << 8) | time.Duration(b[1])) * time.Millisecond / 10 // up to 6.5 secs wait
|
|
||||||
|
|
||||||
// // Why delay if nothing to process?
|
|
||||||
// if scriptsToProcess > 0 {
|
|
||||||
// time.Sleep(wait)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Start transaction fotr lock process.
|
|
||||||
// tx, err := runtime.Db.Beginx()
|
|
||||||
// if err != nil {
|
|
||||||
// runtime.Log.Error("Database: unable to start transaction", err)
|
|
||||||
// return false, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Lock the database.
|
|
||||||
// _, err = tx.Exec(runtime.StoreProvider.QueryStartLock())
|
|
||||||
// if err != nil {
|
|
||||||
// runtime.Log.Error("Database: unable to lock tables", err)
|
|
||||||
// return false, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Unlock the database at the end of this function.
|
|
||||||
// defer func() {
|
|
||||||
// _, err = tx.Exec(runtime.StoreProvider.QueryFinishLock())
|
|
||||||
// if err != nil {
|
|
||||||
// runtime.Log.Error("Database: unable to unlock tables", err)
|
|
||||||
// }
|
|
||||||
// tx.Commit()
|
|
||||||
// }()
|
|
||||||
|
|
||||||
// // Try to record this process as leader of database migration process.
|
|
||||||
// _, err = tx.Exec(runtime.StoreProvider.QueryInsertProcessID())
|
|
||||||
// if err != nil {
|
|
||||||
// runtime.Log.Info("Database: marked as slave process awaiting upgrade")
|
|
||||||
// return false, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // We are the leader!
|
|
||||||
// runtime.Log.Info("Database: marked as database upgrade process leader")
|
|
||||||
// return true, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Unlock completes process that was started with Lock().
|
|
||||||
// func Unlock(runtime *env.Runtime, tx *sqlx.Tx, err error, amLeader bool) error {
|
|
||||||
// if amLeader {
|
|
||||||
// defer func() {
|
|
||||||
// doUnlock(runtime)
|
|
||||||
// }()
|
|
||||||
|
|
||||||
// if tx != nil {
|
|
||||||
// if err == nil {
|
|
||||||
// tx.Commit()
|
|
||||||
// runtime.Log.Info("Database: is ready")
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// tx.Rollback()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// runtime.Log.Error("Database: install/upgrade failed", err)
|
|
||||||
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return nil // not the leader, so ignore errors
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Helper method for defer function called from Unlock().
|
|
||||||
// func doUnlock(runtime *env.Runtime) error {
|
|
||||||
// tx, err := runtime.Db.Beginx()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// _, err = tx.Exec(runtime.StoreProvider.QueryDeleteProcessID())
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return tx.Commit()
|
|
||||||
// }
|
|
|
@ -12,11 +12,12 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/asset"
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
"github.com/documize/community/server/web"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scripts holds all .SQL files for all supported database providers.
|
// Scripts holds all .SQL files for all supported database providers.
|
||||||
|
@ -33,21 +34,19 @@ type Script struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadScripts returns .SQL scripts for supported database providers.
|
// LoadScripts returns .SQL scripts for supported database providers.
|
||||||
func LoadScripts() (s Scripts, err error) {
|
func LoadScripts(runtime *env.Runtime) (s Scripts, err error) {
|
||||||
assetDir := "bindata/scripts"
|
|
||||||
|
|
||||||
// MySQL
|
// MySQL
|
||||||
s.MySQL, err = loadFiles(fmt.Sprintf("%s/mysql", assetDir))
|
s.MySQL, err = loadFiles(runtime.Assets, "scripts/mysql")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// PostgreSQL
|
// PostgreSQL
|
||||||
s.PostgreSQL, err = loadFiles(fmt.Sprintf("%s/postgresql", assetDir))
|
s.PostgreSQL, err = loadFiles(runtime.Assets, "scripts/postgresql")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// PostgreSQL
|
// PostgreSQL
|
||||||
s.SQLServer, err = loadFiles(fmt.Sprintf("%s/sqlserver", assetDir))
|
s.SQLServer, err = loadFiles(runtime.Assets, "scripts/sqlserver")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -70,20 +69,22 @@ func SpecificScripts(runtime *env.Runtime, all Scripts) (s []Script) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadFiles returns all SQL scripts in specified folder as [][]byte.
|
// loadFiles returns all SQL scripts in specified folder as [][]byte.
|
||||||
func loadFiles(path string) (b []Script, err error) {
|
func loadFiles(fs embed.FS, path string) (b []Script, err error) {
|
||||||
buf := []byte{}
|
scripts, err := asset.FetchStaticDir(fs, path)
|
||||||
scripts, err := web.AssetDir(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(scripts)
|
sort.Strings(scripts)
|
||||||
for _, file := range scripts {
|
|
||||||
buf, err = web.Asset(fmt.Sprintf("%s/%s", path, file))
|
for i := range scripts {
|
||||||
|
filename := scripts[i]
|
||||||
|
sqlfile, _, err := asset.FetchStatic(fs, fmt.Sprintf("%s/%s", path, filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b = append(b, Script{Version: extractVersionNumber(file), Script: buf})
|
b = append(b, Script{Version: extractVersionNumber(filename), Script: []byte(sqlfile)})
|
||||||
}
|
}
|
||||||
|
|
||||||
return b, nil
|
return b, nil
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
/* community edition */
|
/* community edition */
|
||||||
ALTER TABLE organization ADD COLUMN `service` VARCHAR(100) NOT NULL DEFAULT 'https://api.documize.com' AFTER `domain`;
|
ALTER TABLE organization ADD COLUMN `service` VARCHAR(100) NOT NULL DEFAULT '' AFTER `domain`;
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ ALTER TABLE dmz_org
|
||||||
CHANGE `title` `c_title` VARCHAR(500) NOT NULL,
|
CHANGE `title` `c_title` VARCHAR(500) NOT NULL,
|
||||||
CHANGE `message` `c_message` VARCHAR(500) NOT NULL,
|
CHANGE `message` `c_message` VARCHAR(500) NOT NULL,
|
||||||
CHANGE `domain` `c_domain` VARCHAR(200) NOT NULL DEFAULT '',
|
CHANGE `domain` `c_domain` VARCHAR(200) NOT NULL DEFAULT '',
|
||||||
CHANGE `service` `c_service` VARCHAR(200) NOT NULL DEFAULT 'https://api.documize.com',
|
CHANGE `service` `c_service` VARCHAR(200) NOT NULL DEFAULT '',
|
||||||
CHANGE `email` `c_email` VARCHAR(500) NOT NULL DEFAULT '',
|
CHANGE `email` `c_email` VARCHAR(500) NOT NULL DEFAULT '',
|
||||||
CHANGE `allowanonymousaccess` `c_anonaccess` BOOL NOT NULL DEFAULT 0,
|
CHANGE `allowanonymousaccess` `c_anonaccess` BOOL NOT NULL DEFAULT 0,
|
||||||
CHANGE `authprovider` `c_authprovider` CHAR(20) NOT NULL DEFAULT 'documize',
|
CHANGE `authprovider` `c_authprovider` CHAR(20) NOT NULL DEFAULT 'documize',
|
||||||
|
|
7
core/database/scripts/mysql/db_00032.sql
Normal file
7
core/database/scripts/mysql/db_00032.sql
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/* Community Edition */
|
||||||
|
|
||||||
|
-- Increase column sizes to support rich text data entry
|
||||||
|
ALTER TABLE dmz_org MODIFY `c_message` VARCHAR(800) NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE dmz_space MODIFY `c_desc` VARCHAR(800) NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE dmz_category MODIFY `c_name` VARCHAR(200) NOT NULL DEFAULT '';
|
||||||
|
ALTER TABLE dmz_category ADD COLUMN `c_default` BOOL NOT NULL DEFAULT 0 AFTER `c_name`;
|
4
core/database/scripts/mysql/db_00033.sql
Normal file
4
core/database/scripts/mysql/db_00033.sql
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/* Community Edition */
|
||||||
|
|
||||||
|
-- Allow for pinned documents per space.
|
||||||
|
ALTER TABLE dmz_doc ADD COLUMN `c_seq` INT NOT NULL DEFAULT 99999 AFTER `c_versionorder`;
|
5
core/database/scripts/mysql/db_00034.sql
Normal file
5
core/database/scripts/mysql/db_00034.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* Community Edition */
|
||||||
|
|
||||||
|
-- Local aware.
|
||||||
|
ALTER TABLE dmz_org ADD COLUMN `c_locale` VARCHAR(20) NOT NULL DEFAULT 'en-US';
|
||||||
|
ALTER TABLE dmz_user ADD COLUMN `c_locale` VARCHAR(20) NOT NULL DEFAULT 'en-US';
|
|
@ -228,7 +228,7 @@ CREATE TABLE dmz_org (
|
||||||
c_title varchar(500) COLLATE ucs_basic NOT NULL,
|
c_title varchar(500) COLLATE ucs_basic NOT NULL,
|
||||||
c_message varchar(500) COLLATE ucs_basic NOT NULL,
|
c_message varchar(500) COLLATE ucs_basic NOT NULL,
|
||||||
c_domain varchar(200) COLLATE ucs_basic NOT NULL DEFAULT '',
|
c_domain varchar(200) COLLATE ucs_basic NOT NULL DEFAULT '',
|
||||||
c_service varchar(200) COLLATE ucs_basic NOT NULL DEFAULT 'https://api.documize.com',
|
c_service varchar(200) COLLATE ucs_basic NOT NULL DEFAULT '',
|
||||||
c_email varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
|
c_email varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
|
||||||
c_anonaccess bool NOT NULL DEFAULT '0',
|
c_anonaccess bool NOT NULL DEFAULT '0',
|
||||||
c_authprovider varchar(20) COLLATE ucs_basic NOT NULL DEFAULT 'documize',
|
c_authprovider varchar(20) COLLATE ucs_basic NOT NULL DEFAULT 'documize',
|
||||||
|
|
7
core/database/scripts/postgresql/db_00008.sql
Normal file
7
core/database/scripts/postgresql/db_00008.sql
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/* Community Edition */
|
||||||
|
|
||||||
|
-- Increase column sizes to support rich text data entry
|
||||||
|
ALTER TABLE dmz_org ALTER COLUMN c_message TYPE VARCHAR(2000);
|
||||||
|
ALTER TABLE dmz_space ALTER COLUMN c_desc TYPE VARCHAR(2000);
|
||||||
|
ALTER TABLE dmz_category ALTER COLUMN c_name TYPE VARCHAR(200);
|
||||||
|
ALTER TABLE dmz_category ADD COLUMN c_default bool NOT NULL DEFAULT '0';
|
5
core/database/scripts/postgresql/db_00009.sql
Normal file
5
core/database/scripts/postgresql/db_00009.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* Community Edition */
|
||||||
|
|
||||||
|
-- Allow for pinned documents per space.
|
||||||
|
ALTER TABLE dmz_doc ADD COLUMN c_seq INT NOT NULL DEFAULT '99999';
|
||||||
|
|
5
core/database/scripts/postgresql/db_00010.sql
Normal file
5
core/database/scripts/postgresql/db_00010.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* Community Edition */
|
||||||
|
|
||||||
|
-- Local aware.
|
||||||
|
ALTER TABLE dmz_org ADD COLUMN c_locale VARCHAR(20) NOT NULL DEFAULT 'en-US';
|
||||||
|
ALTER TABLE dmz_user ADD COLUMN c_locale VARCHAR(20) NOT NULL DEFAULT 'en-US';
|
|
@ -212,7 +212,7 @@ CREATE TABLE dmz_org (
|
||||||
c_title NVARCHAR(500) COLLATE Latin1_General_CS_AS NOT NULL,
|
c_title NVARCHAR(500) COLLATE Latin1_General_CS_AS NOT NULL,
|
||||||
c_message NVARCHAR(500) COLLATE Latin1_General_CS_AS NOT NULL,
|
c_message NVARCHAR(500) COLLATE Latin1_General_CS_AS NOT NULL,
|
||||||
c_domain NVARCHAR(200) COLLATE Latin1_General_CS_AS NOT NULL DEFAULT '',
|
c_domain NVARCHAR(200) COLLATE Latin1_General_CS_AS NOT NULL DEFAULT '',
|
||||||
c_service NVARCHAR(200) COLLATE Latin1_General_CS_AS NOT NULL DEFAULT 'https://api.documize.com',
|
c_service NVARCHAR(200) COLLATE Latin1_General_CS_AS NOT NULL DEFAULT '',
|
||||||
c_email NVARCHAR(500) COLLATE Latin1_General_CS_AS NOT NULL DEFAULT '',
|
c_email NVARCHAR(500) COLLATE Latin1_General_CS_AS NOT NULL DEFAULT '',
|
||||||
c_anonaccess BIT NOT NULL DEFAULT '0',
|
c_anonaccess BIT NOT NULL DEFAULT '0',
|
||||||
c_authprovider NVARCHAR(20) COLLATE Latin1_General_CS_AS NOT NULL DEFAULT 'documize',
|
c_authprovider NVARCHAR(20) COLLATE Latin1_General_CS_AS NOT NULL DEFAULT 'documize',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* Enterprise edition */
|
/* Community edition */
|
||||||
|
|
||||||
-- Fulltext search support
|
-- Fulltext search support
|
||||||
IF EXISTS (SELECT * FROM sysfulltextcatalogs ftc WHERE ftc.name = N'dmz_search_catalog')
|
IF EXISTS (SELECT * FROM sysfulltextcatalogs ftc WHERE ftc.name = N'dmz_search_catalog')
|
||||||
|
|
7
core/database/scripts/sqlserver/db_00005.sql
Normal file
7
core/database/scripts/sqlserver/db_00005.sql
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/* Community edition */
|
||||||
|
|
||||||
|
-- Increase column sizes to support rich text data entry
|
||||||
|
ALTER TABLE dmz_org ALTER COLUMN c_message NVARCHAR(2000);
|
||||||
|
ALTER TABLE dmz_space ALTER COLUMN c_desc NVARCHAR(2000);
|
||||||
|
ALTER TABLE dmz_category ALTER COLUMN c_name NVARCHAR(200);
|
||||||
|
ALTER TABLE dmz_category ADD c_default BIT NOT NULL DEFAULT '0';
|
4
core/database/scripts/sqlserver/db_00006.sql
Normal file
4
core/database/scripts/sqlserver/db_00006.sql
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/* Community edition */
|
||||||
|
|
||||||
|
-- Allow for pinned documents per space.
|
||||||
|
ALTER TABLE dmz_doc ADD c_seq INT NOT NULL DEFAULT '99999';
|
5
core/database/scripts/sqlserver/db_00007.sql
Normal file
5
core/database/scripts/sqlserver/db_00007.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* Community edition */
|
||||||
|
|
||||||
|
-- Local aware.
|
||||||
|
ALTER TABLE dmz_org ADD c_locale NVARCHAR(20) NOT NULL DEFAULT 'en-US';
|
||||||
|
ALTER TABLE dmz_user ADD c_locale NVARCHAR(20) NOT NULL DEFAULT 'en-US';
|
4
core/database/scripts/sqlserver/db_00008.sql
Normal file
4
core/database/scripts/sqlserver/db_00008.sql
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/* Community edition */
|
||||||
|
|
||||||
|
-- Performance indexes
|
||||||
|
CREATE INDEX idx_action_5 ON dmz_action (c_orgid,c_userid,c_docid,c_actiontype,c_iscomplete,c_reftype,c_reftypeid);
|
8
core/database/scripts/sqlserver/db_00009.sql
Normal file
8
core/database/scripts/sqlserver/db_00009.sql
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/* Community edition */
|
||||||
|
|
||||||
|
-- Performance indexes
|
||||||
|
CREATE INDEX idx_action_6 ON dmz_action (c_orgid,c_reftypeid,c_reftype);
|
||||||
|
|
||||||
|
CREATE INDEX idx_action_7 ON dmz_action (c_orgid,c_refid);
|
||||||
|
|
||||||
|
CREATE INDEX idx_section_5 ON dmz_section (c_orgid,c_refid);
|
6
core/database/scripts/sqlserver/db_00010.sql
Normal file
6
core/database/scripts/sqlserver/db_00010.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/* Community edition */
|
||||||
|
|
||||||
|
-- Performance indexes
|
||||||
|
CREATE INDEX idx_action_8 ON dmz_action (c_orgid,c_docid);
|
||||||
|
|
||||||
|
CREATE INDEX idx_user_3 ON dmz_user (c_refid);
|
|
@ -102,7 +102,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="/assets/img/setup/logo.png" alt="Documize">
|
<img src="/assets/img/setup/logo.png" alt="Documize Community">
|
||||||
</div>
|
</div>
|
||||||
<div class="content clearfix">
|
<div class="content clearfix">
|
||||||
<div class="image">
|
<div class="image">
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<h1>Database Error</h1>
|
<h1>Database Error</h1>
|
||||||
<p>There seems to be a problem with the Documize database: <strong>{{.DBname}}</strong></p>
|
<p>There seems to be a problem with the Documize Community database: <strong>{{.DBname}}</strong></p>
|
||||||
<p><em>{{.Issue}}</em></p>
|
<p><em>{{.Issue}}</em></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
File diff suppressed because one or more lines are too long
2
core/env/flags.go
vendored
2
core/env/flags.go
vendored
|
@ -21,6 +21,7 @@ type Flags struct {
|
||||||
ForceHTTPPort2SSL string // (optional) HTTP that should be redirected to HTTPS
|
ForceHTTPPort2SSL string // (optional) HTTP that should be redirected to HTTPS
|
||||||
SSLCertFile string // (optional) name of SSL certificate PEM file
|
SSLCertFile string // (optional) name of SSL certificate PEM file
|
||||||
SSLKeyFile string // (optional) name of SSL key PEM file
|
SSLKeyFile string // (optional) name of SSL key PEM file
|
||||||
|
TLSVersion string // (optional) minimum TLS version for SSL connections
|
||||||
SiteMode string // (optional) if 1 then serve offline web page
|
SiteMode string // (optional) if 1 then serve offline web page
|
||||||
Location string // reserved
|
Location string // reserved
|
||||||
ConfigSource string // tells us if configuration info was obtained from command line or config file
|
ConfigSource string // tells us if configuration info was obtained from command line or config file
|
||||||
|
@ -43,6 +44,7 @@ type httpConfig struct {
|
||||||
ForceSSLPort int
|
ForceSSLPort int
|
||||||
Cert string
|
Cert string
|
||||||
Key string
|
Key string
|
||||||
|
TLSVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
type databaseConfig struct {
|
type databaseConfig struct {
|
||||||
|
|
13
core/env/parser.go
vendored
13
core/env/parser.go
vendored
|
@ -84,8 +84,13 @@ func configFile() (f Flags, ok bool) {
|
||||||
f.ForceHTTPPort2SSL = strconv.Itoa(ct.HTTP.ForceSSLPort)
|
f.ForceHTTPPort2SSL = strconv.Itoa(ct.HTTP.ForceSSLPort)
|
||||||
f.SSLCertFile = ct.HTTP.Cert
|
f.SSLCertFile = ct.HTTP.Cert
|
||||||
f.SSLKeyFile = ct.HTTP.Key
|
f.SSLKeyFile = ct.HTTP.Key
|
||||||
|
f.TLSVersion = ct.HTTP.TLSVersion
|
||||||
f.Location = strings.ToLower(ct.Install.Location)
|
f.Location = strings.ToLower(ct.Install.Location)
|
||||||
|
|
||||||
|
if len(f.TLSVersion) == 0 {
|
||||||
|
f.TLSVersion = "1.3"
|
||||||
|
}
|
||||||
|
|
||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -93,7 +98,7 @@ func configFile() (f Flags, ok bool) {
|
||||||
// commandLineEnv loads command line and OS environment variables required by the program to function.
|
// commandLineEnv loads command line and OS environment variables required by the program to function.
|
||||||
func commandLineEnv() (f Flags, ok bool) {
|
func commandLineEnv() (f Flags, ok bool) {
|
||||||
ok = true
|
ok = true
|
||||||
var dbConn, dbType, jwtKey, siteMode, port, certFile, keyFile, forcePort2SSL, location string
|
var dbConn, dbType, jwtKey, siteMode, port, certFile, keyFile, forcePort2SSL, TLSVersion, location string
|
||||||
|
|
||||||
// register(&configFile, "salt", false, "the salt string used to encode JWT tokens, if not set a random value will be generated")
|
// register(&configFile, "salt", false, "the salt string used to encode JWT tokens, if not set a random value will be generated")
|
||||||
register(&jwtKey, "salt", false, "the salt string used to encode JWT tokens, if not set a random value will be generated")
|
register(&jwtKey, "salt", false, "the salt string used to encode JWT tokens, if not set a random value will be generated")
|
||||||
|
@ -101,6 +106,7 @@ func commandLineEnv() (f Flags, ok bool) {
|
||||||
register(&keyFile, "key", false, "the key.pem file used for https")
|
register(&keyFile, "key", false, "the key.pem file used for https")
|
||||||
register(&port, "port", false, "http/https port number")
|
register(&port, "port", false, "http/https port number")
|
||||||
register(&forcePort2SSL, "forcesslport", false, "redirect given http port number to TLS")
|
register(&forcePort2SSL, "forcesslport", false, "redirect given http port number to TLS")
|
||||||
|
register(&TLSVersion, "tlsversion", false, "select minimum TLS: 1.0, 1.1, 1.2, 1.3")
|
||||||
register(&siteMode, "offline", false, "set to '1' for OFFLINE mode")
|
register(&siteMode, "offline", false, "set to '1' for OFFLINE mode")
|
||||||
register(&dbType, "dbtype", true, "specify the database provider: mysql|percona|mariadb|postgresql|sqlserver")
|
register(&dbType, "dbtype", true, "specify the database provider: mysql|percona|mariadb|postgresql|sqlserver")
|
||||||
register(&dbConn, "db", true, `'database specific connection string for example "user:password@tcp(localhost:3306)/dbname"`)
|
register(&dbConn, "db", true, `'database specific connection string for example "user:password@tcp(localhost:3306)/dbname"`)
|
||||||
|
@ -118,9 +124,14 @@ func commandLineEnv() (f Flags, ok bool) {
|
||||||
f.SiteMode = siteMode
|
f.SiteMode = siteMode
|
||||||
f.SSLCertFile = certFile
|
f.SSLCertFile = certFile
|
||||||
f.SSLKeyFile = keyFile
|
f.SSLKeyFile = keyFile
|
||||||
|
f.TLSVersion = TLSVersion
|
||||||
f.Location = strings.ToLower(location)
|
f.Location = strings.ToLower(location)
|
||||||
f.ConfigSource = "flags/environment"
|
f.ConfigSource = "flags/environment"
|
||||||
|
|
||||||
|
if len(f.TLSVersion) == 0 {
|
||||||
|
f.TLSVersion = "1.3"
|
||||||
|
}
|
||||||
|
|
||||||
return f, ok
|
return f, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
core/env/runtime.go
vendored
2
core/env/runtime.go
vendored
|
@ -15,6 +15,7 @@ package env
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"embed"
|
||||||
|
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
@ -42,6 +43,7 @@ type Runtime struct {
|
||||||
StoreProvider StoreProvider
|
StoreProvider StoreProvider
|
||||||
Log Logger
|
Log Logger
|
||||||
Product domain.Product
|
Product domain.Product
|
||||||
|
Assets embed.FS
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartTx begins database transaction with given transaction isolation level.
|
// StartTx begins database transaction with given transaction isolation level.
|
||||||
|
|
91
core/i18n/localize.go
Normal file
91
core/i18n/localize.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package i18n
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/asset"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultLocale = "en-US"
|
||||||
|
)
|
||||||
|
|
||||||
|
var localeMap map[string]map[string]string
|
||||||
|
|
||||||
|
// SupportedLocales returns array of locales.
|
||||||
|
func SupportedLocales() (locales []string) {
|
||||||
|
locales = append(locales, "en-US")
|
||||||
|
locales = append(locales, "de-DE")
|
||||||
|
locales = append(locales, "zh-CN")
|
||||||
|
locales = append(locales, "pt-BR")
|
||||||
|
locales = append(locales, "fr-FR")
|
||||||
|
locales = append(locales, "ja-JP")
|
||||||
|
locales = append(locales, "it-IT")
|
||||||
|
locales = append(locales, "es-AR")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intialize will load language files
|
||||||
|
func Initialize(e embed.FS) (err error) {
|
||||||
|
localeMap = make(map[string]map[string]string)
|
||||||
|
|
||||||
|
locales := SupportedLocales()
|
||||||
|
|
||||||
|
for i := range locales {
|
||||||
|
content, _, err := asset.FetchStatic(e, "i18n/"+locales[i]+".json")
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, fmt.Sprintf("missing locale %s", locales[i]))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload interface{}
|
||||||
|
json.Unmarshal([]byte(content), &payload)
|
||||||
|
m := payload.(map[string]interface{})
|
||||||
|
|
||||||
|
translations := make(map[string]string)
|
||||||
|
|
||||||
|
for j := range m {
|
||||||
|
translations[j] = m[j].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
localeMap[locales[i]] = translations
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Localize will returns string value for given key using specified locale).
|
||||||
|
// e.g. locale = "en-US", key = "admin_billing"
|
||||||
|
//
|
||||||
|
// Replacements are for replacing string placeholders ({1} {2} {3}) with
|
||||||
|
// replacement text.
|
||||||
|
// e.g. "This is {1} example" --> replacements[0] will replace {1}
|
||||||
|
func Localize(locale string, key string, replacements ...string) (s string) {
|
||||||
|
l, ok := localeMap[locale]
|
||||||
|
if !ok {
|
||||||
|
// fallback
|
||||||
|
l = localeMap[DefaultLocale]
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok = l[key]
|
||||||
|
if !ok {
|
||||||
|
// missing translation key is echo'ed back
|
||||||
|
s = fmt.Sprintf("!! %s !!", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// placeholders are one-based: {1} {2} {3}
|
||||||
|
// replacements array is zero-based hence the +1 below
|
||||||
|
if len(replacements) > 0 {
|
||||||
|
for i := range replacements {
|
||||||
|
s = strings.Replace(s, fmt.Sprintf("{%d}", i+1), replacements[i], 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ func CommandWithTimeout(command *exec.Cmd, timeout time.Duration) ([]byte, error
|
||||||
select {
|
select {
|
||||||
case <-time.After(timeout):
|
case <-time.After(timeout):
|
||||||
if err := command.Process.Kill(); err != nil {
|
if err := command.Process.Kill(); err != nil {
|
||||||
fmt.Errorf("failed to kill: ", err)
|
fmt.Printf("failed to kill: %s", err.Error())
|
||||||
}
|
}
|
||||||
<-done // prevent memory leak
|
<-done // prevent memory leak
|
||||||
//fmt.Println("DEBUG timeout")
|
//fmt.Println("DEBUG timeout")
|
||||||
|
|
|
@ -48,8 +48,8 @@ func WriteServerError(w http.ResponseWriter, method string, err error) {
|
||||||
|
|
||||||
// WriteError notifies HTTP client of general application error.
|
// WriteError notifies HTTP client of general application error.
|
||||||
func WriteError(w http.ResponseWriter, method string) {
|
func WriteError(w http.ResponseWriter, method string) {
|
||||||
writeStatus(w, http.StatusBadRequest)
|
writeStatus(w, http.StatusBadRequest)
|
||||||
w.Write([]byte("{Error: 'Internal server error'}"))
|
w.Write([]byte("{Error: 'Internal server error'}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteDuplicateError notifies HTTP client of duplicate data that has been rejected.
|
// WriteDuplicateError notifies HTTP client of duplicate data that has been rejected.
|
||||||
|
@ -114,3 +114,17 @@ func WriteJSON(w http.ResponseWriter, v interface{}) {
|
||||||
j, _ := json.Marshal(v)
|
j, _ := json.Marshal(v)
|
||||||
w.Write(j)
|
w.Write(j)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteText to HTTP response
|
||||||
|
func WriteText(w http.ResponseWriter, data []byte) {
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteXML to HTTP response
|
||||||
|
func WriteXML(w http.ResponseWriter, data []byte) {
|
||||||
|
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
50
core/stringutil/sanitize.go
Normal file
50
core/stringutil/sanitize.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||||
|
//
|
||||||
|
// This software (Documize Community Edition) is licensed under
|
||||||
|
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// You can operate outside the AGPL restrictions by purchasing
|
||||||
|
// Documize Enterprise Edition and obtaining a commercial license
|
||||||
|
// by contacting <sales@documize.com>.
|
||||||
|
//
|
||||||
|
// https://documize.com
|
||||||
|
|
||||||
|
package stringutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CleanDBValue returns like query minus dodgy characters.
|
||||||
|
func CleanDBValue(filter string) string {
|
||||||
|
filter = strings.ReplaceAll(filter, " ", "")
|
||||||
|
filter = strings.ReplaceAll(filter, " ' ", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "'", "")
|
||||||
|
filter = strings.ReplaceAll(filter, " ` ", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "`", "")
|
||||||
|
filter = strings.ReplaceAll(filter, " \" ", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "\"", "")
|
||||||
|
filter = strings.ReplaceAll(filter, " -- ", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "--", "")
|
||||||
|
filter = strings.ReplaceAll(filter, ";", "")
|
||||||
|
filter = strings.ReplaceAll(filter, ":", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "~", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "!", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "#", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "%", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "*", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "\\", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "/", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "union select", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "UNION SELECT", "")
|
||||||
|
filter = strings.ReplaceAll(filter, " from ", "")
|
||||||
|
filter = strings.ReplaceAll(filter, " FROM ", "")
|
||||||
|
filter = strings.ReplaceAll(filter, " OR 1=1 ", "")
|
||||||
|
filter = strings.ReplaceAll(filter, " OR 1=1 ", "")
|
||||||
|
filter = strings.ReplaceAll(filter, " = ", "")
|
||||||
|
filter = strings.ReplaceAll(filter, "=", "")
|
||||||
|
|
||||||
|
filter = strings.TrimSpace(filter)
|
||||||
|
|
||||||
|
return filter
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
# This Docker Compose file will start up Documize with PostgreSQL.
|
# This Docker Compose file will start up Documize with PostgreSQL.
|
||||||
#
|
#
|
||||||
# Use 'documize-enterprise-linux-amd64' for Enterprise Edition (default).
|
# Use 'documize-community-plus-linux-amd64' for Community+ Edition (default).
|
||||||
# Use 'documize-community-linux-amd64' for Community Edition.
|
# Use 'documize-community-linux-amd64' for Community Edition.
|
||||||
#
|
#
|
||||||
# You can move between editions anytime without any data loss
|
# You can move between editions anytime without any data loss
|
||||||
|
@ -14,7 +14,7 @@ version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:latest
|
image: postgres:12
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
|
@ -29,7 +29,7 @@ services:
|
||||||
|
|
||||||
app:
|
app:
|
||||||
image: debian:latest
|
image: debian:latest
|
||||||
command: /bin/sh -c "apt-get -qq update && apt-get -qq install -y wget && wget https://documize.s3-eu-west-1.amazonaws.com/downloads/documize-enterprise-linux-amd64 && chmod 777 ./documize-enterprise-linux-amd64 && ./documize-enterprise-linux-amd64"
|
command: /bin/sh -c "apt-get -qq update && apt-get -qq install -y wget && wget https://community-downloads.s3.us-east-2.amazonaws.com/documize-community-plus-linux-amd64 && chmod 777 ./documize-community-plus-linux-amd64 && ./documize-community-plus-linux-amd64"
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
ports:
|
ports:
|
||||||
|
|
|
@ -13,7 +13,6 @@ package activity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
|
@ -29,16 +28,16 @@ type Store struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordUserActivity logs user initiated data changes.
|
// RecordUserActivity logs user initiated data changes.
|
||||||
func (s Store) RecordUserActivity(ctx domain.RequestContext, activity activity.UserActivity) (err error) {
|
func (s Store) RecordUserActivity(ctx domain.RequestContext, activity activity.UserActivity) {
|
||||||
activity.OrgID = ctx.OrgID
|
activity.OrgID = ctx.OrgID
|
||||||
activity.UserID = ctx.UserID
|
activity.UserID = ctx.UserID
|
||||||
activity.Created = time.Now().UTC()
|
activity.Created = time.Now().UTC()
|
||||||
|
|
||||||
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_user_activity (c_orgid, c_userid, c_spaceid, c_docid, c_sectionid, c_sourcetype, c_activitytype, c_metadata, c_created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
|
_, err := ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_user_activity (c_orgid, c_userid, c_spaceid, c_docid, c_sectionid, c_sourcetype, c_activitytype, c_metadata, c_created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
|
||||||
activity.OrgID, activity.UserID, activity.SpaceID, activity.DocumentID, activity.SectionID, activity.SourceType, activity.ActivityType, activity.Metadata, activity.Created)
|
activity.OrgID, activity.UserID, activity.SpaceID, activity.DocumentID, activity.SectionID, activity.SourceType, activity.ActivityType, activity.Metadata, activity.Created)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "execute record user activity")
|
s.Runtime.Log.Error("execute record user activity", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -46,7 +45,7 @@ func (s Store) RecordUserActivity(ctx domain.RequestContext, activity activity.U
|
||||||
|
|
||||||
// GetDocumentActivity returns the metadata for a specified document.
|
// GetDocumentActivity returns the metadata for a specified document.
|
||||||
func (s Store) GetDocumentActivity(ctx domain.RequestContext, id string) (a []activity.DocumentActivity, err error) {
|
func (s Store) GetDocumentActivity(ctx domain.RequestContext, id string) (a []activity.DocumentActivity, err error) {
|
||||||
qry := s.Bind(`SELECT a.id, DATE(a.c_created) AS created, a.c_orgid AS orgid,
|
qry := s.Bind(`SELECT a.id, a.c_created AS created, a.c_orgid AS orgid,
|
||||||
COALESCE(a.c_userid, '') AS userid, a.c_spaceid AS spaceid,
|
COALESCE(a.c_userid, '') AS userid, a.c_spaceid AS spaceid,
|
||||||
a.c_docid AS documentid, a.c_sectionid AS sectionid, a.c_activitytype AS activitytype,
|
a.c_docid AS documentid, a.c_sectionid AS sectionid, a.c_activitytype AS activitytype,
|
||||||
a.c_metadata AS metadata,
|
a.c_metadata AS metadata,
|
||||||
|
@ -77,8 +76,10 @@ func (s Store) GetDocumentActivity(ctx domain.RequestContext, id string) (a []ac
|
||||||
|
|
||||||
// DeleteDocumentChangeActivity removes all entries for document changes (add, remove, update).
|
// DeleteDocumentChangeActivity removes all entries for document changes (add, remove, update).
|
||||||
func (s Store) DeleteDocumentChangeActivity(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
func (s Store) DeleteDocumentChangeActivity(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
||||||
rows, err = s.DeleteWhere(ctx.Transaction,
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_user_activity WHERE c_orgid=? AND c_docid=? AND (c_activitytype=1 OR c_activitytype=2 OR c_activitytype=3 OR c_activitytype=4 OR c_activitytype=7)"), ctx.OrgID, documentID)
|
||||||
fmt.Sprintf("DELETE FROM dmz_user_activity WHERE c_orgid='%s' AND c_docid='%s' AND (c_activitytype=1 OR c_activitytype=2 OR c_activitytype=3 OR c_activitytype=4 OR c_activitytype=7)", ctx.OrgID, documentID))
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,6 +180,7 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
dataSize := len(a.Data)
|
dataSize := len(a.Data)
|
||||||
|
|
||||||
|
// w.WriteHeader(http.StatusOK)
|
||||||
w.Header().Set("Content-Type", typ)
|
w.Header().Set("Content-Type", typ)
|
||||||
w.Header().Set("Content-Disposition", `Attachment; filename="`+a.Filename+`" ; `+`filename*="`+a.Filename+`"`)
|
w.Header().Set("Content-Disposition", `Attachment; filename="`+a.Filename+`" ; `+`filename*="`+a.Filename+`"`)
|
||||||
if dataSize != 0 {
|
if dataSize != 0 {
|
||||||
|
@ -191,7 +192,6 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
|
||||||
h.Runtime.Log.Error("write attachment", err)
|
h.Runtime.Log.Error("write attachment", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeAttachmentDownload)
|
h.Store.Audit.Record(ctx, audit.EventTypeAttachmentDownload)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ package attachment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -147,8 +146,10 @@ func (s Store) Delete(ctx domain.RequestContext, id string) (rows int64, err err
|
||||||
|
|
||||||
// DeleteSection removes all attachments agasinst a section.
|
// DeleteSection removes all attachments agasinst a section.
|
||||||
func (s Store) DeleteSection(ctx domain.RequestContext, sectionID string) (rows int64, err error) {
|
func (s Store) DeleteSection(ctx domain.RequestContext, sectionID string) (rows int64, err error) {
|
||||||
rows, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_attachment WHERE c_orgid='%s' AND c_sectionid='%s'",
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_doc_attachment WHERE c_orgid=? AND c_sectionid=?"), ctx.OrgID, sectionID)
|
||||||
ctx.OrgID, sectionID))
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ package audit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
"github.com/documize/community/domain/store"
|
"github.com/documize/community/domain/store"
|
||||||
|
@ -35,21 +36,21 @@ func (s Store) Record(ctx domain.RequestContext, t audit.EventType) {
|
||||||
e.IP = ctx.ClientIP
|
e.IP = ctx.ClientIP
|
||||||
e.Type = string(t)
|
e.Type = string(t)
|
||||||
|
|
||||||
tx, err := s.Runtime.Db.Beginx()
|
tx, ok := s.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
if err != nil {
|
if !ok {
|
||||||
s.Runtime.Log.Error("transaction", err)
|
s.Runtime.Log.Info("unable to start transaction")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec(s.Bind("INSERT INTO dmz_audit_log (c_orgid, c_userid, c_eventtype, c_ip, c_created) VALUES (?, ?, ?, ?, ?)"),
|
_, err := tx.Exec(s.Bind("INSERT INTO dmz_audit_log (c_orgid, c_userid, c_eventtype, c_ip, c_created) VALUES (?, ?, ?, ?, ?)"),
|
||||||
e.OrgID, e.UserID, e.Type, e.IP, e.Created)
|
e.OrgID, e.UserID, e.Type, e.IP, e.Created)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
s.Runtime.Rollback(tx)
|
||||||
s.Runtime.Log.Error("prepare audit insert", err)
|
s.Runtime.Log.Error("prepare audit insert", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.Commit()
|
s.Runtime.Commit(tx)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ func AddExternalUser(ctx domain.RequestContext, rt *env.Runtime, store *store.St
|
||||||
if addUser {
|
if addUser {
|
||||||
userID = uniqueid.Generate()
|
userID = uniqueid.Generate()
|
||||||
u.RefID = userID
|
u.RefID = userID
|
||||||
|
u.Locale = ctx.OrgLocale
|
||||||
|
|
||||||
err = store.User.Add(ctx, u)
|
err = store.User.Add(ctx, u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
|
"github.com/documize/community/core/i18n"
|
||||||
"github.com/documize/community/core/response"
|
"github.com/documize/community/core/response"
|
||||||
"github.com/documize/community/core/secrets"
|
"github.com/documize/community/core/secrets"
|
||||||
"github.com/documize/community/core/streamutil"
|
"github.com/documize/community/core/streamutil"
|
||||||
|
@ -57,7 +58,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
// Org contains raw auth provider config
|
// Org contains raw auth provider config
|
||||||
org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID)
|
org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = "Error: unable to get organization record"
|
result.Message = i18n.Localize(ctx.Locale, "server_err_org")
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Error(result.Message, err)
|
h.Runtime.Log.Error(result.Message, err)
|
||||||
|
@ -66,7 +67,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Exit if not using Keycloak
|
// Exit if not using Keycloak
|
||||||
if org.AuthProvider != ath.AuthProviderKeycloak {
|
if org.AuthProvider != ath.AuthProviderKeycloak {
|
||||||
result.Message = "Error: skipping user sync with Keycloak as it is not the configured option"
|
result.Message = i18n.Localize(ctx.Locale, "server_keycloak_error1")
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Info(result.Message)
|
h.Runtime.Log.Info(result.Message)
|
||||||
|
@ -77,7 +78,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
c := ath.KeycloakConfig{}
|
c := ath.KeycloakConfig{}
|
||||||
err = json.Unmarshal([]byte(org.AuthConfig), &c)
|
err = json.Unmarshal([]byte(org.AuthConfig), &c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = "Error: unable read Keycloak configuration data"
|
result.Message = i18n.Localize(ctx.Locale, "server_keycloak_error2")
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Error(result.Message, err)
|
h.Runtime.Log.Error(result.Message, err)
|
||||||
|
@ -87,7 +88,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
// User list from Keycloak
|
// User list from Keycloak
|
||||||
kcUsers, err := Fetch(c)
|
kcUsers, err := Fetch(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = "Error: unable to fetch Keycloak users: " + err.Error()
|
result.Message = i18n.Localize(ctx.Locale, "server_keycloak_error3", err.Error())
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Error(result.Message, err)
|
h.Runtime.Log.Error(result.Message, err)
|
||||||
|
@ -97,7 +98,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
// User list from Documize
|
// User list from Documize
|
||||||
dmzUsers, err := h.Store.User.GetUsersForOrganization(ctx, "", 99999)
|
dmzUsers, err := h.Store.User.GetUsersForOrganization(ctx, "", 99999)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = "Error: unable to fetch Documize users"
|
result.Message = i18n.Localize(ctx.Locale, "server_error_user")
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Error(result.Message, err)
|
h.Runtime.Log.Error(result.Message, err)
|
||||||
|
@ -135,8 +136,8 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Message = fmt.Sprintf("Keycloak sync found %d users, %d new users added, %d users with missing data ignored",
|
result.Message = i18n.Localize(ctx.Locale, "server_keycloak_summary",
|
||||||
len(kcUsers), len(insert), missing)
|
fmt.Sprintf("%d", len(kcUsers)), fmt.Sprintf("%d", len(insert)), fmt.Sprintf("%d", missing))
|
||||||
|
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Info(result.Message)
|
h.Runtime.Log.Info(result.Message)
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
|
"github.com/documize/community/core/i18n"
|
||||||
"github.com/documize/community/core/response"
|
"github.com/documize/community/core/response"
|
||||||
"github.com/documize/community/core/secrets"
|
"github.com/documize/community/core/secrets"
|
||||||
"github.com/documize/community/core/streamutil"
|
"github.com/documize/community/core/streamutil"
|
||||||
|
@ -146,7 +147,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
// Org contains raw auth provider config
|
// Org contains raw auth provider config
|
||||||
org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID)
|
org, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = "Error: unable to get organization record"
|
result.Message = i18n.Localize(ctx.Locale, "server_error_org")
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Error(result.Message, err)
|
h.Runtime.Log.Error(result.Message, err)
|
||||||
|
@ -155,7 +156,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Exit if not using LDAP
|
// Exit if not using LDAP
|
||||||
if org.AuthProvider != ath.AuthProviderLDAP {
|
if org.AuthProvider != ath.AuthProviderLDAP {
|
||||||
result.Message = "Error: skipping user sync with LDAP as it is not the configured option"
|
result.Message = i18n.Localize(ctx.Locale, "server_ldap_error1")
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Info(result.Message)
|
h.Runtime.Log.Info(result.Message)
|
||||||
|
@ -166,7 +167,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
c := lm.LDAPConfig{}
|
c := lm.LDAPConfig{}
|
||||||
err = json.Unmarshal([]byte(org.AuthConfig), &c)
|
err = json.Unmarshal([]byte(org.AuthConfig), &c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = "Error: unable read LDAP configuration data"
|
result.Message = i18n.Localize(ctx.Locale, "server_ldap_error2")
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Error(result.Message, err)
|
h.Runtime.Log.Error(result.Message, err)
|
||||||
|
@ -176,7 +177,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
// Get user list from LDAP.
|
// Get user list from LDAP.
|
||||||
ldapUsers, err := fetchUsers(c)
|
ldapUsers, err := fetchUsers(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = "Error: unable to fetch LDAP users: " + err.Error()
|
result.Message = i18n.Localize(ctx.Locale, "server_ldap_error3", err.Error())
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Error(result.Message, err)
|
h.Runtime.Log.Error(result.Message, err)
|
||||||
|
@ -186,7 +187,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
// Get user list from Documize
|
// Get user list from Documize
|
||||||
dmzUsers, err := h.Store.User.GetUsersForOrganization(ctx, "", 99999)
|
dmzUsers, err := h.Store.User.GetUsersForOrganization(ctx, "", 99999)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = "Error: unable to fetch Documize users"
|
result.Message = i18n.Localize(ctx.Locale, "server_error_user")
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
response.WriteJSON(w, result)
|
response.WriteJSON(w, result)
|
||||||
h.Runtime.Log.Error(result.Message, err)
|
h.Runtime.Log.Error(result.Message, err)
|
||||||
|
@ -223,10 +224,8 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
result.IsError = false
|
result.IsError = false
|
||||||
result.Message = "Sync complete with LDAP server"
|
result.Message = i18n.Localize(ctx.Locale, "server_ldap_complete")
|
||||||
result.Message = fmt.Sprintf(
|
result.Message = i18n.Localize(ctx.Locale, "server_ldap_summary", fmt.Sprintf("%d", len(ldapUsers)), fmt.Sprintf("%d", len(insert)), fmt.Sprintf("%d", missing))
|
||||||
"LDAP sync found %d users, %d new users added, %d users with missing data ignored",
|
|
||||||
len(ldapUsers), len(insert), missing)
|
|
||||||
|
|
||||||
h.Runtime.Log.Info(result.Message)
|
h.Runtime.Log.Info(result.Message)
|
||||||
|
|
||||||
|
@ -327,21 +326,16 @@ func (h *Handler) Authenticate(w http.ResponseWriter, r *http.Request) {
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if len(lu.Email) == 0 || len(u.Email) == 0 {
|
||||||
|
response.WriteUnauthorizedError(w)
|
||||||
|
h.Runtime.Log.Infof("LDAP user without email faild auth (%s)", username)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Create user account if not found
|
// If user authenticated BUT is not within Documize, we fail authentication.
|
||||||
|
// If dual auth is enabled, we can try regular email/password login (see next).
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
h.Runtime.Log.Info("Adding new LDAP user " + lu.Email + " @ " + dom)
|
ok = false
|
||||||
|
|
||||||
u = convertUser(lc, lu)
|
|
||||||
u.Salt = secrets.GenerateSalt()
|
|
||||||
u.Password = secrets.GeneratePassword(secrets.GenerateRandomPassword(), u.Salt)
|
|
||||||
|
|
||||||
u, err = auth.AddExternalUser(ctx, h.Runtime, h.Store, u, lc.DefaultPermissionAddSpace)
|
|
||||||
if err != nil {
|
|
||||||
response.WriteServerError(w, method, err)
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ import (
|
||||||
"github.com/documize/community/core/stringutil"
|
"github.com/documize/community/core/stringutil"
|
||||||
lm "github.com/documize/community/model/auth"
|
lm "github.com/documize/community/model/auth"
|
||||||
"github.com/documize/community/model/user"
|
"github.com/documize/community/model/user"
|
||||||
|
ld "github.com/go-ldap/ldap/v3"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
ld "gopkg.in/ldap.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connect establishes connection to LDAP server.
|
// Connect establishes connection to LDAP server.
|
||||||
|
@ -172,9 +172,9 @@ func executeGroupFilter(c lm.LDAPConfig) (u []lm.LDAPUser, err error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get CN element from DN.
|
||||||
for _, entry := range rawMembers {
|
for _, entry := range rawMembers {
|
||||||
// get CN element from DN
|
parts := splitDN(entry)
|
||||||
parts := strings.Split(entry, ",")
|
|
||||||
if len(parts) == 0 {
|
if len(parts) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -204,6 +204,44 @@ func executeGroupFilter(c lm.LDAPConfig) (u []lm.LDAPUser, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// splitDN handles splitting of DN string whilst respecting
|
||||||
|
// escaped comma characters.
|
||||||
|
//
|
||||||
|
// DN values can contain escaped commas like in two ways:
|
||||||
|
//
|
||||||
|
// \,
|
||||||
|
// \5c,
|
||||||
|
//
|
||||||
|
// Relevant notes:
|
||||||
|
//
|
||||||
|
// https://docs.oracle.com/cd/E19424-01/820-4811/gdxpo/index.html#6ng8i269q
|
||||||
|
// https://devblogs.microsoft.com/scripting/how-can-i-work-with-a-cn-that-has-a-comma-in-it/
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// CN=Surname\, Name,OU=Something,OU=AD-Example,OU=Examaple,DC=example,DC=example,DC=com
|
||||||
|
//
|
||||||
|
// When we split on comma, here is our logic:
|
||||||
|
//
|
||||||
|
// 1. Replace any escaped comma values with a special character sequence.
|
||||||
|
// 2. Split string on comma as per usual.
|
||||||
|
// 3. Put back the original escaped comma values.
|
||||||
|
func splitDN(dn string) []string {
|
||||||
|
dn = strings.ReplaceAll(dn, `\5c,`, "!!1!!")
|
||||||
|
dn = strings.ReplaceAll(dn, `\,`, "!!2!!")
|
||||||
|
|
||||||
|
sp := strings.Split(dn, ",")
|
||||||
|
|
||||||
|
for i := range sp {
|
||||||
|
val := sp[i]
|
||||||
|
val = strings.ReplaceAll(val, "!!1!!", `\5c,`)
|
||||||
|
val = strings.ReplaceAll(val, "!!2!!", `\,`)
|
||||||
|
sp[i] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return sp
|
||||||
|
}
|
||||||
|
|
||||||
// extractUser build user record from LDAP result attributes.
|
// extractUser build user record from LDAP result attributes.
|
||||||
func extractUser(c lm.LDAPConfig, e *ld.Entry) (u lm.LDAPUser) {
|
func extractUser(c lm.LDAPConfig, e *ld.Entry) (u lm.LDAPUser) {
|
||||||
u.Firstname = e.GetAttributeValue(c.AttributeUserFirstname)
|
u.Firstname = e.GetAttributeValue(c.AttributeUserFirstname)
|
||||||
|
|
39
domain/auth/ldap/ldap_test.go
Normal file
39
domain/auth/ldap/ldap_test.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||||
|
//
|
||||||
|
// This software (Documize Community Edition) is licensed under
|
||||||
|
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// You can operate outside the AGPL restrictions by purchasing
|
||||||
|
// Documize Enterprise Edition and obtaining a commercial license
|
||||||
|
// by contacting <sales@documize.com>.
|
||||||
|
//
|
||||||
|
// https://documize.com
|
||||||
|
|
||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testSplitData = []struct {
|
||||||
|
in string
|
||||||
|
count int
|
||||||
|
}{
|
||||||
|
{`CN=Surname\,Name,OU=Something,OU=AD-Example,OU=Examaple,DC=example,DC=example,DC=com`, 7},
|
||||||
|
{`CN=Surname\, Name,OU=Something,OU=AD-Example,OU=Examaple,DC=example,DC=example,DC=com`, 7},
|
||||||
|
{`CN=Surname\5c, Name,OU=Some\,thing,OU=AD-Example,OU=Examaple,DC=example,DC=example,DC=com`, 7},
|
||||||
|
{`CN=Surname\5c,Name,OU=Something,OU=AD-Example,OU=Examaple,DC=example,DC=example,DC=com`, 7},
|
||||||
|
{`CN=Given,OU=Something,OU=AD-Example,OU=Examaple,DC=example,DC=example,DC=com`, 7},
|
||||||
|
{"cn=Hubert\\, J. Farnsworth,ou=people,dc=planetexpress,dc=com", 4},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_SplitDN(t *testing.T) {
|
||||||
|
for _, td := range testSplitData {
|
||||||
|
sp := splitDN(td.in)
|
||||||
|
if len(sp) != td.count {
|
||||||
|
t.Errorf("Did not receive %d split entries", td.count)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Logf("%d entries: %v", len(sp), sp)
|
||||||
|
}
|
||||||
|
}
|
|
@ -244,7 +244,7 @@ func (b backerHandler) dmzOrg(files *[]backupItem) (err error) {
|
||||||
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
||||||
coalesce(c_sub,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS subscription,
|
coalesce(c_sub,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS subscription,
|
||||||
coalesce(c_authconfig,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS authconfig, c_maxtags AS maxtags,
|
coalesce(c_authconfig,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS authconfig, c_maxtags AS maxtags,
|
||||||
c_theme AS theme, c_logo AS logo, c_created AS created, c_revised AS revised
|
c_theme AS theme, c_logo AS logo, c_locale as locale, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_org`+w)
|
FROM dmz_org`+w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -308,7 +308,7 @@ func (b backerHandler) dmzUserAccount(files *[]backupItem) (err error) {
|
||||||
err = b.Runtime.Db.Select(&u, `SELECT u.id, u.c_refid AS refid,
|
err = b.Runtime.Db.Select(&u, `SELECT u.id, u.c_refid AS refid,
|
||||||
u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email,
|
u.c_firstname AS firstname, u.c_lastname AS lastname, u.c_email AS email,
|
||||||
u.c_initials AS initials, u.c_globaladmin AS globaladmin,
|
u.c_initials AS initials, u.c_globaladmin AS globaladmin,
|
||||||
u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion,
|
u.c_password AS password, u.c_salt AS salt, u.c_reset AS reset, u.c_lastversion AS lastversion, u.c_locale as locale,
|
||||||
u.c_created AS created, u.c_revised AS revised
|
u.c_created AS created, u.c_revised AS revised
|
||||||
FROM dmz_user u`+w)
|
FROM dmz_user u`+w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -545,7 +545,8 @@ func (b backerHandler) dmzCategory(files *[]backupItem) (err error) {
|
||||||
err = b.Runtime.Db.Select(&cat, `
|
err = b.Runtime.Db.Select(&cat, `
|
||||||
SELECT id, c_refid AS refid,
|
SELECT id, c_refid AS refid,
|
||||||
c_orgid AS orgid, c_spaceid AS spaceid,
|
c_orgid AS orgid, c_spaceid AS spaceid,
|
||||||
c_name AS name, c_created AS created, c_revised AS revised
|
c_name AS name, c_default AS isdefault,
|
||||||
|
c_created AS created, c_revised AS revised
|
||||||
FROM dmz_category`+w)
|
FROM dmz_category`+w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "select.category")
|
return errors.Wrap(err, "select.category")
|
||||||
|
@ -681,7 +682,7 @@ func (b backerHandler) dmzDocument(files *[]backupItem) (err error) {
|
||||||
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
||||||
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
||||||
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
||||||
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_doc`+w)
|
FROM dmz_doc`+w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "select.document")
|
return errors.Wrap(err, "select.document")
|
||||||
|
|
|
@ -89,7 +89,7 @@ func (h *Handler) Backup(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Runtime.Log.Info("Backup started")
|
h.Runtime.Log.Infof("Backup started %s", ctx.OrgID)
|
||||||
|
|
||||||
bh := backerHandler{Runtime: h.Runtime, Store: h.Store, Context: ctx, Spec: spec}
|
bh := backerHandler{Runtime: h.Runtime, Store: h.Store, Context: ctx, Spec: spec}
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ func (h *Handler) Backup(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Runtime.Log.Info(fmt.Sprintf("Backup size pending download %d", len(bk)))
|
h.Runtime.Log.Info(fmt.Sprintf("Backup size of org %s pending download %d", ctx.OrgID, len(bk)))
|
||||||
|
|
||||||
// Standard HTTP headers.
|
// Standard HTTP headers.
|
||||||
w.Header().Set("Content-Type", "application/zip")
|
w.Header().Set("Content-Type", "application/zip")
|
||||||
|
@ -124,6 +124,7 @@ func (h *Handler) Backup(w http.ResponseWriter, r *http.Request) {
|
||||||
// instead of parsing 'Content-Disposition' header.
|
// instead of parsing 'Content-Disposition' header.
|
||||||
// This HTTP header is CORS white-listed.
|
// This HTTP header is CORS white-listed.
|
||||||
w.Header().Set("x-documize-filename", filename)
|
w.Header().Set("x-documize-filename", filename)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
// Write backup to response stream.
|
// Write backup to response stream.
|
||||||
x, err := w.Write(bk)
|
x, err := w.Write(bk)
|
||||||
|
@ -140,8 +141,6 @@ func (h *Handler) Backup(w http.ResponseWriter, r *http.Request) {
|
||||||
if !spec.Retain {
|
if !spec.Retain {
|
||||||
os.Remove(filename)
|
os.Remove(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore receives ZIP file for restore operation.
|
// Restore receives ZIP file for restore operation.
|
||||||
|
|
|
@ -370,14 +370,14 @@ func (r *restoreHandler) dmzOrg() (err error) {
|
||||||
INSERT INTO dmz_org (c_refid, c_company, c_title, c_message,
|
INSERT INTO dmz_org (c_refid, c_company, c_title, c_message,
|
||||||
c_domain, c_service, c_email, c_anonaccess, c_authprovider, c_authconfig,
|
c_domain, c_service, c_email, c_anonaccess, c_authprovider, c_authconfig,
|
||||||
c_maxtags, c_verified, c_serial, c_sub, c_active,
|
c_maxtags, c_verified, c_serial, c_sub, c_active,
|
||||||
c_theme, c_logo, c_created, c_revised)
|
c_theme, c_logo, c_locale, c_created, c_revised)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||||
org[i].RefID, org[i].Company, org[i].Title, org[i].Message,
|
org[i].RefID, org[i].Company, org[i].Title, org[i].Message,
|
||||||
strings.ToLower(org[i].Domain), org[i].ConversionEndpoint, strings.ToLower(org[i].Email),
|
strings.ToLower(org[i].Domain), org[i].ConversionEndpoint, strings.ToLower(org[i].Email),
|
||||||
org[i].AllowAnonymousAccess, org[i].AuthProvider, org[i].AuthConfig,
|
org[i].AllowAnonymousAccess, org[i].AuthProvider, org[i].AuthConfig,
|
||||||
org[i].MaxTags, r.Runtime.StoreProvider.IsTrue(), org[i].Serial,
|
org[i].MaxTags, r.Runtime.StoreProvider.IsTrue(), org[i].Serial,
|
||||||
org[i].Subscription, org[i].Active,
|
org[i].Subscription, org[i].Active,
|
||||||
org[i].Theme, org[i].Logo,
|
org[i].Theme, org[i].Logo, org[i].Locale,
|
||||||
org[i].Created, org[i].Revised)
|
org[i].Created, org[i].Revised)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Context.Transaction.Rollback()
|
r.Context.Transaction.Rollback()
|
||||||
|
@ -412,6 +412,7 @@ func (r *restoreHandler) dmzOrg() (err error) {
|
||||||
org[0].Title = r.Spec.Org.Title
|
org[0].Title = r.Spec.Org.Title
|
||||||
org[0].Subscription = r.Spec.Org.Subscription
|
org[0].Subscription = r.Spec.Org.Subscription
|
||||||
org[0].Theme = r.Spec.Org.Theme
|
org[0].Theme = r.Spec.Org.Theme
|
||||||
|
org[0].Locale = r.Spec.Org.Locale
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = r.Context.Transaction.NamedExec(`UPDATE dmz_org SET
|
_, err = r.Context.Transaction.NamedExec(`UPDATE dmz_org SET
|
||||||
|
@ -425,7 +426,8 @@ func (r *restoreHandler) dmzOrg() (err error) {
|
||||||
c_message=:message,
|
c_message=:message,
|
||||||
c_title=:title,
|
c_title=:title,
|
||||||
c_serial=:serial,
|
c_serial=:serial,
|
||||||
c_sub=:subscription
|
c_sub=:subscription,
|
||||||
|
c_locale=:locale
|
||||||
WHERE c_refid=:refid`, &org[0])
|
WHERE c_refid=:refid`, &org[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Context.Transaction.Rollback()
|
r.Context.Transaction.Rollback()
|
||||||
|
@ -743,9 +745,9 @@ func (r *restoreHandler) dmzCategory() (err error) {
|
||||||
|
|
||||||
for i := range ct {
|
for i := range ct {
|
||||||
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(`
|
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(`
|
||||||
INSERT INTO dmz_category (c_refid, c_orgid, c_spaceid, c_name, c_created, c_revised)
|
INSERT INTO dmz_category (c_refid, c_orgid, c_spaceid, c_name, c_default, c_created, c_revised)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)`),
|
VALUES (?, ?, ?, ?, ?, ?, ?)`),
|
||||||
ct[i].RefID, r.remapOrg(ct[i].OrgID), ct[i].SpaceID, ct[i].Name, ct[i].Created, ct[i].Revised)
|
ct[i].RefID, r.remapOrg(ct[i].OrgID), ct[i].SpaceID, ct[i].Name, ct[i].IsDefault, ct[i].Created, ct[i].Revised)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Context.Transaction.Rollback()
|
r.Context.Transaction.Rollback()
|
||||||
|
@ -1334,12 +1336,13 @@ func (r *restoreHandler) dmzDoc() (err error) {
|
||||||
INSERT INTO dmz_doc
|
INSERT INTO dmz_doc
|
||||||
(c_refid, c_orgid, c_spaceid, c_userid, c_job, c_location,
|
(c_refid, c_orgid, c_spaceid, c_userid, c_job, c_location,
|
||||||
c_name, c_desc, c_slug, c_tags, c_template, c_protection, c_approval,
|
c_name, c_desc, c_slug, c_tags, c_template, c_protection, c_approval,
|
||||||
c_lifecycle, c_versioned, c_versionid, c_versionorder, c_groupid, c_created, c_revised)
|
c_lifecycle, c_versioned, c_versionid, c_versionorder, c_seq, c_groupid,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
c_created, c_revised)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||||
doc[i].RefID, r.remapOrg(doc[i].OrgID), doc[i].SpaceID, r.remapUser(doc[i].UserID), doc[i].Job,
|
doc[i].RefID, r.remapOrg(doc[i].OrgID), doc[i].SpaceID, r.remapUser(doc[i].UserID), doc[i].Job,
|
||||||
doc[i].Location, doc[i].Name, doc[i].Excerpt, doc[i].Slug, doc[i].Tags,
|
doc[i].Location, doc[i].Name, doc[i].Excerpt, doc[i].Slug, doc[i].Tags,
|
||||||
doc[i].Template, doc[i].Protection, doc[i].Approval, doc[i].Lifecycle,
|
doc[i].Template, doc[i].Protection, doc[i].Approval, doc[i].Lifecycle,
|
||||||
doc[i].Versioned, doc[i].VersionID, doc[i].VersionOrder, doc[i].GroupID,
|
doc[i].Versioned, doc[i].VersionID, doc[i].VersionOrder, doc[i].Sequence, doc[i].GroupID,
|
||||||
doc[i].Created, doc[i].Revised)
|
doc[i].Created, doc[i].Revised)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1734,11 +1737,11 @@ func (r *restoreHandler) dmzUser() (err error) {
|
||||||
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(`
|
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(`
|
||||||
INSERT INTO dmz_user
|
INSERT INTO dmz_user
|
||||||
(c_refid, c_firstname, c_lastname, c_email, c_initials, c_globaladmin,
|
(c_refid, c_firstname, c_lastname, c_email, c_initials, c_globaladmin,
|
||||||
c_password, c_salt, c_reset, c_active, c_lastversion, c_created, c_revised)
|
c_password, c_salt, c_reset, c_active, c_lastversion, c_locale, c_created, c_revised)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||||
r.remapUser(u[i].RefID), u[i].Firstname, u[i].Lastname, strings.ToLower(u[i].Email), u[i].Initials,
|
r.remapUser(u[i].RefID), u[i].Firstname, u[i].Lastname, strings.ToLower(u[i].Email), u[i].Initials,
|
||||||
u[i].GlobalAdmin, u[i].Password, u[i].Salt, u[i].Reset, u[i].Active,
|
u[i].GlobalAdmin, u[i].Password, u[i].Salt, u[i].Reset, u[i].Active,
|
||||||
u[i].LastVersion, u[i].Created, u[i].Revised)
|
u[i].LastVersion, u[i].Locale, u[i].Created, u[i].Revised)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Context.Transaction.Rollback()
|
r.Context.Transaction.Rollback()
|
||||||
|
|
|
@ -74,10 +74,10 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category max length 30.
|
// Category max length 50.
|
||||||
cat.Name = strings.TrimSpace(cat.Name)
|
cat.Name = strings.TrimSpace(cat.Name)
|
||||||
if len(cat.Name) > 30 {
|
if len(cat.Name) > 50 {
|
||||||
cat.Name = cat.Name[:30]
|
cat.Name = cat.Name[:50]
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Category.Add(ctx, cat)
|
err = h.Store.Category.Add(ctx, cat)
|
||||||
|
@ -105,14 +105,6 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Space.IncrementCategoryCount(ctx, cat.SpaceID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
response.WriteServerError(w, method, err)
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
cat, err = h.Store.Category.Get(ctx, cat.RefID)
|
cat, err = h.Store.Category.Get(ctx, cat.RefID)
|
||||||
|
@ -122,6 +114,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.Store.Space.SetStats(ctx, cat.SpaceID)
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeCategoryAdd)
|
h.Store.Audit.Record(ctx, audit.EventTypeCategoryAdd)
|
||||||
|
|
||||||
response.WriteJSON(w, cat)
|
response.WriteJSON(w, cat)
|
||||||
|
@ -303,16 +296,9 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Space.DecrementCategoryCount(ctx, cat.SpaceID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
response.WriteServerError(w, method, err)
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
|
h.Store.Space.SetStats(ctx, cat.SpaceID)
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeCategoryDelete)
|
h.Store.Audit.Record(ctx, audit.EventTypeCategoryDelete)
|
||||||
|
|
||||||
response.WriteEmpty(w)
|
response.WriteEmpty(w)
|
||||||
|
|
|
@ -33,8 +33,8 @@ func (s Store) Add(ctx domain.RequestContext, c category.Category) (err error) {
|
||||||
c.Created = time.Now().UTC()
|
c.Created = time.Now().UTC()
|
||||||
c.Revised = time.Now().UTC()
|
c.Revised = time.Now().UTC()
|
||||||
|
|
||||||
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_category (c_refid, c_orgid, c_spaceid, c_name, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?)"),
|
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_category (c_refid, c_orgid, c_spaceid, c_name, c_default, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?)"),
|
||||||
c.RefID, c.OrgID, c.SpaceID, c.Name, c.Created, c.Revised)
|
c.RefID, c.OrgID, c.SpaceID, c.Name, c.IsDefault, c.Created, c.Revised)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "unable to execute insert category")
|
err = errors.Wrap(err, "unable to execute insert category")
|
||||||
|
@ -47,7 +47,7 @@ func (s Store) Add(ctx domain.RequestContext, c category.Category) (err error) {
|
||||||
// Context is used to for user ID.
|
// Context is used to for user ID.
|
||||||
func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (c []category.Category, err error) {
|
func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (c []category.Category, err error) {
|
||||||
err = s.Runtime.Db.Select(&c, s.Bind(`
|
err = s.Runtime.Db.Select(&c, s.Bind(`
|
||||||
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
|
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_default AS isdefault, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_category
|
FROM dmz_category
|
||||||
WHERE c_orgid=? AND c_spaceid=? AND c_refid IN
|
WHERE c_orgid=? AND c_spaceid=? AND c_refid IN
|
||||||
(
|
(
|
||||||
|
@ -77,7 +77,7 @@ func (s Store) GetAllBySpace(ctx domain.RequestContext, spaceID string) (c []cat
|
||||||
c = []category.Category{}
|
c = []category.Category{}
|
||||||
|
|
||||||
err = s.Runtime.Db.Select(&c, s.Bind(`
|
err = s.Runtime.Db.Select(&c, s.Bind(`
|
||||||
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
|
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_default AS isdefault, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_category
|
FROM dmz_category
|
||||||
WHERE c_orgid=? AND c_spaceid=? AND c_spaceid IN
|
WHERE c_orgid=? AND c_spaceid=? AND c_spaceid IN
|
||||||
(
|
(
|
||||||
|
@ -105,7 +105,7 @@ func (s Store) GetAllBySpace(ctx domain.RequestContext, spaceID string) (c []cat
|
||||||
// GetByOrg returns all categories accessible by user for their org.
|
// GetByOrg returns all categories accessible by user for their org.
|
||||||
func (s Store) GetByOrg(ctx domain.RequestContext, userID string) (c []category.Category, err error) {
|
func (s Store) GetByOrg(ctx domain.RequestContext, userID string) (c []category.Category, err error) {
|
||||||
err = s.Runtime.Db.Select(&c, s.Bind(`
|
err = s.Runtime.Db.Select(&c, s.Bind(`
|
||||||
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
|
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_default AS isdefault, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_category
|
FROM dmz_category
|
||||||
WHERE c_orgid=? AND c_refid IN
|
WHERE c_orgid=? AND c_refid IN
|
||||||
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='category' AND c_refid IN (
|
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='category' AND c_refid IN (
|
||||||
|
@ -131,7 +131,7 @@ func (s Store) GetByOrg(ctx domain.RequestContext, userID string) (c []category.
|
||||||
func (s Store) Update(ctx domain.RequestContext, c category.Category) (err error) {
|
func (s Store) Update(ctx domain.RequestContext, c category.Category) (err error) {
|
||||||
c.Revised = time.Now().UTC()
|
c.Revised = time.Now().UTC()
|
||||||
|
|
||||||
_, err = ctx.Transaction.NamedExec(s.Bind("UPDATE dmz_category SET c_name=:name, c_revised=:revised WHERE c_orgid=:orgid AND c_refid=:refid"), c)
|
_, err = ctx.Transaction.NamedExec(s.Bind("UPDATE dmz_category SET c_name=:name, c_default=:isdefault, c_revised=:revised WHERE c_orgid=:orgid AND c_refid=:refid"), c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute update for category %s", c.RefID))
|
err = errors.Wrap(err, fmt.Sprintf("unable to execute update for category %s", c.RefID))
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ func (s Store) Update(ctx domain.RequestContext, c category.Category) (err error
|
||||||
// Get returns specified category
|
// Get returns specified category
|
||||||
func (s Store) Get(ctx domain.RequestContext, id string) (c category.Category, err error) {
|
func (s Store) Get(ctx domain.RequestContext, id string) (c category.Category, err error) {
|
||||||
err = s.Runtime.Db.Get(&c, s.Bind(`
|
err = s.Runtime.Db.Get(&c, s.Bind(`
|
||||||
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
|
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_default AS isdefault, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_category
|
FROM dmz_category
|
||||||
WHERE c_orgid=? AND c_refid=?`),
|
WHERE c_orgid=? AND c_refid=?`),
|
||||||
ctx.OrgID, id)
|
ctx.OrgID, id)
|
||||||
|
@ -176,46 +176,69 @@ func (s Store) AssociateDocument(ctx domain.RequestContext, m category.Member) (
|
||||||
|
|
||||||
// DisassociateDocument removes document associatation from category.
|
// DisassociateDocument removes document associatation from category.
|
||||||
func (s Store) DisassociateDocument(ctx domain.RequestContext, categoryID, documentID string) (rows int64, err error) {
|
func (s Store) DisassociateDocument(ctx domain.RequestContext, categoryID, documentID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_orgid='%s' AND c_categoryid='%s' AND c_docid='%s'",
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_category_member WHERE c_orgid=? AND c_categoryid=? AND c_docid=?"),
|
||||||
ctx.OrgID, categoryID, documentID)
|
ctx.OrgID, categoryID, documentID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveCategoryMembership removes all category associations from the store.
|
// RemoveCategoryMembership removes all category associations from the store.
|
||||||
func (s Store) RemoveCategoryMembership(ctx domain.RequestContext, categoryID string) (rows int64, err error) {
|
func (s Store) RemoveCategoryMembership(ctx domain.RequestContext, categoryID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_orgid='%s' AND c_categoryid='%s'",
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_category_member WHERE c_orgid=? AND c_categoryid=?"),
|
||||||
ctx.OrgID, categoryID)
|
ctx.OrgID, categoryID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveSpaceCategoryMemberships removes all category associations from the store for the space.
|
// RemoveSpaceCategoryMemberships removes all category associations from the store for the space.
|
||||||
func (s Store) RemoveSpaceCategoryMemberships(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
func (s Store) RemoveSpaceCategoryMemberships(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_orgid='%s' AND c_spaceid='%s'",
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_category_member WHERE c_orgid=? AND c_spaceid=?"),
|
||||||
ctx.OrgID, spaceID)
|
ctx.OrgID, spaceID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveDocumentCategories removes all document category associations from the store.
|
// RemoveDocumentCategories removes all document category associations from the store.
|
||||||
func (s Store) RemoveDocumentCategories(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
func (s Store) RemoveDocumentCategories(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_orgid='%s' AND c_docid='%s'",
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_category_member WHERE c_orgid=? AND c_docid=?"),
|
||||||
ctx.OrgID, documentID)
|
ctx.OrgID, documentID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteBySpace removes all category and category associations for given space.
|
// DeleteBySpace removes all category and category associations for given space.
|
||||||
func (s Store) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
func (s Store) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
||||||
s1 := fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_orgid='%s' AND c_spaceid='%s'", ctx.OrgID, spaceID)
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_category_member WHERE c_orgid=? AND c_spaceid=?"),
|
||||||
_, err = s.DeleteWhere(ctx.Transaction, s1)
|
ctx.OrgID, spaceID)
|
||||||
if err != nil {
|
|
||||||
return
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s2 := fmt.Sprintf("DELETE FROM dmz_category WHERE c_orgid='%s' AND c_spaceid='%s'", ctx.OrgID, spaceID)
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_category WHERE c_orgid=? AND c_spaceid=?"),
|
||||||
return s.DeleteWhere(ctx.Transaction, s2)
|
ctx.OrgID, spaceID)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSpaceCategorySummary returns number of documents and users for space categories.
|
// GetSpaceCategorySummary returns number of documents and users for space categories.
|
||||||
|
@ -263,7 +286,7 @@ func (s Store) GetSpaceCategorySummary(ctx domain.RequestContext, spaceID string
|
||||||
// GetDocumentCategoryMembership returns all space categories associated with given document.
|
// GetDocumentCategoryMembership returns all space categories associated with given document.
|
||||||
func (s Store) GetDocumentCategoryMembership(ctx domain.RequestContext, documentID string) (c []category.Category, err error) {
|
func (s Store) GetDocumentCategoryMembership(ctx domain.RequestContext, documentID string) (c []category.Category, err error) {
|
||||||
err = s.Runtime.Db.Select(&c, s.Bind(`
|
err = s.Runtime.Db.Select(&c, s.Bind(`
|
||||||
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
|
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_default AS isdefault, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_category
|
FROM dmz_category
|
||||||
WHERE c_orgid=? AND c_refid IN (SELECT c_categoryid FROM dmz_category_member WHERE c_orgid=? AND c_docid=?)`),
|
WHERE c_orgid=? AND c_refid IN (SELECT c_categoryid FROM dmz_category_member WHERE c_orgid=? AND c_docid=?)`),
|
||||||
ctx.OrgID, ctx.OrgID, documentID)
|
ctx.OrgID, ctx.OrgID, documentID)
|
||||||
|
|
|
@ -44,6 +44,8 @@ type RequestContext struct {
|
||||||
GlobalAdmin bool
|
GlobalAdmin bool
|
||||||
ViewUsers bool
|
ViewUsers bool
|
||||||
Subscription Subscription
|
Subscription Subscription
|
||||||
|
Locale string
|
||||||
|
OrgLocale string
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetAppURL returns full HTTP url for the app
|
//GetAppURL returns full HTTP url for the app
|
||||||
|
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"github.com/documize/community/model/activity"
|
"github.com/documize/community/model/activity"
|
||||||
"github.com/documize/community/model/attachment"
|
"github.com/documize/community/model/attachment"
|
||||||
"github.com/documize/community/model/audit"
|
"github.com/documize/community/model/audit"
|
||||||
|
cm "github.com/documize/community/model/category"
|
||||||
"github.com/documize/community/model/doc"
|
"github.com/documize/community/model/doc"
|
||||||
"github.com/documize/community/model/page"
|
"github.com/documize/community/model/page"
|
||||||
"github.com/documize/community/model/space"
|
"github.com/documize/community/model/space"
|
||||||
|
@ -165,7 +166,8 @@ func (h *Handler) convert(w http.ResponseWriter, r *http.Request, job, spaceID s
|
||||||
response.WriteJSON(w, nd)
|
response.WriteJSON(w, nd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Store, indexer indexer.Indexer, filename, job string, sp space.Space, fileResult *api.DocumentConversionResponse) (newDocument doc.Document, err error) {
|
func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Store, indexer indexer.Indexer, filename,
|
||||||
|
job string, sp space.Space, fileResult *api.DocumentConversionResponse) (newDocument doc.Document, err error) {
|
||||||
// Convert into database objects
|
// Convert into database objects
|
||||||
document := convertFileResult(filename, fileResult)
|
document := convertFileResult(filename, fileResult)
|
||||||
document.Job = job
|
document.Job = job
|
||||||
|
@ -174,6 +176,7 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Sto
|
||||||
document.UserID = ctx.UserID
|
document.UserID = ctx.UserID
|
||||||
documentID := uniqueid.Generate()
|
documentID := uniqueid.Generate()
|
||||||
document.RefID = documentID
|
document.RefID = documentID
|
||||||
|
document.Sequence = doc.Unsequenced
|
||||||
|
|
||||||
if r.Product.Edition == domain.CommunityEdition {
|
if r.Product.Edition == domain.CommunityEdition {
|
||||||
document.Lifecycle = workflow.LifecycleLive
|
document.Lifecycle = workflow.LifecycleLive
|
||||||
|
@ -243,18 +246,33 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Sto
|
||||||
da = append(da, a)
|
da = append(da, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add default categories to newly created document (if we have them).
|
||||||
|
cats, err := store.Category.GetBySpace(ctx, document.SpaceID)
|
||||||
|
if err != nil {
|
||||||
|
r.Log.Error("fetch default categories for new document", err)
|
||||||
|
}
|
||||||
|
for ic := range cats {
|
||||||
|
if cats[ic].IsDefault {
|
||||||
|
c := cm.Member{}
|
||||||
|
c.OrgID = ctx.OrgID
|
||||||
|
c.SpaceID = sp.RefID
|
||||||
|
c.RefID = uniqueid.Generate()
|
||||||
|
c.DocumentID = document.RefID
|
||||||
|
c.CategoryID = cats[ic].RefID
|
||||||
|
|
||||||
|
err = store.Category.AssociateDocument(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
r.Log.Error("apply default category to new document", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: newDocument.SpaceID,
|
SpaceID: document.SpaceID,
|
||||||
DocumentID: newDocument.RefID,
|
DocumentID: document.RefID,
|
||||||
SourceType: activity.SourceTypeDocument,
|
SourceType: activity.SourceTypeDocument,
|
||||||
ActivityType: activity.TypeCreated})
|
ActivityType: activity.TypeCreated})
|
||||||
|
|
||||||
err = store.Space.IncrementContentCount(ctx, newDocument.SpaceID)
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Wrap(err, "cannot increment space content count")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ctx.Transaction.Commit()
|
err = ctx.Transaction.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "cannot commit new document import")
|
err = errors.Wrap(err, "cannot commit new document import")
|
||||||
|
@ -269,6 +287,7 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Sto
|
||||||
|
|
||||||
go indexer.IndexDocument(ctx, newDocument, da)
|
go indexer.IndexDocument(ctx, newDocument, da)
|
||||||
|
|
||||||
|
store.Space.SetStats(ctx, newDocument.SpaceID)
|
||||||
store.Audit.Record(ctx, audit.EventTypeDocumentUpload)
|
store.Audit.Record(ctx, audit.EventTypeDocumentUpload)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -66,6 +66,8 @@ func FilterCategoryProtected(docs []doc.Document, cats []category.Category, memb
|
||||||
|
|
||||||
// CopyDocument clones an existing document
|
// CopyDocument clones an existing document
|
||||||
func CopyDocument(ctx domain.RequestContext, s store.Store, documentID string) (newDocumentID string, err error) {
|
func CopyDocument(ctx domain.RequestContext, s store.Store, documentID string) (newDocumentID string, err error) {
|
||||||
|
unseq := doc.Unsequenced
|
||||||
|
|
||||||
doc, err := s.Document.Get(ctx, documentID)
|
doc, err := s.Document.Get(ctx, documentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "unable to fetch existing document")
|
err = errors.Wrap(err, "unable to fetch existing document")
|
||||||
|
@ -79,6 +81,7 @@ func CopyDocument(ctx domain.RequestContext, s store.Store, documentID string) (
|
||||||
doc.VersionID = ""
|
doc.VersionID = ""
|
||||||
doc.GroupID = ""
|
doc.GroupID = ""
|
||||||
doc.Template = false
|
doc.Template = false
|
||||||
|
doc.Sequence = unseq
|
||||||
|
|
||||||
// Duplicate pages and associated meta
|
// Duplicate pages and associated meta
|
||||||
pages, err := s.Page.GetPages(ctx, documentID)
|
pages, err := s.Page.GetPages(ctx, documentID)
|
||||||
|
|
|
@ -14,6 +14,7 @@ package document
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -63,6 +64,13 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
document, err := h.Store.Document.Get(ctx, id)
|
document, err := h.Store.Document.Get(ctx, id)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
response.WriteNotFoundError(w, method, id)
|
response.WriteNotFoundError(w, method, id)
|
||||||
|
@ -81,28 +89,22 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// draft mode does not record document views
|
// draft mode does not record document views
|
||||||
if document.Lifecycle == workflow.LifecycleLive {
|
if document.Lifecycle == workflow.LifecycleLive {
|
||||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: document.SpaceID,
|
SpaceID: document.SpaceID,
|
||||||
DocumentID: document.RefID,
|
DocumentID: document.RefID,
|
||||||
SourceType: activity.SourceTypeDocument,
|
SourceType: activity.SourceTypeDocument,
|
||||||
ActivityType: activity.TypeRead})
|
ActivityType: activity.TypeRead})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeDocumentView)
|
h.Store.Audit.Record(ctx, audit.EventTypeDocumentView)
|
||||||
|
|
||||||
response.WriteJSON(w, document)
|
response.WriteJSON(w, document)
|
||||||
|
@ -190,10 +192,26 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort document list by title.
|
sortedDocs := doc.SortedDocs{}
|
||||||
sort.Sort(doc.ByName(filtered))
|
|
||||||
|
|
||||||
response.WriteJSON(w, filtered)
|
for j := range filtered {
|
||||||
|
if filtered[j].Sequence == doc.Unsequenced {
|
||||||
|
sortedDocs.Unpinned = append(sortedDocs.Unpinned, filtered[j])
|
||||||
|
} else {
|
||||||
|
sortedDocs.Pinned = append(sortedDocs.Pinned, filtered[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort document list by title.
|
||||||
|
sort.Sort(doc.ByName(sortedDocs.Unpinned))
|
||||||
|
|
||||||
|
// Sort document list by sequence.
|
||||||
|
sort.Sort(doc.BySeq(sortedDocs.Pinned))
|
||||||
|
|
||||||
|
final := sortedDocs.Pinned
|
||||||
|
final = append(final, sortedDocs.Unpinned...)
|
||||||
|
|
||||||
|
response.WriteJSON(w, final)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates an existing document using the format described
|
// Update updates an existing document using the format described
|
||||||
|
@ -231,36 +249,40 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
d.RefID = documentID
|
d.RefID = documentID
|
||||||
|
|
||||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
var ok bool
|
||||||
if err != nil {
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If space changed for document, remove document categories.
|
// If space changed for document, remove document categories.
|
||||||
oldDoc, err := h.Store.Document.Get(ctx, documentID)
|
oldDoc, err := h.Store.Document.Get(ctx, documentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Transaction.Rollback()
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldDoc.SpaceID != d.SpaceID {
|
if oldDoc.SpaceID != d.SpaceID {
|
||||||
h.Store.Category.RemoveDocumentCategories(ctx, d.RefID)
|
_, _ = h.Store.Category.RemoveDocumentCategories(ctx, d.RefID)
|
||||||
err = h.Store.Document.MoveActivity(ctx, documentID, oldDoc.SpaceID, d.SpaceID)
|
err = h.Store.Document.MoveActivity(ctx, documentID, oldDoc.SpaceID, d.SpaceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Transaction.Rollback()
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// d.Name = bluemonday.StrictPolicy().Sanitize(d.Name)
|
||||||
|
// d.Excerpt = bluemonday.StrictPolicy().Sanitize(d.Excerpt)
|
||||||
|
|
||||||
err = h.Store.Document.Update(ctx, d)
|
err = h.Store.Document.Update(ctx, d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Transaction.Rollback()
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
|
@ -272,7 +294,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
||||||
if len(d.GroupID) > 0 {
|
if len(d.GroupID) > 0 {
|
||||||
err = h.Store.Document.UpdateGroup(ctx, d)
|
err = h.Store.Document.UpdateGroup(ctx, d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Transaction.Rollback()
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
|
@ -309,7 +331,12 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
|
_ = h.Store.Space.SetStats(ctx, d.SpaceID)
|
||||||
|
if oldDoc.SpaceID != d.SpaceID {
|
||||||
|
_ = h.Store.Space.SetStats(ctx, oldDoc.SpaceID)
|
||||||
|
}
|
||||||
|
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeDocumentUpdate)
|
h.Store.Audit.Record(ctx, audit.EventTypeDocumentUpdate)
|
||||||
|
|
||||||
|
@ -340,6 +367,13 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
|
@ -376,13 +410,6 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
|
||||||
if err != nil {
|
|
||||||
response.WriteServerError(w, method, err)
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.Store.Document.Delete(ctx, documentID)
|
_, err = h.Store.Document.Delete(ctx, documentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Transaction.Rollback()
|
ctx.Transaction.Rollback()
|
||||||
|
@ -411,16 +438,9 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
ActivityType: activity.TypeDeleted})
|
ActivityType: activity.TypeDeleted})
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Space.DecrementContentCount(ctx, doc.SpaceID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
response.WriteServerError(w, method, err)
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
|
h.Store.Space.SetStats(ctx, doc.SpaceID)
|
||||||
h.Store.Audit.Record(ctx, audit.EventTypeDocumentDelete)
|
h.Store.Audit.Record(ctx, audit.EventTypeDocumentDelete)
|
||||||
|
|
||||||
go h.Indexer.DeleteDocument(ctx, documentID)
|
go h.Indexer.DeleteDocument(ctx, documentID)
|
||||||
|
@ -481,18 +501,13 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: "",
|
SpaceID: "",
|
||||||
DocumentID: "",
|
DocumentID: "",
|
||||||
Metadata: options.Keywords,
|
Metadata: options.Keywords,
|
||||||
SourceType: activity.SourceTypeSearch,
|
SourceType: activity.SourceTypeSearch,
|
||||||
ActivityType: activity.TypeSearched})
|
ActivityType: activity.TypeSearched})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -527,18 +542,13 @@ func (h *Handler) recordSearchActivity(ctx domain.RequestContext, q []search.Que
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, isExisting := prev[q[i].DocumentID]; !isExisting {
|
if _, isExisting := prev[q[i].DocumentID]; !isExisting {
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: q[i].SpaceID,
|
SpaceID: q[i].SpaceID,
|
||||||
DocumentID: q[i].DocumentID,
|
DocumentID: q[i].DocumentID,
|
||||||
Metadata: keywords,
|
Metadata: keywords,
|
||||||
SourceType: activity.SourceTypeSearch,
|
SourceType: activity.SourceTypeSearch,
|
||||||
ActivityType: activity.TypeSearched})
|
ActivityType: activity.TypeSearched})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
prev[q[i].DocumentID] = true
|
prev[q[i].DocumentID] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -557,6 +567,13 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
document, err := h.Store.Document.Get(ctx, id)
|
document, err := h.Store.Document.Get(ctx, id)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
response.WriteNotFoundError(w, method, id)
|
response.WriteNotFoundError(w, method, id)
|
||||||
|
@ -664,23 +681,22 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
||||||
// Get version information for this document.
|
// Get version information for this document.
|
||||||
v := []doc.Version{}
|
v := []doc.Version{}
|
||||||
if len(document.GroupID) > 0 {
|
if len(document.GroupID) > 0 {
|
||||||
// Get versions.
|
// Get versions
|
||||||
vt, err := h.Store.Document.GetVersions(ctx, document.GroupID)
|
vt, err := h.Store.Document.GetVersions(ctx, document.GroupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// What about draft document versions?
|
// Determine which document versions user can see.
|
||||||
if record.DocumentLifecycle {
|
for i := range vt {
|
||||||
// We can see and manage document lifecycle so take all versions.
|
// Everyone can see live documents
|
||||||
v = vt
|
if vt[i].Lifecycle == workflow.LifecycleLive {
|
||||||
} else {
|
v = append(v, vt[i])
|
||||||
// Only send back LIVE content because user cannot drafts.
|
}
|
||||||
for i := range vt {
|
// Only lifecycle admins can see draft documents
|
||||||
if vt[i].Lifecycle == workflow.LifecycleLive {
|
if vt[i].Lifecycle == workflow.LifecycleDraft && record.DocumentLifecycle {
|
||||||
v = append(v, vt[i])
|
v = append(v, vt[i])
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -706,24 +722,12 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
||||||
data.Versions = v
|
data.Versions = v
|
||||||
data.Attachments = a
|
data.Attachments = a
|
||||||
|
|
||||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
|
||||||
if err != nil {
|
|
||||||
response.WriteServerError(w, method, err)
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if document.Lifecycle == workflow.LifecycleLive {
|
if document.Lifecycle == workflow.LifecycleLive {
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: document.SpaceID,
|
SpaceID: document.SpaceID,
|
||||||
DocumentID: document.RefID,
|
DocumentID: document.RefID,
|
||||||
SourceType: activity.SourceTypeDocument,
|
SourceType: activity.SourceTypeDocument,
|
||||||
ActivityType: activity.TypeRead})
|
ActivityType: activity.TypeRead})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
|
@ -787,7 +791,7 @@ func (h *Handler) Export(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(export))
|
_, _ = w.Write([]byte(export))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duplicate makes a copy of a document.
|
// Duplicate makes a copy of a document.
|
||||||
|
@ -826,10 +830,10 @@ func (h *Handler) Duplicate(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
var ok bool
|
||||||
if err != nil {
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
response.WriteServerError(w, method, err)
|
if !ok {
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1015,3 +1019,225 @@ func (h *Handler) Duplicate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
response.WriteEmpty(w)
|
response.WriteEmpty(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pin marks existing document with sequence number so that it
|
||||||
|
// appears at the top-most space view.
|
||||||
|
func (h *Handler) Pin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "document.Pin"
|
||||||
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
|
documentID := request.Param(r, "documentID")
|
||||||
|
if len(documentID) == 0 {
|
||||||
|
response.WriteMissingDataError(w, method, "documentID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
|
response.WriteServerError(w, method, errors.New("unable to start transaction"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := h.Store.Document.Get(ctx, documentID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !permission.CanManageSpace(ctx, *h.Store, d.SpaceID) {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the next sequence number for this newly pinned document.
|
||||||
|
seq, err := h.Store.Document.PinSequence(ctx, d.SpaceID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.Store.Document.Pin(ctx, documentID, seq+1)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
|
SpaceID: d.SpaceID,
|
||||||
|
DocumentID: documentID,
|
||||||
|
SourceType: activity.SourceTypeDocument,
|
||||||
|
ActivityType: activity.TypePinned})
|
||||||
|
|
||||||
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
|
h.Store.Audit.Record(ctx, audit.EventTypeDocPinAdd)
|
||||||
|
|
||||||
|
response.WriteEmpty(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpin removes an existing document from the space pinned list.
|
||||||
|
func (h *Handler) Unpin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "document.Unpin"
|
||||||
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
|
documentID := request.Param(r, "documentID")
|
||||||
|
if len(documentID) == 0 {
|
||||||
|
response.WriteMissingDataError(w, method, "documentID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
|
response.WriteServerError(w, method, errors.New("unable to start transaction"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := h.Store.Document.Get(ctx, documentID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !permission.CanManageSpace(ctx, *h.Store, d.SpaceID) {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.Store.Document.Unpin(ctx, documentID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
|
SpaceID: d.SpaceID,
|
||||||
|
DocumentID: documentID,
|
||||||
|
SourceType: activity.SourceTypeDocument,
|
||||||
|
ActivityType: activity.TypeUnpinned})
|
||||||
|
|
||||||
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
|
h.Store.Audit.Record(ctx, audit.EventTypeDocPinRemove)
|
||||||
|
|
||||||
|
response.WriteEmpty(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PinMove moves pinned document up or down in the sequence.
|
||||||
|
func (h *Handler) PinMove(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "document.PinMove"
|
||||||
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
|
documentID := request.Param(r, "documentID")
|
||||||
|
if len(documentID) == 0 {
|
||||||
|
response.WriteMissingDataError(w, method, "documentID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
direction := request.Query(r, "direction")
|
||||||
|
if len(direction) == 0 {
|
||||||
|
response.WriteMissingDataError(w, method, "direction")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||||
|
if !ok {
|
||||||
|
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||||
|
response.WriteServerError(w, method, errors.New("unable to start transaction"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := h.Store.Document.Get(ctx, documentID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !permission.CanManageSpace(ctx, *h.Store, d.SpaceID) {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all pinned documents in the space.
|
||||||
|
pinnedDocs, err := h.Store.Document.Pinned(ctx, d.SpaceID)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort document list by sequence.
|
||||||
|
sort.Sort(doc.BySeq(pinnedDocs))
|
||||||
|
|
||||||
|
// Resequence the documents.
|
||||||
|
for i := range pinnedDocs {
|
||||||
|
if pinnedDocs[i].RefID == documentID {
|
||||||
|
if direction == "u" {
|
||||||
|
if i-1 >= 0 {
|
||||||
|
me := pinnedDocs[i].Sequence
|
||||||
|
target := pinnedDocs[i-1].Sequence
|
||||||
|
|
||||||
|
pinnedDocs[i-1].Sequence = me
|
||||||
|
pinnedDocs[i].Sequence = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if direction == "d" {
|
||||||
|
if i+1 < len(pinnedDocs) {
|
||||||
|
me := pinnedDocs[i].Sequence
|
||||||
|
target := pinnedDocs[i+1].Sequence
|
||||||
|
|
||||||
|
pinnedDocs[i+1].Sequence = me
|
||||||
|
pinnedDocs[i].Sequence = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort document list by sequence.
|
||||||
|
sort.Sort(doc.BySeq(pinnedDocs))
|
||||||
|
|
||||||
|
// Save the resequenced documents.
|
||||||
|
for i := range pinnedDocs {
|
||||||
|
err = h.Store.Document.Pin(ctx, pinnedDocs[i].RefID, i+1)
|
||||||
|
if err != nil {
|
||||||
|
h.Runtime.Rollback(ctx.Transaction)
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
|
SpaceID: d.SpaceID,
|
||||||
|
DocumentID: documentID,
|
||||||
|
SourceType: activity.SourceTypeDocument,
|
||||||
|
ActivityType: activity.TypePinSequence})
|
||||||
|
|
||||||
|
h.Store.Audit.Record(ctx, audit.EventTypeDocPinChange)
|
||||||
|
|
||||||
|
h.Runtime.Commit(ctx.Transaction)
|
||||||
|
|
||||||
|
response.WriteEmpty(w)
|
||||||
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ func BuildExport(ctx domain.RequestContext, s store.Store, spec exportSpec) (htm
|
||||||
export.WriteString(`<meta charset="utf-8">`)
|
export.WriteString(`<meta charset="utf-8">`)
|
||||||
export.WriteString(`<meta http-equiv="X-UA-Compatible" content="IE=edge">`)
|
export.WriteString(`<meta http-equiv="X-UA-Compatible" content="IE=edge">`)
|
||||||
export.WriteString("<title>")
|
export.WriteString("<title>")
|
||||||
export.WriteString("Documize Export")
|
export.WriteString("Documize Community Export")
|
||||||
export.WriteString("</title>")
|
export.WriteString("</title>")
|
||||||
export.WriteString("<style>")
|
export.WriteString("<style>")
|
||||||
export.WriteString(baseCSS)
|
export.WriteString(baseCSS)
|
||||||
|
@ -96,7 +96,7 @@ func BuildExport(ctx domain.RequestContext, s store.Store, spec exportSpec) (htm
|
||||||
|
|
||||||
// Show title and timestamp.
|
// Show title and timestamp.
|
||||||
generated := time.Now().UTC().Format(time.ANSIC)
|
generated := time.Now().UTC().Format(time.ANSIC)
|
||||||
export.WriteString(fmt.Sprintf("<h1 class='export-h1'>%s</h1>", "Documize Export"))
|
export.WriteString(fmt.Sprintf("<h1 class='export-h1'>%s</h1>", "Documize Community Export"))
|
||||||
export.WriteString(fmt.Sprintf("<div class='export-stamp'>%v</div>", generated))
|
export.WriteString(fmt.Sprintf("<div class='export-stamp'>%v</div>", generated))
|
||||||
|
|
||||||
// Spit out table of contents.
|
// Spit out table of contents.
|
||||||
|
|
|
@ -16,10 +16,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
"github.com/documize/community/domain/store"
|
"github.com/documize/community/domain/store"
|
||||||
"github.com/documize/community/model/doc"
|
"github.com/documize/community/model/doc"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Store provides data access to space category information.
|
// Store provides data access to space category information.
|
||||||
|
@ -36,10 +37,12 @@ func (s Store) Add(ctx domain.RequestContext, d doc.Document) (err error) {
|
||||||
|
|
||||||
_, err = ctx.Transaction.Exec(s.Bind(`
|
_, err = ctx.Transaction.Exec(s.Bind(`
|
||||||
INSERT INTO dmz_doc (c_refid, c_orgid, c_spaceid, c_userid, c_job, c_location, c_name, c_desc, c_slug, c_tags,
|
INSERT INTO dmz_doc (c_refid, c_orgid, c_spaceid, c_userid, c_job, c_location, c_name, c_desc, c_slug, c_tags,
|
||||||
c_template, c_protection, c_approval, c_lifecycle, c_versioned, c_versionid, c_versionorder, c_groupid, c_created, c_revised)
|
c_template, c_protection, c_approval, c_lifecycle, c_versioned, c_versionid, c_versionorder, c_seq, c_groupid,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
c_created, c_revised)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||||
d.RefID, d.OrgID, d.SpaceID, d.UserID, d.Job, d.Location, d.Name, d.Excerpt, d.Slug, d.Tags,
|
d.RefID, d.OrgID, d.SpaceID, d.UserID, d.Job, d.Location, d.Name, d.Excerpt, d.Slug, d.Tags,
|
||||||
d.Template, d.Protection, d.Approval, d.Lifecycle, d.Versioned, d.VersionID, d.VersionOrder, d.GroupID, d.Created, d.Revised)
|
d.Template, d.Protection, d.Approval, d.Lifecycle, d.Versioned, d.VersionID, d.VersionOrder, d.Sequence,
|
||||||
|
d.GroupID, d.Created, d.Revised)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "execute insert document")
|
err = errors.Wrap(err, "execute insert document")
|
||||||
|
@ -55,7 +58,7 @@ func (s Store) Get(ctx domain.RequestContext, id string) (document doc.Document,
|
||||||
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
||||||
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
||||||
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
||||||
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_doc
|
FROM dmz_doc
|
||||||
WHERE c_orgid=? AND c_refid=?`),
|
WHERE c_orgid=? AND c_refid=?`),
|
||||||
ctx.OrgID, id)
|
ctx.OrgID, id)
|
||||||
|
@ -78,7 +81,7 @@ func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
|
||||||
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
||||||
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
||||||
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
||||||
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_doc
|
FROM dmz_doc
|
||||||
WHERE c_orgid=? AND c_template=`+s.IsFalse()+` AND c_spaceid IN
|
WHERE c_orgid=? AND c_template=`+s.IsFalse()+` AND c_spaceid IN
|
||||||
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid=? AND c_refid IN
|
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid=? AND c_refid IN
|
||||||
|
@ -111,7 +114,7 @@ func (s Store) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (docu
|
||||||
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
||||||
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
||||||
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
||||||
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_doc
|
FROM dmz_doc
|
||||||
WHERE c_orgid=? AND c_spaceid=? AND c_template=`+s.IsTrue()+` AND c_lifecycle=1
|
WHERE c_orgid=? AND c_spaceid=? AND c_template=`+s.IsTrue()+` AND c_lifecycle=1
|
||||||
AND c_spaceid IN
|
AND c_spaceid IN
|
||||||
|
@ -167,7 +170,8 @@ func (s Store) Update(ctx domain.RequestContext, document doc.Document) (err err
|
||||||
c_spaceid=:spaceid, c_userid=:userid, c_job=:job, c_location=:location, c_name=:name,
|
c_spaceid=:spaceid, c_userid=:userid, c_job=:job, c_location=:location, c_name=:name,
|
||||||
c_desc=:excerpt, c_slug=:slug, c_tags=:tags, c_template=:template,
|
c_desc=:excerpt, c_slug=:slug, c_tags=:tags, c_template=:template,
|
||||||
c_protection=:protection, c_approval=:approval, c_lifecycle=:lifecycle,
|
c_protection=:protection, c_approval=:approval, c_lifecycle=:lifecycle,
|
||||||
c_versioned=:versioned, c_versionid=:versionid, c_versionorder=:versionorder,
|
c_versioned=:versioned, c_versionid=:versionid, c_versionorder=:versionorder,
|
||||||
|
c_seq=:sequence,
|
||||||
c_groupid=:groupid, c_revised=:revised
|
c_groupid=:groupid, c_revised=:revised
|
||||||
WHERE c_orgid=:orgid AND c_refid=:refid`),
|
WHERE c_orgid=:orgid AND c_refid=:refid`),
|
||||||
&document)
|
&document)
|
||||||
|
@ -250,31 +254,11 @@ func (s Store) MoveActivity(ctx domain.RequestContext, documentID, oldSpaceID, n
|
||||||
// Delete removes the specified document.
|
// Delete removes the specified document.
|
||||||
// Remove document pages, revisions, attachments, updates the search subsystem.
|
// Remove document pages, revisions, attachments, updates the search subsystem.
|
||||||
func (s Store) Delete(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
func (s Store) Delete(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
||||||
rows, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_section WHERE c_docid='%s' AND c_orgid='%s'", documentID, ctx.OrgID))
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_section WHERE c_orgid=? AND c_docid=?"), ctx.OrgID, documentID)
|
||||||
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_section_revision WHERE c_orgid=? AND c_docid=?"), ctx.OrgID, documentID)
|
||||||
if err != nil {
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_doc_attachment WHERE c_orgid=? AND c_docid=?"), ctx.OrgID, documentID)
|
||||||
return
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_category_member WHERE c_orgid=? AND c_docid=?"), ctx.OrgID, documentID)
|
||||||
}
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_doc_vote WHERE c_orgid=? AND c_docid=?"), ctx.OrgID, documentID)
|
||||||
|
|
||||||
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_section_revision WHERE c_docid='%s' AND c_orgid='%s'", documentID, ctx.OrgID))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_attachment WHERE c_docid='%s' AND c_orgid='%s'", documentID, ctx.OrgID))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_docid='%s' AND c_orgid='%s'", documentID, ctx.OrgID))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_vote WHERE c_docid='%s' AND c_orgid='%s'", documentID, ctx.OrgID))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.DeleteConstrained(ctx.Transaction, "dmz_doc", ctx.OrgID, documentID)
|
return s.DeleteConstrained(ctx.Transaction, "dmz_doc", ctx.OrgID, documentID)
|
||||||
}
|
}
|
||||||
|
@ -282,25 +266,10 @@ func (s Store) Delete(ctx domain.RequestContext, documentID string) (rows int64,
|
||||||
// DeleteBySpace removes all documents for given space.
|
// DeleteBySpace removes all documents for given space.
|
||||||
// Remove document pages, revisions, attachments, updates the search subsystem.
|
// Remove document pages, revisions, attachments, updates the search subsystem.
|
||||||
func (s Store) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
func (s Store) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
||||||
rows, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_section WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid='%s' AND c_orgid='%s')", spaceID, ctx.OrgID))
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_section WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid=? AND c_orgid=?)"), spaceID, ctx.OrgID)
|
||||||
if err != nil {
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_section_revision WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid=? AND c_orgid=?)"), spaceID, ctx.OrgID)
|
||||||
return
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_doc_attachment WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid=? AND c_orgid=?)"), spaceID, ctx.OrgID)
|
||||||
}
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_doc_vote WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid=? AND c_orgid=?)"), spaceID, ctx.OrgID)
|
||||||
|
|
||||||
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_section_revision WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid='%s' AND c_orgid='%s')", spaceID, ctx.OrgID))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_attachment WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid='%s' AND c_orgid='%s')", spaceID, ctx.OrgID))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_vote WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid='%s' AND c_orgid='%s')", spaceID, ctx.OrgID))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.DeleteConstrained(ctx.Transaction, "dmz_doc", ctx.OrgID, spaceID)
|
return s.DeleteConstrained(ctx.Transaction, "dmz_doc", ctx.OrgID, spaceID)
|
||||||
}
|
}
|
||||||
|
@ -331,3 +300,78 @@ func (s Store) GetVersions(ctx domain.RequestContext, groupID string) (v []doc.V
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pin allocates sequence number to specified document so that it appears
|
||||||
|
// at the documents list.
|
||||||
|
func (s Store) Pin(ctx domain.RequestContext, documentID string, seq int) (err error) {
|
||||||
|
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_doc SET c_seq=? WHERE c_orgid=? AND c_refid=?"),
|
||||||
|
seq, ctx.OrgID, documentID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "document.store.Pin")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpin resets sequence number for given document.
|
||||||
|
func (s Store) Unpin(ctx domain.RequestContext, documentID string) (err error) {
|
||||||
|
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_doc SET c_seq=? WHERE c_orgid=? AND c_refid=?"),
|
||||||
|
doc.Unsequenced, ctx.OrgID, documentID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "document.store.Unpin")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PinSequence fectches pinned documents and returns current
|
||||||
|
// maximum sequence value.
|
||||||
|
func (s Store) PinSequence(ctx domain.RequestContext, spaceID string) (max int, err error) {
|
||||||
|
max = 0
|
||||||
|
|
||||||
|
err = s.Runtime.Db.Get(&max, s.Bind(`
|
||||||
|
SELECT COALESCE(MAX(c_seq), 0)
|
||||||
|
FROM dmz_doc
|
||||||
|
WHERE c_orgid=? AND c_spaceid=?
|
||||||
|
AND c_seq != 99999`),
|
||||||
|
ctx.OrgID, spaceID)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
max = doc.Unsequenced
|
||||||
|
err = errors.Wrap(err, "document.store.PinSequence")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pinned documents for space are fetched.
|
||||||
|
func (s Store) Pinned(ctx domain.RequestContext, spaceID string) (d []doc.Document, err error) {
|
||||||
|
d = []doc.Document{}
|
||||||
|
|
||||||
|
err = s.Runtime.Db.Select(&d, s.Bind(`
|
||||||
|
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_userid AS userid,
|
||||||
|
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
|
||||||
|
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
|
||||||
|
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
|
||||||
|
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid,
|
||||||
|
c_created AS created, c_revised AS revised
|
||||||
|
FROM dmz_doc
|
||||||
|
WHERE c_orgid=? AND c_spaceid=?
|
||||||
|
AND c_seq != 99999
|
||||||
|
ORDER BY c_seq`),
|
||||||
|
ctx.OrgID, spaceID)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "document.store.Pinned")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ package group
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
|
@ -104,7 +103,10 @@ func (s Store) Delete(ctx domain.RequestContext, refID string) (rows int64, err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_group_member WHERE c_orgid='%s' AND c_groupid='%s'", ctx.OrgID, refID))
|
|
||||||
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_group_member WHERE c_orgid=? AND c_groupid=?"), ctx.OrgID, refID)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGroupMembers returns all user associated with given group.
|
// GetGroupMembers returns all user associated with given group.
|
||||||
|
@ -143,15 +145,8 @@ func (s Store) JoinGroup(ctx domain.RequestContext, groupID, userID string) (err
|
||||||
|
|
||||||
// LeaveGroup removes user from group.
|
// LeaveGroup removes user from group.
|
||||||
func (s Store) LeaveGroup(ctx domain.RequestContext, groupID, userID string) (err error) {
|
func (s Store) LeaveGroup(ctx domain.RequestContext, groupID, userID string) (err error) {
|
||||||
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_group_member WHERE c_orgid='%s' AND c_groupid='%s' AND c_userid='%s'",
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_group_member WHERE c_orgid=? AND c_groupid=? AND c_userid=?"),
|
||||||
ctx.OrgID, groupID, userID))
|
ctx.OrgID, groupID, userID)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Wrap(err, "clear group member")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -182,16 +177,8 @@ func (s Store) GetMembers(ctx domain.RequestContext) (r []group.Record, err erro
|
||||||
|
|
||||||
// RemoveUserGroups remove user from all group.
|
// RemoveUserGroups remove user from all group.
|
||||||
func (s Store) RemoveUserGroups(ctx domain.RequestContext, userID string) (err error) {
|
func (s Store) RemoveUserGroups(ctx domain.RequestContext, userID string) (err error) {
|
||||||
_, err = s.DeleteWhere(ctx.Transaction,
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_group_member WHERE c_orgid=? AND c_userid=?"),
|
||||||
fmt.Sprintf("DELETE FROM dmz_group_member WHERE c_orgid='%s' AND c_userid='%s'",
|
ctx.OrgID, userID)
|
||||||
ctx.OrgID, userID))
|
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Wrap(err, "RemoveUserGroups")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ package link
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -156,12 +155,18 @@ func (s Store) MarkOrphanAttachmentLink(ctx domain.RequestContext, attachmentID
|
||||||
|
|
||||||
// DeleteSourcePageLinks removes saved links for given source.
|
// DeleteSourcePageLinks removes saved links for given source.
|
||||||
func (s Store) DeleteSourcePageLinks(ctx domain.RequestContext, pageID string) (rows int64, err error) {
|
func (s Store) DeleteSourcePageLinks(ctx domain.RequestContext, pageID string) (rows int64, err error) {
|
||||||
return s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_link WHERE c_orgid='%s' AND c_sourcesectionid='%s'", ctx.OrgID, pageID))
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_doc_link WHERE c_orgid=? AND c_sourcesectionid=?"),
|
||||||
|
ctx.OrgID, pageID)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSourceDocumentLinks removes saved links for given document.
|
// DeleteSourceDocumentLinks removes saved links for given document.
|
||||||
func (s Store) DeleteSourceDocumentLinks(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
func (s Store) DeleteSourceDocumentLinks(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
||||||
return s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_link WHERE c_orgid='%s' AND c_sourcedocid='%s'", ctx.OrgID, documentID))
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_doc_link WHERE c_orgid=? AND c_sourcedocid=?"),
|
||||||
|
ctx.OrgID, documentID)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteLink removes saved link from the store.
|
// DeleteLink removes saved link from the store.
|
||||||
|
|
|
@ -61,7 +61,7 @@ background-color: #f6f6f6;
|
||||||
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
||||||
Document Approval Role Granted
|
{{.Subject}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
||||||
|
@ -69,19 +69,14 @@ background-color: #f6f6f6;
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
<p>You are requested to approve all changes to the following document:</p>
|
<p>{{.ActionText}}</p>
|
||||||
<p style="font-weight: bold;">{{.Document}}</p>
|
<p style="font-weight: bold;">{{.Document}}</p>
|
||||||
<p>{{.Inviter}}</p>
|
<p>{{.Inviter}}</p>
|
||||||
</td>
|
</td>1
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">View document</a>
|
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">{{.ClickHere}}</a>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px; color: #7a8184;" valign="top">
|
|
||||||
Have any questions? <a href="mailto:{{.SenderEmail}}" style="color: #7a8184;">Contact Us</a>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -16,6 +16,7 @@ package mail
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/i18n"
|
||||||
"github.com/documize/community/domain/smtp"
|
"github.com/documize/community/domain/smtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,32 +27,32 @@ func (m *Mailer) DocumentApprover(recipient, inviterName, inviterEmail, url, doc
|
||||||
|
|
||||||
// check inviter name
|
// check inviter name
|
||||||
if inviterName == "Hello You" || len(inviterName) == 0 {
|
if inviterName == "Hello You" || len(inviterName) == 0 {
|
||||||
inviterName = "Your colleague"
|
inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender")
|
||||||
}
|
}
|
||||||
|
|
||||||
em := smtp.EmailMessage{}
|
em := smtp.EmailMessage{}
|
||||||
em.Subject = fmt.Sprintf("%s has granted you document approval", inviterName)
|
em.Subject = i18n.Localize(m.Context.Locale, "mail_template_approval", inviterName)
|
||||||
em.ToEmail = recipient
|
em.ToEmail = recipient
|
||||||
em.ToName = recipient
|
em.ToName = recipient
|
||||||
em.ReplyTo = inviterEmail
|
em.ReplyTo = inviterEmail
|
||||||
em.ReplyName = inviterName
|
em.ReplyName = inviterName
|
||||||
|
|
||||||
if IsBlockedEmailDomain(em.ToEmail) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters := struct {
|
parameters := struct {
|
||||||
Subject string
|
Subject string
|
||||||
Inviter string
|
Inviter string
|
||||||
URL string
|
URL string
|
||||||
Document string
|
Document string
|
||||||
SenderEmail string
|
SenderEmail string
|
||||||
|
ActionText string
|
||||||
|
ClickHere string
|
||||||
}{
|
}{
|
||||||
em.Subject,
|
em.Subject,
|
||||||
inviterName,
|
inviterName,
|
||||||
url,
|
url,
|
||||||
document,
|
document,
|
||||||
m.Config.SenderEmail,
|
m.Config.SenderEmail,
|
||||||
|
i18n.Localize(m.Context.Locale, "mail_template_approval_explain"),
|
||||||
|
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||||
}
|
}
|
||||||
|
|
||||||
html, err := m.ParseTemplate("mail/document-approver.html", parameters)
|
html, err := m.ParseTemplate("mail/document-approver.html", parameters)
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width" />
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
||||||
<title>Your Documize Invitation</title>
|
|
||||||
|
|
||||||
|
|
||||||
<style type="text/css">
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 640px) {
|
|
||||||
h1 {
|
|
||||||
font-weight: 600 !important; margin: 20px 0 5px !important;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-weight: 600 !important; margin: 20px 0 5px !important;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-weight: 600 !important; margin: 20px 0 5px !important;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
font-weight: 600 !important; margin: 20px 0 5px !important;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 22px !important;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: 18px !important;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 16px !important;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding: 10px !important;
|
|
||||||
}
|
|
||||||
.content-wrap {
|
|
||||||
padding: 10px !important;
|
|
||||||
}
|
|
||||||
.invoice {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6; background: #f6f6f6; margin: 0; padding: 0;">
|
|
||||||
|
|
||||||
<table class="body-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background: #f6f6f6; margin: 0; padding: 0;">
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0;" valign="top"></td>
|
|
||||||
<td class="container" width="600" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto; padding: 0;" valign="top">
|
|
||||||
<div class="content" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
|
|
||||||
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
|
||||||
{{.Inviter}} has invited you to use Documize
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
|
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
|
||||||
Documize provides easy access to all your Word documents so everyone can find and edit documents - no more network drives.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
|
||||||
<strong style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">Your co-workers are using Documize right now.</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
|
||||||
Use your email address as your password ({{.Email}}).
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
|
||||||
<a href="{{.Url}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to access Documize</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px; color: #7a8184;" valign="top">
|
|
||||||
Have any questions? <a href="mailto:{{.SenderEmail}}" style="color: #7a8184;">Contact Us</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0;" valign="top"></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -59,25 +59,15 @@ background-color: #f6f6f6;
|
||||||
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
||||||
{{.Inviter}} has invited you to their Documize account
|
{{.Subject}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
||||||
<td class="content-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
|
<td class="content-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
|
||||||
Documize provides secure and easy access to all your documentation so everyone can find and edit the same thing.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to access Documize</a>
|
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">{{.ClickHere}}</a>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px; color: #7a8184;" valign="top">
|
|
||||||
Have any questions? <a href="mailto:{{.SenderEmail}}" style="color: #7a8184;">Contact Us</a>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -59,35 +59,20 @@ background-color: #f6f6f6;
|
||||||
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
||||||
{{.Inviter}} has invited you to Documize
|
{{.Subject}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
||||||
<td class="content-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
|
<td class="content-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
|
||||||
Documize provides secure and easy access to all your documentation so everyone can find and edit the same thing.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
<strong style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">Your co-workers are using Documize right now.</strong>
|
{{.Password}}
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
|
||||||
Your temporary password: {{.Password}}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to access Documize</a>
|
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">{{.ClickHere}}</a>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px; color: #7a8184;" valign="top">
|
|
||||||
Have any questions? <a href="mailto:{{.SenderEmail}}" style="color: #7a8184;">Contact Us</a>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -13,15 +13,17 @@ package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/asset"
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
"github.com/documize/community/core/mail"
|
"github.com/documize/community/core/mail"
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
"github.com/documize/community/domain/setting"
|
"github.com/documize/community/domain/setting"
|
||||||
ds "github.com/documize/community/domain/smtp"
|
ds "github.com/documize/community/domain/smtp"
|
||||||
"github.com/documize/community/domain/store"
|
"github.com/documize/community/domain/store"
|
||||||
"github.com/documize/community/server/web"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mailer provides emailing facilities
|
// Mailer provides emailing facilities
|
||||||
|
@ -43,15 +45,15 @@ func (m *Mailer) Initialize() {
|
||||||
func (m *Mailer) ParseTemplate(filename string, params interface{}) (html string, err error) {
|
func (m *Mailer) ParseTemplate(filename string, params interface{}) (html string, err error) {
|
||||||
html = ""
|
html = ""
|
||||||
|
|
||||||
file, err := web.ReadFile(filename)
|
content, _, err := asset.FetchStatic(m.Runtime.Assets, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, fmt.Sprintf("missing %s", filename))
|
||||||
|
m.Runtime.Log.Error("failed to load mail template", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
emailTemplate := string(file)
|
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
|
t := template.Must(template.New("emailTemplate").Parse(content))
|
||||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
|
||||||
t.Execute(buffer, ¶ms)
|
t.Execute(buffer, ¶ms)
|
||||||
|
|
||||||
html = buffer.String()
|
html = buffer.String()
|
||||||
|
|
|
@ -61,30 +61,15 @@ background-color: #f6f6f6;
|
||||||
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
||||||
Your Documize password reset request
|
{{.Subject}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
||||||
<td class="content-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
|
<td class="content-wrap" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
|
||||||
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
|
||||||
Someone has requested to reset your Documize password. If this was you, then please click below to specify a new password.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
|
||||||
<strong style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">If you did not request a password reset, please change your password and contact us.</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Click here to reset your password</a>
|
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">{{.ClickHere}}</a>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px; color: #7a8184;" valign="top">
|
|
||||||
Have any questions? <a href="mailto:{{.SenderEmail}}" style="color: #7a8184;">Contact Us</a>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -61,7 +61,7 @@ background-color: #f6f6f6;
|
||||||
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
||||||
{{.Inviter}} has shared {{.Folder}} with you
|
{{.Subject}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
||||||
|
@ -75,12 +75,7 @@ background-color: #f6f6f6;
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Login to Documize</a>
|
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">{{.ClickHere}}</a>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px; color: #7a8184;" valign="top">
|
|
||||||
Have any questions? <a href="mailto:{{.SenderEmail}}" style="color: #7a8184;">Contact Us</a>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -61,7 +61,7 @@ background-color: #f6f6f6;
|
||||||
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
<table class="main" width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background: #fff; margin: 0; padding: 0; border: 1px solid #e9e9e9;">
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
<td class="alert alert-warning" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background: #1b75bb; margin: 0; padding: 20px;" align="center" valign="top">
|
||||||
{{.Inviter}} has shared {{.Folder}} with you on Documize
|
{{.Subject}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; margin: 0; padding: 0;">
|
||||||
|
@ -74,19 +74,9 @@ background-color: #f6f6f6;
|
||||||
</strong>
|
</strong>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
|
||||||
Documize provides secure and easy access to all your documentation so everyone can find and edit the same thing.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
|
||||||
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">Go to Documize</a>
|
<a href="{{.URL}}" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; background: #4ccb6a; margin: 0; padding: 0; border-color: #4ccb6a; border-style: solid; border-width: 10px 20px;">{{.ClickHere}}</a>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; padding: 0;">
|
|
||||||
<td class="content-block" style="font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px; color: #7a8184;" valign="top">
|
|
||||||
Have any questions? <a href="mailto:team@documize.com" style="color: #7a8184;">Contact Documize</a>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -14,6 +14,7 @@ package mail
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/i18n"
|
||||||
"github.com/documize/community/domain/smtp"
|
"github.com/documize/community/domain/smtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,20 +25,16 @@ func (m *Mailer) ShareSpaceExistingUser(recipient, inviterName, inviterEmail, ur
|
||||||
|
|
||||||
// check inviter name
|
// check inviter name
|
||||||
if inviterName == "Hello You" || len(inviterName) == 0 {
|
if inviterName == "Hello You" || len(inviterName) == 0 {
|
||||||
inviterName = "Your colleague"
|
inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender")
|
||||||
}
|
}
|
||||||
|
|
||||||
em := smtp.EmailMessage{}
|
em := smtp.EmailMessage{}
|
||||||
em.Subject = fmt.Sprintf("%s has shared %s with you", inviterName, folder)
|
em.Subject = i18n.Localize(m.Context.Locale, "mail_template_shared", inviterName, folder)
|
||||||
em.ToEmail = recipient
|
em.ToEmail = recipient
|
||||||
em.ToName = recipient
|
em.ToName = recipient
|
||||||
em.ReplyTo = inviterEmail
|
em.ReplyTo = inviterEmail
|
||||||
em.ReplyName = inviterName
|
em.ReplyName = inviterName
|
||||||
|
|
||||||
if IsBlockedEmailDomain(em.ToEmail) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters := struct {
|
parameters := struct {
|
||||||
Subject string
|
Subject string
|
||||||
Inviter string
|
Inviter string
|
||||||
|
@ -45,6 +42,7 @@ func (m *Mailer) ShareSpaceExistingUser(recipient, inviterName, inviterEmail, ur
|
||||||
Folder string
|
Folder string
|
||||||
Intro string
|
Intro string
|
||||||
SenderEmail string
|
SenderEmail string
|
||||||
|
ClickHere string
|
||||||
}{
|
}{
|
||||||
em.Subject,
|
em.Subject,
|
||||||
inviterName,
|
inviterName,
|
||||||
|
@ -52,6 +50,7 @@ func (m *Mailer) ShareSpaceExistingUser(recipient, inviterName, inviterEmail, ur
|
||||||
folder,
|
folder,
|
||||||
intro,
|
intro,
|
||||||
m.Config.SenderEmail,
|
m.Config.SenderEmail,
|
||||||
|
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||||
}
|
}
|
||||||
|
|
||||||
html, err := m.ParseTemplate("mail/share-space-existing-user.html", parameters)
|
html, err := m.ParseTemplate("mail/share-space-existing-user.html", parameters)
|
||||||
|
@ -77,20 +76,16 @@ func (m *Mailer) ShareSpaceNewUser(recipient, inviterName, inviterEmail, url, sp
|
||||||
|
|
||||||
// check inviter name
|
// check inviter name
|
||||||
if inviterName == "Hello You" || len(inviterName) == 0 {
|
if inviterName == "Hello You" || len(inviterName) == 0 {
|
||||||
inviterName = "Your colleague"
|
inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender")
|
||||||
}
|
}
|
||||||
|
|
||||||
em := smtp.EmailMessage{}
|
em := smtp.EmailMessage{}
|
||||||
em.Subject = fmt.Sprintf("%s has shared %s with you on Documize", inviterName, space)
|
em.Subject = i18n.Localize(m.Context.Locale, "mail_template_invited", inviterName, space)
|
||||||
em.ToEmail = recipient
|
em.ToEmail = recipient
|
||||||
em.ToName = recipient
|
em.ToName = recipient
|
||||||
em.ReplyTo = inviterEmail
|
em.ReplyTo = inviterEmail
|
||||||
em.ReplyName = inviterName
|
em.ReplyName = inviterName
|
||||||
|
|
||||||
if IsBlockedEmailDomain(em.ToEmail) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters := struct {
|
parameters := struct {
|
||||||
Subject string
|
Subject string
|
||||||
Inviter string
|
Inviter string
|
||||||
|
@ -98,6 +93,7 @@ func (m *Mailer) ShareSpaceNewUser(recipient, inviterName, inviterEmail, url, sp
|
||||||
Invitation string
|
Invitation string
|
||||||
Folder string
|
Folder string
|
||||||
SenderEmail string
|
SenderEmail string
|
||||||
|
ClickHere string
|
||||||
}{
|
}{
|
||||||
em.Subject,
|
em.Subject,
|
||||||
inviterName,
|
inviterName,
|
||||||
|
@ -105,6 +101,7 @@ func (m *Mailer) ShareSpaceNewUser(recipient, inviterName, inviterEmail, url, sp
|
||||||
invitationMessage,
|
invitationMessage,
|
||||||
space,
|
space,
|
||||||
m.Config.SenderEmail,
|
m.Config.SenderEmail,
|
||||||
|
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||||
}
|
}
|
||||||
|
|
||||||
html, err := m.ParseTemplate("mail/share-space-new-user.html", parameters)
|
html, err := m.ParseTemplate("mail/share-space-new-user.html", parameters)
|
||||||
|
|
|
@ -14,6 +14,7 @@ package mail
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/i18n"
|
||||||
"github.com/documize/community/domain/smtp"
|
"github.com/documize/community/domain/smtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,20 +25,16 @@ func (m *Mailer) InviteNewUser(recipient, inviterName, inviterEmail, url, userna
|
||||||
|
|
||||||
// check inviter name
|
// check inviter name
|
||||||
if inviterName == "Hello You" || len(inviterName) == 0 {
|
if inviterName == "Hello You" || len(inviterName) == 0 {
|
||||||
inviterName = "Your colleague"
|
inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender")
|
||||||
}
|
}
|
||||||
|
|
||||||
em := smtp.EmailMessage{}
|
em := smtp.EmailMessage{}
|
||||||
em.Subject = fmt.Sprintf("%s has invited you to Documize", inviterName)
|
em.Subject = i18n.Localize(m.Context.Locale, "mail_template_user_invite", inviterName)
|
||||||
em.ToEmail = recipient
|
em.ToEmail = recipient
|
||||||
em.ToName = recipient
|
em.ToName = recipient
|
||||||
em.ReplyTo = inviterEmail
|
em.ReplyTo = inviterEmail
|
||||||
em.ReplyName = inviterName
|
em.ReplyName = inviterName
|
||||||
|
|
||||||
if IsBlockedEmailDomain(em.ToEmail) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters := struct {
|
parameters := struct {
|
||||||
Subject string
|
Subject string
|
||||||
Inviter string
|
Inviter string
|
||||||
|
@ -45,13 +42,15 @@ func (m *Mailer) InviteNewUser(recipient, inviterName, inviterEmail, url, userna
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
SenderEmail string
|
SenderEmail string
|
||||||
|
ClickHere string
|
||||||
}{
|
}{
|
||||||
em.Subject,
|
em.Subject,
|
||||||
inviterName,
|
inviterName,
|
||||||
url,
|
url,
|
||||||
recipient,
|
recipient,
|
||||||
password,
|
i18n.Localize(m.Context.Locale, "mail_template_password") + " " + password,
|
||||||
m.Config.SenderEmail,
|
m.Config.SenderEmail,
|
||||||
|
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||||
}
|
}
|
||||||
|
|
||||||
html, err := m.ParseTemplate("mail/invite-new-user.html", parameters)
|
html, err := m.ParseTemplate("mail/invite-new-user.html", parameters)
|
||||||
|
@ -77,30 +76,28 @@ func (m *Mailer) InviteExistingUser(recipient, inviterName, inviterEmail, url st
|
||||||
|
|
||||||
// check inviter name
|
// check inviter name
|
||||||
if inviterName == "Hello You" || len(inviterName) == 0 {
|
if inviterName == "Hello You" || len(inviterName) == 0 {
|
||||||
inviterName = "Your colleague"
|
inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender")
|
||||||
}
|
}
|
||||||
|
|
||||||
em := smtp.EmailMessage{}
|
em := smtp.EmailMessage{}
|
||||||
em.Subject = fmt.Sprintf("%s has invited you to their Documize account", inviterName)
|
em.Subject = i18n.Localize(m.Context.Locale, "mail_template_user_existing", inviterName)
|
||||||
em.ToEmail = recipient
|
em.ToEmail = recipient
|
||||||
em.ToName = recipient
|
em.ToName = recipient
|
||||||
em.ReplyTo = inviterEmail
|
em.ReplyTo = inviterEmail
|
||||||
em.ReplyName = inviterName
|
em.ReplyName = inviterName
|
||||||
|
|
||||||
if IsBlockedEmailDomain(em.ToEmail) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters := struct {
|
parameters := struct {
|
||||||
Subject string
|
Subject string
|
||||||
Inviter string
|
Inviter string
|
||||||
URL string
|
URL string
|
||||||
SenderEmail string
|
SenderEmail string
|
||||||
|
ClickHere string
|
||||||
}{
|
}{
|
||||||
em.Subject,
|
em.Subject,
|
||||||
inviterName,
|
inviterName,
|
||||||
url,
|
url,
|
||||||
m.Config.SenderEmail,
|
m.Config.SenderEmail,
|
||||||
|
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||||
}
|
}
|
||||||
|
|
||||||
html, err := m.ParseTemplate("mail/invite-existing-user.html", parameters)
|
html, err := m.ParseTemplate("mail/invite-existing-user.html", parameters)
|
||||||
|
@ -125,22 +122,20 @@ func (m *Mailer) PasswordReset(recipient, url string) {
|
||||||
m.Initialize()
|
m.Initialize()
|
||||||
|
|
||||||
em := smtp.EmailMessage{}
|
em := smtp.EmailMessage{}
|
||||||
em.Subject = "Documize password reset request"
|
em.Subject = i18n.Localize(m.Context.Locale, "mail_template_reset_password")
|
||||||
em.ToEmail = recipient
|
em.ToEmail = recipient
|
||||||
em.ToName = recipient
|
em.ToName = recipient
|
||||||
|
|
||||||
if IsBlockedEmailDomain(em.ToEmail) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters := struct {
|
parameters := struct {
|
||||||
Subject string
|
Subject string
|
||||||
URL string
|
URL string
|
||||||
SenderEmail string
|
SenderEmail string
|
||||||
|
ClickHere string
|
||||||
}{
|
}{
|
||||||
em.Subject,
|
em.Subject,
|
||||||
url,
|
url,
|
||||||
m.Config.SenderEmail,
|
m.Config.SenderEmail,
|
||||||
|
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||||
}
|
}
|
||||||
|
|
||||||
html, err := m.ParseTemplate("mail/password-reset.html", parameters)
|
html, err := m.ParseTemplate("mail/password-reset.html", parameters)
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -20,6 +20,7 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/asset"
|
||||||
"github.com/documize/community/core/env"
|
"github.com/documize/community/core/env"
|
||||||
"github.com/documize/community/core/response"
|
"github.com/documize/community/core/response"
|
||||||
"github.com/documize/community/core/uniqueid"
|
"github.com/documize/community/core/uniqueid"
|
||||||
|
@ -28,7 +29,6 @@ import (
|
||||||
"github.com/documize/community/domain/store"
|
"github.com/documize/community/domain/store"
|
||||||
om "github.com/documize/community/model/onboard"
|
om "github.com/documize/community/model/onboard"
|
||||||
"github.com/documize/community/model/permission"
|
"github.com/documize/community/model/permission"
|
||||||
"github.com/documize/community/server/web"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler contains the runtime information such as logging and database.
|
// Handler contains the runtime information such as logging and database.
|
||||||
|
@ -112,14 +112,14 @@ func (h *Handler) loadFile(data om.SampleData, filename string, v interface{}) {
|
||||||
|
|
||||||
// Reads file and unmarshals content as JSON.
|
// Reads file and unmarshals content as JSON.
|
||||||
func (h *Handler) unpackFile(filename string, v interface{}) (err error) {
|
func (h *Handler) unpackFile(filename string, v interface{}) (err error) {
|
||||||
data, err := web.Embed.Asset("bindata/onboard/" + filename)
|
content, _, err := asset.FetchStatic(h.Runtime.Assets, "onboard/"+filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("missing %s", filename))
|
err = errors.Wrap(err, fmt.Sprintf("missing %s", filename))
|
||||||
h.Runtime.Log.Error("failed to load file", err)
|
h.Runtime.Log.Error("failed to load file", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(data, &v)
|
err = json.Unmarshal([]byte(content), &v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("failed to read %s as JSON", filename))
|
err = errors.Wrap(err, fmt.Sprintf("failed to read %s as JSON", filename))
|
||||||
h.Runtime.Log.Error("failed to load file", err)
|
h.Runtime.Log.Error("failed to load file", err)
|
||||||
|
@ -204,12 +204,13 @@ func (h *Handler) processSampleData(data om.SampleData) (err error) {
|
||||||
h.Runtime.Log.Info(fmt.Sprintf("Installing (%d) categories", len(data.Category)))
|
h.Runtime.Log.Info(fmt.Sprintf("Installing (%d) categories", len(data.Category)))
|
||||||
for i := range data.Category {
|
for i := range data.Category {
|
||||||
_, err = data.Context.Transaction.Exec(h.Runtime.Db.Rebind(`
|
_, err = data.Context.Transaction.Exec(h.Runtime.Db.Rebind(`
|
||||||
INSERT INTO dmz_category (c_refid, c_orgid, c_spaceid, c_name, c_created, c_revised)
|
INSERT INTO dmz_category (c_refid, c_orgid, c_spaceid, c_name, c_default, c_created, c_revised)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)`),
|
VALUES (?, ?, ?, ?, ?, ?, ?)`),
|
||||||
h.getMappedID("category", data.Category[i].RefID),
|
h.getMappedID("category", data.Category[i].RefID),
|
||||||
data.Context.OrgID,
|
data.Context.OrgID,
|
||||||
h.getMappedID("space", data.Category[i].SpaceID),
|
h.getMappedID("space", data.Category[i].SpaceID),
|
||||||
data.Category[i].Name,
|
data.Category[i].Name,
|
||||||
|
data.Category[i].IsDefault,
|
||||||
data.Category[i].Created,
|
data.Category[i].Created,
|
||||||
data.Category[i].Revised)
|
data.Category[i].Revised)
|
||||||
|
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
package organization
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/documize/community/core/uniqueid"
|
|
||||||
"github.com/documize/community/model/org"
|
|
||||||
|
|
||||||
"github.com/documize/community/domain/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestSpace tests all space database operations.
|
|
||||||
|
|
||||||
func TestOrganization(t *testing.T) {
|
|
||||||
rt, s, ctx := test.SetupTest()
|
|
||||||
//Create a new organization
|
|
||||||
var err error
|
|
||||||
org := org.Organization{}
|
|
||||||
orgID := uniqueid.Generate()
|
|
||||||
|
|
||||||
t.Run("AddOrginization", func(t *testing.T) {
|
|
||||||
ctx.Transaction, err = rt.Db.Beginx()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
org.RefID = orgID
|
|
||||||
org.Company = "test"
|
|
||||||
org.Title = "test"
|
|
||||||
org.Message = "test"
|
|
||||||
org.Domain = "testDomain"
|
|
||||||
org.Active = true
|
|
||||||
|
|
||||||
err = s.Organization.AddOrganization(ctx, org)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
t.Error("failed to add org organization")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
|
||||||
|
|
||||||
orgGot, err := s.Organization.GetOrganization(ctx, org.RefID)
|
|
||||||
if err != nil || org.Title != orgGot.Title {
|
|
||||||
t.Error("failed to get org organization")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetOrganizationByDomain", func(t *testing.T) {
|
|
||||||
orgGot, err := s.Organization.GetOrganizationByDomain("testDomain")
|
|
||||||
if err != nil || org.Title != orgGot.Title {
|
|
||||||
t.Error("failed to get org organization by domain")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("UpdateOrginization", func(t *testing.T) {
|
|
||||||
ctx.Transaction, err = rt.Db.Beginx()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
org.Title = "testUpdate"
|
|
||||||
|
|
||||||
err = s.Organization.UpdateOrganization(ctx, org)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
t.Error("failed to update org organization")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
|
||||||
|
|
||||||
orgGot, err := s.Organization.GetOrganization(ctx, org.RefID)
|
|
||||||
if err != nil || org.Title != orgGot.Title {
|
|
||||||
t.Error("failed to get updated org organization")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("CheckDomain", func(t *testing.T) {
|
|
||||||
Domain := s.Organization.CheckDomain(ctx, "")
|
|
||||||
if Domain != Domain {
|
|
||||||
t.Error("failed to CheckDomain")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("UpdateAuthConfig", func(t *testing.T) {
|
|
||||||
ctx.Transaction, err = rt.Db.Beginx()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.Organization.UpdateAuthConfig(ctx, org)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
t.Error("failed to update organization AuthConfig")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
|
||||||
})
|
|
||||||
|
|
||||||
//
|
|
||||||
//Run after everything except delete as this makes an org inactive
|
|
||||||
//
|
|
||||||
|
|
||||||
t.Run("RemoveOrganization", func(t *testing.T) {
|
|
||||||
ctx.Transaction, err = rt.Db.Beginx()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.Organization.RemoveOrganization(ctx, org.RefID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
t.Error("failed to remove organization")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
|
||||||
|
|
||||||
orgGot, err := s.Organization.GetOrganization(ctx, org.RefID)
|
|
||||||
if err != nil || orgGot.Active != false {
|
|
||||||
t.Error("failed to get removed organization activity")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
//
|
|
||||||
// teardown code goes here
|
|
||||||
//
|
|
||||||
|
|
||||||
t.Run("DeleteOrganization", func(t *testing.T) {
|
|
||||||
ctx.Transaction, err = rt.Db.Beginx()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_,
|
|
||||||
err = s.Organization.DeleteOrganization(ctx, orgID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
t.Error("failed to delete org organization")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -52,7 +52,7 @@ func (s Store) GetOrganization(ctx domain.RequestContext, id string) (org org.Or
|
||||||
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
||||||
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
|
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
|
||||||
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
||||||
c_maxtags AS maxtags, c_theme AS theme, c_created AS created, c_revised AS revised
|
c_maxtags AS maxtags, c_theme AS theme, c_locale as locale, c_created AS created, c_revised AS revised
|
||||||
FROM dmz_org
|
FROM dmz_org
|
||||||
WHERE c_refid=?`),
|
WHERE c_refid=?`),
|
||||||
id)
|
id)
|
||||||
|
@ -84,7 +84,7 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
|
||||||
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
||||||
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
|
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
|
||||||
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
||||||
c_maxtags AS maxtags, c_created AS created, c_revised AS revised, c_theme AS theme
|
c_maxtags AS maxtags, c_theme AS theme, c_locale as locale, c_created AS created, c_revised AS revised, c_theme AS theme
|
||||||
FROM dmz_org
|
FROM dmz_org
|
||||||
WHERE c_domain=? AND c_active=`+s.IsTrue()),
|
WHERE c_domain=? AND c_active=`+s.IsTrue()),
|
||||||
subdomain)
|
subdomain)
|
||||||
|
@ -99,7 +99,7 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
|
||||||
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
||||||
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
|
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
|
||||||
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
coalesce(c_sub,`+s.EmptyJSON()+`) AS subscription,
|
||||||
c_maxtags AS maxtags, c_created AS created, c_revised AS revised, c_theme AS theme
|
c_maxtags AS maxtags, c_theme AS theme, c_locale as locale, c_created AS created, c_revised AS revised, c_theme AS theme
|
||||||
FROM dmz_org
|
FROM dmz_org
|
||||||
WHERE c_domain='' AND c_active=`+s.IsTrue()))
|
WHERE c_domain='' AND c_active=`+s.IsTrue()))
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ func (s Store) UpdateOrganization(ctx domain.RequestContext, org org.Organizatio
|
||||||
|
|
||||||
_, err = ctx.Transaction.NamedExec(`UPDATE dmz_org SET
|
_, err = ctx.Transaction.NamedExec(`UPDATE dmz_org SET
|
||||||
c_title=:title, c_message=:message, c_service=:conversionendpoint, c_email=:email, c_domain=:domain,
|
c_title=:title, c_message=:message, c_service=:conversionendpoint, c_email=:email, c_domain=:domain,
|
||||||
c_anonaccess=:allowanonymousaccess, c_maxtags=:maxtags, c_theme=:theme, c_revised=:revised
|
c_anonaccess=:allowanonymousaccess, c_maxtags=:maxtags, c_theme=:theme, c_locale=:locale, c_revised=:revised
|
||||||
WHERE c_refid=:refid`,
|
WHERE c_refid=:refid`,
|
||||||
&org)
|
&org)
|
||||||
|
|
||||||
|
|
|
@ -1334,6 +1334,7 @@ func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
|
||||||
documentID := request.Param(r, "documentID")
|
documentID := request.Param(r, "documentID")
|
||||||
if len(documentID) == 0 {
|
if len(documentID) == 0 {
|
||||||
response.WriteMissingDataError(w, method, "documentID")
|
response.WriteMissingDataError(w, method, "documentID")
|
||||||
|
h.Runtime.Log.Infof("Document ID missing for org %s", ctx.OrgID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1343,7 +1344,7 @@ func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
|
||||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Infof("Document not found %s", documentID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1503,18 +1504,13 @@ func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Error(method, err)
|
||||||
} else {
|
} else {
|
||||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||||
SpaceID: doc.SpaceID,
|
SpaceID: doc.SpaceID,
|
||||||
DocumentID: doc.RefID,
|
DocumentID: doc.RefID,
|
||||||
Metadata: source, // deliberate
|
Metadata: source, // deliberate
|
||||||
SourceType: activity.SourceTypeSearch, // deliberate
|
SourceType: activity.SourceTypeSearch, // deliberate
|
||||||
ActivityType: activity.TypeRead})
|
ActivityType: activity.TypeRead})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Transaction.Rollback()
|
|
||||||
h.Runtime.Log.Error(method, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Transaction.Commit()
|
ctx.Transaction.Commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,13 +201,12 @@ func (s Store) Update(ctx domain.RequestContext, page page.Page, refID, userID s
|
||||||
// It then propagates that change into the search table, adds a delete the page revisions history, and audits that the page has been removed.
|
// It then propagates that change into the search table, adds a delete the page revisions history, and audits that the page has been removed.
|
||||||
func (s Store) Delete(ctx domain.RequestContext, documentID, pageID string) (rows int64, err error) {
|
func (s Store) Delete(ctx domain.RequestContext, documentID, pageID string) (rows int64, err error) {
|
||||||
rows, err = s.DeleteConstrained(ctx.Transaction, "dmz_section", ctx.OrgID, pageID)
|
rows, err = s.DeleteConstrained(ctx.Transaction, "dmz_section", ctx.OrgID, pageID)
|
||||||
if err == nil {
|
|
||||||
_, _ = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_section_meta WHERE c_orgid='%s' AND c_sectionid='%s'", ctx.OrgID, pageID))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_section_meta WHERE c_orgid=? AND c_sectionid=?"),
|
||||||
_, _ = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_action WHERE c_orgid='%s' AND c_reftypeid='%s' AND c_reftype='P'", ctx.OrgID, pageID))
|
ctx.OrgID, pageID)
|
||||||
}
|
|
||||||
|
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_action WHERE c_orgid=? AND c_reftypeid=? AND c_reftype='P'"),
|
||||||
|
ctx.OrgID, pageID)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -408,8 +407,8 @@ func (s Store) GetDocumentRevisions(ctx domain.RequestContext, documentID string
|
||||||
|
|
||||||
// DeletePageRevisions deletes all of the page revision records for a given pageID.
|
// DeletePageRevisions deletes all of the page revision records for a given pageID.
|
||||||
func (s Store) DeletePageRevisions(ctx domain.RequestContext, pageID string) (rows int64, err error) {
|
func (s Store) DeletePageRevisions(ctx domain.RequestContext, pageID string) (rows int64, err error) {
|
||||||
rows, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_section_revision WHERE c_orgid='%s' AND c_sectionid='%s'",
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_section_revision WHERE c_orgid=? AND c_sectionid=?"),
|
||||||
ctx.OrgID, pageID))
|
ctx.OrgID, pageID)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ func CanViewDocument(ctx domain.RequestContext, s store.Store, documentID string
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanChangeDocument returns if the clinet has permission to change a given document.
|
// CanChangeDocument returns if the client has permission to change a given document.
|
||||||
func CanChangeDocument(ctx domain.RequestContext, s store.Store, documentID string) bool {
|
func CanChangeDocument(ctx domain.RequestContext, s store.Store, documentID string) bool {
|
||||||
document, err := s.Document.Get(ctx, documentID)
|
document, err := s.Document.Get(ctx, documentID)
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ func CanChangeDocument(ctx domain.RequestContext, s store.Store, documentID stri
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanDeleteDocument returns if the clinet has permission to change a given document.
|
// CanDeleteDocument returns if the client has permission to change a given document.
|
||||||
func CanDeleteDocument(ctx domain.RequestContext, s store.Store, documentID string) bool {
|
func CanDeleteDocument(ctx domain.RequestContext, s store.Store, documentID string) bool {
|
||||||
document, err := s.Document.Get(ctx, documentID)
|
document, err := s.Document.Get(ctx, documentID)
|
||||||
|
|
||||||
|
@ -147,6 +147,25 @@ func CanUploadDocument(ctx domain.RequestContext, s store.Store, spaceID string)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CanManageSpace returns if the user has permission to manage the given space.
|
||||||
|
func CanManageSpace(ctx domain.RequestContext, s store.Store, spaceID string) bool {
|
||||||
|
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, role := range roles {
|
||||||
|
if role.RefID == spaceID && role.Location == pm.LocationSpace && role.Scope == pm.ScopeRow &&
|
||||||
|
pm.ContainsPermission(role.Action, pm.SpaceManage, pm.SpaceOwner) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// CanViewSpace returns if the user has permission to view the given spaceID.
|
// CanViewSpace returns if the user has permission to view the given spaceID.
|
||||||
func CanViewSpace(ctx domain.RequestContext, s store.Store, spaceID string) bool {
|
func CanViewSpace(ctx domain.RequestContext, s store.Store, spaceID string) bool {
|
||||||
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
|
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
|
||||||
|
|
|
@ -266,55 +266,56 @@ func (s Store) GetDocumentPermissions(ctx domain.RequestContext, documentID stri
|
||||||
|
|
||||||
// DeleteDocumentPermissions removes records from dmz_permissions table for given document.
|
// DeleteDocumentPermissions removes records from dmz_permissions table for given document.
|
||||||
func (s Store) DeleteDocumentPermissions(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
func (s Store) DeleteDocumentPermissions(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf("DELETE FROM dmz_permission WHERE c_orgid='%s' AND c_location='document' AND c_refid='%s'", ctx.OrgID, documentID)
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_permission WHERE c_orgid=? AND c_location='document' AND c_refid=?"),
|
||||||
|
ctx.OrgID, documentID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSpacePermissions removes records from dmz_permissions table for given space ID.
|
// DeleteSpacePermissions removes records from dmz_permissions table for given space ID.
|
||||||
func (s Store) DeleteSpacePermissions(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
func (s Store) DeleteSpacePermissions(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf("DELETE FROM dmz_permission WHERE c_orgid='%s' AND c_location='space' AND c_refid='%s'", ctx.OrgID, spaceID)
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid=?"),
|
||||||
|
ctx.OrgID, spaceID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUserSpacePermissions removes all roles for the specified user, for the specified space.
|
// DeleteUserSpacePermissions removes all roles for the specified user, for the specified space.
|
||||||
func (s Store) DeleteUserSpacePermissions(ctx domain.RequestContext, spaceID, userID string) (rows int64, err error) {
|
func (s Store) DeleteUserSpacePermissions(ctx domain.RequestContext, spaceID, userID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf("DELETE FROM dmz_permission WHERE c_orgid='%s' AND c_location='space' AND c_refid='%s' AND c_who='user' AND c_whoid='%s'",
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid=? AND c_who='user' AND c_whoid=?"),
|
||||||
ctx.OrgID, spaceID, userID)
|
ctx.OrgID, spaceID, userID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUserPermissions removes all roles for the specified user, for the specified space.
|
// DeleteUserPermissions removes all roles for the specified user, for the specified space.
|
||||||
func (s Store) DeleteUserPermissions(ctx domain.RequestContext, userID string) (rows int64, err error) {
|
func (s Store) DeleteUserPermissions(ctx domain.RequestContext, userID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf("DELETE FROM dmz_permission WHERE c_orgid='%s' AND c_who='user' AND c_whoid='%s'",
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_permission WHERE c_orgid=? AND c_who='user' AND c_whoid=?"),
|
||||||
ctx.OrgID, userID)
|
ctx.OrgID, userID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCategoryPermissions removes records from dmz_permissions table for given category ID.
|
// DeleteCategoryPermissions removes records from dmz_permissions table for given category ID.
|
||||||
func (s Store) DeleteCategoryPermissions(ctx domain.RequestContext, categoryID string) (rows int64, err error) {
|
func (s Store) DeleteCategoryPermissions(ctx domain.RequestContext, categoryID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf("DELETE FROM dmz_permission WHERE c_orgid='%s' AND c_location='category' AND c_refid='%s'", ctx.OrgID, categoryID)
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_permission WHERE c_orgid=? AND c_location='category' AND c_refid=?"),
|
||||||
|
ctx.OrgID, categoryID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSpaceCategoryPermissions removes all category permission for for given space.
|
// DeleteSpaceCategoryPermissions removes all category permission for for given space.
|
||||||
func (s Store) DeleteSpaceCategoryPermissions(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
func (s Store) DeleteSpaceCategoryPermissions(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf(`
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_permission WHERE c_orgid=? AND c_location='category' AND c_refid IN (SELECT c_refid FROM dmz_category WHERE c_orgid=? AND c_spaceid=?)"),
|
||||||
DELETE FROM dmz_permission WHERE c_orgid='%s' AND c_location='category'
|
|
||||||
AND c_refid IN (SELECT c_refid FROM dmz_category WHERE c_orgid='%s' AND c_spaceid='%s')`,
|
|
||||||
ctx.OrgID, ctx.OrgID, spaceID)
|
ctx.OrgID, ctx.OrgID, spaceID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteGroupPermissions removes all roles for the specified group
|
// DeleteGroupPermissions removes all roles for the specified group
|
||||||
func (s Store) DeleteGroupPermissions(ctx domain.RequestContext, groupID string) (rows int64, err error) {
|
func (s Store) DeleteGroupPermissions(ctx domain.RequestContext, groupID string) (rows int64, err error) {
|
||||||
sql := fmt.Sprintf("DELETE FROM dmz_permission WHERE c_orgid='%s' AND c_who='role' AND c_whoid='%s'",
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_permission WHERE c_orgid=? AND c_who='role' AND c_whoid=?"),
|
||||||
ctx.OrgID, groupID)
|
ctx.OrgID, groupID)
|
||||||
|
|
||||||
return s.DeleteWhere(ctx.Transaction, sql)
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,10 +120,16 @@ func (s Store) DeletePin(ctx domain.RequestContext, id string) (rows int64, err
|
||||||
|
|
||||||
// DeletePinnedSpace removes any pins for specified space.
|
// DeletePinnedSpace removes any pins for specified space.
|
||||||
func (s Store) DeletePinnedSpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
func (s Store) DeletePinnedSpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
||||||
return s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_pin WHERE c_orgid='%s' AND c_spaceid='%s'", ctx.OrgID, spaceID))
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_pin WHERE c_orgid=? AND c_spaceid=?"),
|
||||||
|
ctx.OrgID, spaceID)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePinnedDocument removes any pins for specified document.
|
// DeletePinnedDocument removes any pins for specified document.
|
||||||
func (s Store) DeletePinnedDocument(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
func (s Store) DeletePinnedDocument(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
||||||
return s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_pin WHERE c_orgid='%s' AND c_docid='%s'", ctx.OrgID, documentID))
|
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_pin WHERE c_orgid=? AND c_docid=?"),
|
||||||
|
ctx.OrgID, documentID)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,22 +41,7 @@ const (
|
||||||
CommunityEdition Edition = "Community"
|
CommunityEdition Edition = "Community"
|
||||||
|
|
||||||
// EnterpriseEdition is proprietary closed-source product.
|
// EnterpriseEdition is proprietary closed-source product.
|
||||||
EnterpriseEdition Edition = "Enterprise"
|
EnterpriseEdition Edition = "Community+"
|
||||||
|
|
||||||
// PackageEssentials provides core capabilities.
|
|
||||||
PackageEssentials Package = "Essentials"
|
|
||||||
|
|
||||||
// PackageAdvanced provides analytics, reporting,
|
|
||||||
// content lifecycle, content verisoning, and audit logs.
|
|
||||||
PackageAdvanced Package = "Advanced"
|
|
||||||
|
|
||||||
// PackagePremium provides actions, feedback capture,
|
|
||||||
// approvals workflow, secure external sharing.
|
|
||||||
PackagePremium Package = "Premium"
|
|
||||||
|
|
||||||
// PackageDataCenter provides multi-tenanting
|
|
||||||
// and a bunch of professional services.
|
|
||||||
PackageDataCenter Package = "Data Center"
|
|
||||||
|
|
||||||
// PlanCloud represents *.documize.com hosting.
|
// PlanCloud represents *.documize.com hosting.
|
||||||
PlanCloud Plan = "Cloud"
|
PlanCloud Plan = "Cloud"
|
||||||
|
@ -64,6 +49,9 @@ const (
|
||||||
// PlanSelfHost represents privately hosted Documize instance.
|
// PlanSelfHost represents privately hosted Documize instance.
|
||||||
PlanSelfHost Plan = "Self-host"
|
PlanSelfHost Plan = "Self-host"
|
||||||
|
|
||||||
|
// SeatsFree is Five free users.
|
||||||
|
SeatsFree Seats = 5
|
||||||
|
|
||||||
// Seats0 is 0 users.
|
// Seats0 is 0 users.
|
||||||
Seats0 Seats = 0
|
Seats0 Seats = 0
|
||||||
|
|
||||||
|
@ -120,8 +108,8 @@ func (p *Product) IsValid(ctx RequestContext) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// First 10 is free for Enterprise edition.
|
// First 5 is free for Enterprise edition.
|
||||||
if Seats1 == ctx.Subscription.Seats && time.Now().UTC().Before(ctx.Subscription.End) {
|
if SeatsFree == ctx.Subscription.Seats && time.Now().UTC().Before(ctx.Subscription.End) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,8 +162,10 @@ type SubscriptionUserAccount struct {
|
||||||
// SubscriptionAsXML returns subscription data as XML document:
|
// SubscriptionAsXML returns subscription data as XML document:
|
||||||
//
|
//
|
||||||
// <DocumizeLicense>
|
// <DocumizeLicense>
|
||||||
// <Key>some key</Key>
|
//
|
||||||
// <Signature>some signature</Signature>
|
// <Key>some key</Key>
|
||||||
|
// <Signature>some signature</Signature>
|
||||||
|
//
|
||||||
// </DocumizeLicense>
|
// </DocumizeLicense>
|
||||||
//
|
//
|
||||||
// XML document is empty in case of error.
|
// XML document is empty in case of error.
|
||||||
|
|
|
@ -313,7 +313,8 @@ func (s Store) matchFullText(ctx domain.RequestContext, keywords, itemType strin
|
||||||
ctx.OrgID,
|
ctx.OrgID,
|
||||||
ctx.UserID,
|
ctx.UserID,
|
||||||
ctx.OrgID,
|
ctx.OrgID,
|
||||||
ctx.UserID)
|
ctx.UserID,
|
||||||
|
keywords)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
err = nil
|
err = nil
|
||||||
|
|
|
@ -13,6 +13,7 @@ package search
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
|
@ -84,6 +85,17 @@ func (s StoreSQLServer) Documents(ctx domain.RequestContext, q search.QueryOptio
|
||||||
results = append(results, r2...)
|
results = append(results, r2...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Match doc content
|
||||||
|
if q.Tag {
|
||||||
|
r3, err2 := s.matchTag(ctx, q.Keywords)
|
||||||
|
if err2 != nil {
|
||||||
|
err = errors.Wrap(err2, "search document tag")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, r3...)
|
||||||
|
}
|
||||||
|
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
results = []search.QueryResult{}
|
results = []search.QueryResult{}
|
||||||
}
|
}
|
||||||
|
@ -187,6 +199,55 @@ func (s StoreSQLServer) matchSection(ctx domain.RequestContext, keywords string)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s StoreSQLServer) matchTag(ctx domain.RequestContext, keywords string) (r []search.QueryResult, err error) {
|
||||||
|
// LIKE clause does not like quotes!
|
||||||
|
keywords = strings.Replace(keywords, "'", "", -1)
|
||||||
|
keywords = strings.Replace(keywords, "\"", "", -1)
|
||||||
|
keywords = strings.Replace(keywords, "%", "", -1)
|
||||||
|
keywords = fmt.Sprintf("%%%s%%", strings.ToLower(keywords))
|
||||||
|
|
||||||
|
sql1 := s.Bind(`SELECT
|
||||||
|
d.id, d.c_orgid AS orgid, d.c_refid AS documentid, d.c_refid AS itemid, 'tag' AS itemtype,
|
||||||
|
d.c_spaceid as spaceid, COALESCE(d.c_name,'Unknown') AS document, d.c_tags AS tags,
|
||||||
|
d.c_desc AS excerpt, d.c_template AS template, d.c_versionid AS versionid,
|
||||||
|
COALESCE(l.c_name,'Unknown') AS space, d.c_created AS created, d.c_revised AS revised
|
||||||
|
FROM
|
||||||
|
dmz_doc d
|
||||||
|
LEFT JOIN
|
||||||
|
dmz_space l ON l.c_orgid=d.c_orgid AND l.c_refid = d.c_spaceid
|
||||||
|
WHERE
|
||||||
|
d.c_orgid = ?
|
||||||
|
AND d.c_lifecycle = 1
|
||||||
|
AND d.c_spaceid IN
|
||||||
|
(
|
||||||
|
SELECT c_refid FROM dmz_space WHERE c_orgid=? AND c_refid IN
|
||||||
|
(
|
||||||
|
SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space'
|
||||||
|
UNION ALL
|
||||||
|
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role'
|
||||||
|
AND p.c_location='space' AND (r.c_userid=? OR r.c_userid='0')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND d.c_tags LIKE ?`)
|
||||||
|
|
||||||
|
err = s.Runtime.Db.Select(&r,
|
||||||
|
sql1,
|
||||||
|
ctx.OrgID,
|
||||||
|
ctx.OrgID,
|
||||||
|
ctx.OrgID,
|
||||||
|
ctx.UserID,
|
||||||
|
ctx.OrgID,
|
||||||
|
ctx.UserID,
|
||||||
|
keywords)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
r = []search.QueryResult{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
https://docs.microsoft.com/en-us/sql/relational-databases/search/get-started-with-full-text-search?view=sql-server-2017#options
|
https://docs.microsoft.com/en-us/sql/relational-databases/search/get-started-with-full-text-search?view=sql-server-2017#options
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,8 @@ func (*Provider) Meta() provider.TypeMeta {
|
||||||
section := provider.TypeMeta{}
|
section := provider.TypeMeta{}
|
||||||
|
|
||||||
section.ID = "d46a18f6-49fb-11e8-842f-0ed5f89f718b"
|
section.ID = "d46a18f6-49fb-11e8-842f-0ed5f89f718b"
|
||||||
section.Title = "Draw.io Diagram"
|
section.Title = "Diagrams.net"
|
||||||
section.Description = "Draw.io powered flowcharts and diagrams"
|
section.Description = "Flowcharts and diagrams"
|
||||||
section.ContentType = "flowchart"
|
section.ContentType = "flowchart"
|
||||||
section.PageType = "tab"
|
section.PageType = "tab"
|
||||||
section.Order = 9991
|
section.Order = 9991
|
||||||
|
|
59
domain/section/frame/frame.go
Normal file
59
domain/section/frame/frame.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||||
|
//
|
||||||
|
// This software (Documize Community Edition) is licensed under
|
||||||
|
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// You can operate outside the AGPL restrictions by purchasing
|
||||||
|
// Documize Enterprise Edition and obtaining a commercial license
|
||||||
|
// by contacting <sales@documize.com>.
|
||||||
|
//
|
||||||
|
// https://documize.com
|
||||||
|
|
||||||
|
package frame
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/env"
|
||||||
|
"github.com/documize/community/domain/section/provider"
|
||||||
|
"github.com/documize/community/domain/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider represents IFRAME
|
||||||
|
type Provider struct {
|
||||||
|
Runtime *env.Runtime
|
||||||
|
Store *store.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta describes us
|
||||||
|
func (*Provider) Meta() provider.TypeMeta {
|
||||||
|
section := provider.TypeMeta{}
|
||||||
|
|
||||||
|
section.ID = "1ef25a5a-7b24-470a-afca-bedd8462b7e2"
|
||||||
|
section.Title = "IFrame"
|
||||||
|
section.Description = "Embed an IFRAME code snippet"
|
||||||
|
section.ContentType = "frame"
|
||||||
|
section.PageType = "tab"
|
||||||
|
|
||||||
|
return section
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command stub.
|
||||||
|
func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
provider.WriteEmpty(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render converts as-is.
|
||||||
|
func (*Provider) Render(ctx *provider.Context, config, data string) string {
|
||||||
|
return embed(config, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh just sends back data as-is.
|
||||||
|
func (*Provider) Refresh(ctx *provider.Context, config, data string) string {
|
||||||
|
return embed(config, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func embed(config, data string) string {
|
||||||
|
// return bluemonday.UGCPolicy().Sanitize(data)
|
||||||
|
return data
|
||||||
|
}
|
|
@ -1,116 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize Community Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
|
||||||
"github.com/documize/community/domain"
|
|
||||||
"github.com/documize/community/domain/section/provider"
|
|
||||||
gogithub "github.com/google/go-github/github"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func clientID(ctx domain.RequestContext, s *domain.Store) string {
|
|
||||||
c, _ := s.Setting.Get(meta.ConfigHandle(), "clientID")
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func clientSecret(ctx domain.RequestContext, s *domain.Store) string {
|
|
||||||
c, _ := s.Setting.Get(meta.ConfigHandle(), "clientSecret")
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func authorizationCallbackURL(ctx domain.RequestContext, s *domain.Store) string {
|
|
||||||
// NOTE: URL value must have the path and query "/api/public/validate?section=github"
|
|
||||||
c, _ := s.Setting.Get(meta.ConfigHandle(), "authorizationCallbackURL")
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateToken(ctx provider.Context, s *domain.Store, ptoken string) error {
|
|
||||||
// Github authorization check
|
|
||||||
authClient := gogithub.NewClient((&gogithub.BasicAuthTransport{
|
|
||||||
Username: clientID(ctx.Request, s),
|
|
||||||
Password: clientSecret(ctx.Request, s),
|
|
||||||
}).Client())
|
|
||||||
_, _, err := authClient.Authorizations.Check(context.Background(), clientID(ctx.Request, s), ptoken)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Provider) githubClient(config *githubConfig) *gogithub.Client {
|
|
||||||
ts := oauth2.StaticTokenSource(
|
|
||||||
&oauth2.Token{AccessToken: config.Token},
|
|
||||||
)
|
|
||||||
tc := oauth2.NewClient(oauth2.NoContext, ts)
|
|
||||||
|
|
||||||
return gogithub.NewClient(tc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback is called by a browser redirect from Github, via the validation endpoint
|
|
||||||
func Callback(rt *env.Runtime, s *domain.Store, res http.ResponseWriter, req *http.Request) error {
|
|
||||||
ctx := domain.GetRequestContext(req)
|
|
||||||
|
|
||||||
code := req.URL.Query().Get("code")
|
|
||||||
state := req.URL.Query().Get("state")
|
|
||||||
|
|
||||||
ghurl := "https://github.com/login/oauth/access_token"
|
|
||||||
vals := "client_id=" + clientID(ctx, s)
|
|
||||||
vals += "&client_secret=" + clientSecret(ctx, s)
|
|
||||||
vals += "&code=" + code
|
|
||||||
vals += "&state=" + state
|
|
||||||
|
|
||||||
req2, err := http.NewRequest("POST", ghurl+"?"+vals, strings.NewReader(vals))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
req2.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
res2, err := http.DefaultClient.Do(req2)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var gt githubCallbackT
|
|
||||||
|
|
||||||
err = json.NewDecoder(res2.Body).Decode(>)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = res2.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
returl, err := url.QueryUnescape(state)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
up, err := url.Parse(returl)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
target := up.Scheme + "://" + up.Host + up.Path + "?mode=edit&code=" + gt.AccessToken
|
|
||||||
|
|
||||||
http.Redirect(res, req, target, http.StatusTemporaryRedirect)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,315 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize unity Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
gogithub "github.com/google/go-github/github"
|
|
||||||
)
|
|
||||||
|
|
||||||
const commitTimeFormat = "2006-01-02, 15:04"
|
|
||||||
|
|
||||||
type githubCommit struct {
|
|
||||||
Owner string `json:"owner"`
|
|
||||||
Repo string `json:"repo"`
|
|
||||||
ShowRepo bool `json:"showRepo"`
|
|
||||||
Branch string `json:"branch"`
|
|
||||||
ShowBranch bool `json:"showBranch"`
|
|
||||||
Date string `json:"date"`
|
|
||||||
BinDate time.Time `json:"-"` // only used for sorting
|
|
||||||
ShowDate bool `json:"showDate"`
|
|
||||||
Login string `json:"login"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Avatar string `json:"avatar"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
URL template.URL `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type githubAuthorStats struct {
|
|
||||||
Author string `json:"author"`
|
|
||||||
Login string `json:"login"`
|
|
||||||
Avatar string `json:"avatar"`
|
|
||||||
CommitCount int `json:"commitCount"`
|
|
||||||
Repos []string `json:"repos"`
|
|
||||||
OpenIssues int `json:"openIssues"`
|
|
||||||
ClosedIssues int `json:"closedIssues"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// order commits in a way that makes sense of the table
|
|
||||||
type orderCommits []githubCommit
|
|
||||||
|
|
||||||
func (s orderCommits) Len() int { return len(s) }
|
|
||||||
func (s orderCommits) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
func (s orderCommits) Less(i, j int) bool {
|
|
||||||
if s[i].Repo == s[j].Repo {
|
|
||||||
if s[i].Branch == s[j].Branch {
|
|
||||||
if s[i].BinDate == s[j].BinDate {
|
|
||||||
return s[i].Name < s[j].Name
|
|
||||||
}
|
|
||||||
return s[i].BinDate.Before(s[j].BinDate)
|
|
||||||
}
|
|
||||||
return s[i].Branch < s[j].Branch
|
|
||||||
}
|
|
||||||
return s[i].Repo < s[j].Repo
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort stats in order that that should be presented.
|
|
||||||
type asToSort []githubAuthorStats
|
|
||||||
|
|
||||||
func (s asToSort) Len() int { return len(s) }
|
|
||||||
func (s asToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
func (s asToSort) Less(i, j int) bool {
|
|
||||||
return s[i].CommitCount > s[j].CommitCount
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort branches in order that that should be presented.
|
|
||||||
type branchByID []githubBranch
|
|
||||||
|
|
||||||
func (s branchByID) Len() int { return len(s) }
|
|
||||||
func (s branchByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
func (s branchByID) Less(i, j int) bool {
|
|
||||||
return s[i].ID < s[j].ID
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagCommitsData = "commitsData"
|
|
||||||
|
|
||||||
func getCommits(client *gogithub.Client, config *githubConfig) ([]githubCommit, []githubAuthorStats, error) {
|
|
||||||
|
|
||||||
if !config.ShowCommits {
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// first make sure we've got all the branches
|
|
||||||
for _, orb := range config.Lists {
|
|
||||||
if orb.Included {
|
|
||||||
|
|
||||||
branches, _, err := client.Repositories.ListBranches(context.Background(), orb.Owner, orb.Repo,
|
|
||||||
&gogithub.ListOptions{PerPage: 100})
|
|
||||||
if err == nil {
|
|
||||||
render := make([]githubBranch, len(branches))
|
|
||||||
for kc, vb := range branches {
|
|
||||||
for _, existing := range config.Lists {
|
|
||||||
if orb.Owner == existing.Owner && orb.Repo == existing.Repo && orb.Name == *vb.Name {
|
|
||||||
goto found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render[kc] = githubBranch{
|
|
||||||
Owner: orb.Owner,
|
|
||||||
Repo: orb.Repo,
|
|
||||||
Name: *vb.Name,
|
|
||||||
ID: fmt.Sprintf("%s:%s:%s", orb.Owner, orb.Repo, *vb.Name),
|
|
||||||
Included: true,
|
|
||||||
URL: "https://github.com/" + orb.Owner + "/" + orb.Repo + "/tree/" + *vb.Name,
|
|
||||||
}
|
|
||||||
found:
|
|
||||||
}
|
|
||||||
config.Lists = append(config.Lists, render...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(branchByID(config.Lists))
|
|
||||||
|
|
||||||
config.UserNames = make(map[string]string)
|
|
||||||
|
|
||||||
authorStats := make(map[string]githubAuthorStats)
|
|
||||||
|
|
||||||
contribBranch := make(map[string]map[string]struct{})
|
|
||||||
|
|
||||||
overall := []githubCommit{}
|
|
||||||
|
|
||||||
for _, orb := range config.Lists {
|
|
||||||
if orb.Included {
|
|
||||||
|
|
||||||
opts := &gogithub.CommitsListOptions{
|
|
||||||
SHA: orb.Name,
|
|
||||||
ListOptions: gogithub.ListOptions{PerPage: config.BranchLines}}
|
|
||||||
|
|
||||||
if config.SincePtr != nil {
|
|
||||||
opts.Since = *config.SincePtr
|
|
||||||
}
|
|
||||||
|
|
||||||
guff, _, err := client.Repositories.ListCommits(context.Background(), orb.Owner, orb.Repo, opts)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
thisBranch := fmt.Sprintf("%s:%s", orb.Repo, orb.Name)
|
|
||||||
|
|
||||||
for _, v := range guff {
|
|
||||||
|
|
||||||
var d, m, u string
|
|
||||||
var bd time.Time
|
|
||||||
if v.Commit != nil {
|
|
||||||
if v.Commit.Committer.Date != nil {
|
|
||||||
d = v.Commit.Committer.Date.Format(commitTimeFormat)
|
|
||||||
bd = *v.Commit.Committer.Date
|
|
||||||
}
|
|
||||||
if v.Commit.Message != nil {
|
|
||||||
m = *v.Commit.Message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.HTMLURL != nil {
|
|
||||||
u = *v.HTMLURL
|
|
||||||
}
|
|
||||||
|
|
||||||
// author commits
|
|
||||||
al, an, aa := "", "", githubGravatar
|
|
||||||
if v.Author != nil {
|
|
||||||
if v.Author.Login != nil {
|
|
||||||
al = *v.Author.Login
|
|
||||||
an = getUserName(client, config, al)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Author.AvatarURL != nil {
|
|
||||||
aa = *v.Author.AvatarURL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
l := al // use author login
|
|
||||||
|
|
||||||
overall = append(overall, githubCommit{
|
|
||||||
Owner: orb.Owner,
|
|
||||||
Repo: orb.Repo,
|
|
||||||
Branch: orb.Name,
|
|
||||||
Name: an,
|
|
||||||
Login: l,
|
|
||||||
Message: m,
|
|
||||||
Date: d,
|
|
||||||
BinDate: bd,
|
|
||||||
Avatar: aa,
|
|
||||||
URL: template.URL(u),
|
|
||||||
})
|
|
||||||
|
|
||||||
if _, ok := contribBranch[l]; !ok {
|
|
||||||
contribBranch[l] = make(map[string]struct{})
|
|
||||||
}
|
|
||||||
contribBranch[l][thisBranch] = struct{}{}
|
|
||||||
|
|
||||||
cum := authorStats[l]
|
|
||||||
cum.Login = l
|
|
||||||
cum.Author = an
|
|
||||||
cum.Avatar = aa
|
|
||||||
cum.CommitCount++
|
|
||||||
// TODO review, this code removed as too slow
|
|
||||||
//cmt, _, err := client.Repositories.GetCommit(orb.Owner, orb.Repo, *v.SHA)
|
|
||||||
//if err == nil {
|
|
||||||
// if cmt.Stats != nil {
|
|
||||||
// if cmt.Stats.Total != nil {
|
|
||||||
// cum.TotalChanges += (*cmt.Stats.Total)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
authorStats[l] = cum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(orderCommits(overall))
|
|
||||||
|
|
||||||
for k := range overall {
|
|
||||||
overall[k].ShowRepo = true
|
|
||||||
overall[k].ShowBranch = true
|
|
||||||
overall[k].ShowDate = true
|
|
||||||
if k > 0 {
|
|
||||||
if overall[k].Repo == overall[k-1].Repo {
|
|
||||||
overall[k].ShowRepo = false
|
|
||||||
if overall[k].Branch == overall[k-1].Branch {
|
|
||||||
overall[k].ShowBranch = false
|
|
||||||
if overall[k].Date == overall[k-1].Date {
|
|
||||||
overall[k].ShowDate = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
retStats := make([]githubAuthorStats, 0, len(authorStats))
|
|
||||||
for _, v := range authorStats {
|
|
||||||
repos := contribBranch[v.Login]
|
|
||||||
v.Repos = make([]string, 0, len(repos))
|
|
||||||
for r := range repos {
|
|
||||||
v.Repos = append(v.Repos, r)
|
|
||||||
}
|
|
||||||
sort.Strings(v.Repos)
|
|
||||||
retStats = append(retStats, v)
|
|
||||||
}
|
|
||||||
sort.Sort(asToSort(retStats))
|
|
||||||
|
|
||||||
return overall, retStats, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshCommits(gr *githubRender, config *githubConfig, client *gogithub.Client) (err error) {
|
|
||||||
if !config.ShowCommits {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
gr.BranchCommits, gr.AuthorStats, err = getCommits(client, config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderCommits(payload *githubRender, c *githubConfig) error {
|
|
||||||
if !c.ShowCommits {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.CommitCount = 0
|
|
||||||
for range payload.BranchCommits {
|
|
||||||
payload.CommitCount++
|
|
||||||
}
|
|
||||||
payload.HasCommits = payload.CommitCount > 0
|
|
||||||
|
|
||||||
for i := range payload.Issues {
|
|
||||||
var author int
|
|
||||||
for a := range payload.AuthorStats {
|
|
||||||
if payload.AuthorStats[a].Login == payload.Issues[i].Name ||
|
|
||||||
(payload.AuthorStats[a].Login == "" && payload.Issues[i].Name == unassignedIssue) {
|
|
||||||
author = a
|
|
||||||
goto found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// no Author found for issue, so create one
|
|
||||||
payload.AuthorStats = append(payload.AuthorStats, githubAuthorStats{
|
|
||||||
Author: payload.Issues[i].Name,
|
|
||||||
Avatar: payload.Issues[i].Avatar,
|
|
||||||
})
|
|
||||||
author = len(payload.AuthorStats) - 1
|
|
||||||
found:
|
|
||||||
if payload.Issues[i].IsOpen {
|
|
||||||
payload.AuthorStats[author].OpenIssues++
|
|
||||||
} else {
|
|
||||||
payload.AuthorStats[author].ClosedIssues++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
payload.HasAuthorStats = len(payload.AuthorStats) > 0
|
|
||||||
sort.Sort(asToSort(payload.AuthorStats))
|
|
||||||
|
|
||||||
payload.NumContributors = len(payload.AuthorStats) - 1
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
reports[tagCommitsData] = report{refreshCommits, renderCommits, commitsTemplate}
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize Community Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
package github
|
|
||||||
|
|
||||||
const commitsTemplate = `
|
|
||||||
<div class="section-github-render">
|
|
||||||
<!--
|
|
||||||
{{if .HasAuthorStats}}
|
|
||||||
<div class="heading">Contributors</div>
|
|
||||||
<p>
|
|
||||||
There
|
|
||||||
{{if eq 1 .NumContributors}}is{{else}}are{{end}}
|
|
||||||
{{.NumContributors}}
|
|
||||||
{{if eq 1 .NumContributors}}contributor{{else}}contributors{{end}}
|
|
||||||
across {{.RepoCount}}
|
|
||||||
{{if eq 1 .RepoCount}} repository. {{else}} repositories. {{end}}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<table class="github-table">
|
|
||||||
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="title">Contributors</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{{range $stats := .AuthorStats}}
|
|
||||||
<tr>
|
|
||||||
<td class="no-width">
|
|
||||||
<img class="github-avatar" alt="@{{$stats.Author}}" src="{{$stats.Avatar}}" />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="contributor-name">{{$stats.Author}}</div>
|
|
||||||
<div class="contributor-meta">
|
|
||||||
{{if gt $stats.OpenIssues 0}}
|
|
||||||
assigned {{$stats.OpenIssues}}
|
|
||||||
{{if eq 1 $stats.OpenIssues}} issue {{else}} issues {{end}}
|
|
||||||
{{end}}
|
|
||||||
{{if gt $stats.ClosedIssues 0}}
|
|
||||||
· {{$stats.ClosedIssues}} closed
|
|
||||||
{{end}}
|
|
||||||
{{if gt $stats.CommitCount 0}}
|
|
||||||
{{if gt $stats.OpenIssues 0}} · {{end}}
|
|
||||||
{{if gt $stats.ClosedIssues 0}} · {{end}}
|
|
||||||
made {{$stats.CommitCount}}
|
|
||||||
{{if eq 1 $stats.CommitCount}} commit {{else}} commits {{end}}
|
|
||||||
on {{len $stats.Repos}} {{if eq 1 (len $stats.Repos)}} branch {{else}} branches {{end}}
|
|
||||||
{{range $repo := $stats.Repos}} · {{$repo}} {{end}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{end}}
|
|
||||||
-->
|
|
||||||
|
|
||||||
{{if .HasCommits}}
|
|
||||||
<table class="github-table" style="width: 100%;">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="title">Commits <span>· {{len .BranchCommits}} commits</span>
|
|
||||||
</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{range $commit := .BranchCommits}}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="{{$commit.URL}}">{{$commit.Message}}</a>
|
|
||||||
<span class="data"> {{$commit.Branch}}</span>
|
|
||||||
</td>
|
|
||||||
<td class="right-column">
|
|
||||||
<div class="contributor-meta">
|
|
||||||
{{$commit.Date}}
|
|
||||||
<img class="github-avatar" title="@{{$commit.Name}}" alt="@{{$commit.Name}}" src="{{$commit.Avatar}}" />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
`
|
|
|
@ -1,251 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize Community Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"html/template"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
|
||||||
"github.com/documize/community/domain"
|
|
||||||
"github.com/documize/community/domain/section/provider"
|
|
||||||
gogithub "github.com/google/go-github/github"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO find a smaller image than the one below
|
|
||||||
const githubGravatar = "https://i2.wp.com/assets-cdn.github.com/images/gravatars/gravatar-user-420.png"
|
|
||||||
|
|
||||||
var meta provider.TypeMeta
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
meta = provider.TypeMeta{}
|
|
||||||
|
|
||||||
meta.ID = "38c0e4c5-291c-415e-8a4d-262ee80ba5df"
|
|
||||||
meta.Title = "GitHub"
|
|
||||||
meta.Description = "Link code commits and issues"
|
|
||||||
meta.ContentType = "github"
|
|
||||||
meta.PageType = "tab"
|
|
||||||
meta.Callback = Callback
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provider represents GitHub
|
|
||||||
type Provider struct {
|
|
||||||
Runtime *env.Runtime
|
|
||||||
Store *domain.Store
|
|
||||||
}
|
|
||||||
|
|
||||||
// Meta describes us.
|
|
||||||
func (*Provider) Meta() provider.TypeMeta {
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command to run the various functions required...
|
|
||||||
func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
query := r.URL.Query()
|
|
||||||
method := query.Get("method")
|
|
||||||
|
|
||||||
if len(method) == 0 {
|
|
||||||
msg := "missing method name"
|
|
||||||
provider.WriteMessage(w, "gitub", msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if method == "config" {
|
|
||||||
var ret struct {
|
|
||||||
CID string `json:"clientID"`
|
|
||||||
URL string `json:"authorizationCallbackURL"`
|
|
||||||
}
|
|
||||||
ret.CID = clientID(ctx.Request, p.Store)
|
|
||||||
ret.URL = authorizationCallbackURL(ctx.Request, p.Store)
|
|
||||||
provider.WriteJSON(w, ret)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer r.Body.Close() // ignore error
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
p.Runtime.Log.Error("bad body", errors.New("Missing body"))
|
|
||||||
provider.WriteMessage(w, "github", "bad body")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if method == "saveSecret" { // secret Token update code
|
|
||||||
|
|
||||||
// write the new one, direct from JS
|
|
||||||
if err = ctx.SaveSecrets(string(body), p.Store); err != nil {
|
|
||||||
p.Runtime.Log.Error("github settoken configuration", err)
|
|
||||||
provider.WriteError(w, "github", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
provider.WriteEmpty(w)
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the config from the client-side
|
|
||||||
config := githubConfig{}
|
|
||||||
err = json.Unmarshal(body, &config)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
p.Runtime.Log.Error("github Command Unmarshal", err)
|
|
||||||
provider.WriteError(w, "github", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Clean()
|
|
||||||
// always use DB version of the token
|
|
||||||
config.Token = ctx.GetSecrets("token", p.Store) // get the secret token in the database
|
|
||||||
|
|
||||||
client := p.githubClient(&config)
|
|
||||||
|
|
||||||
switch method {
|
|
||||||
|
|
||||||
case "checkAuth":
|
|
||||||
|
|
||||||
if len(config.Token) == 0 {
|
|
||||||
err = errors.New("empty github token")
|
|
||||||
} else {
|
|
||||||
err = validateToken(*ctx, p.Store, config.Token)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
// token now invalid, so wipe it
|
|
||||||
ctx.SaveSecrets("", p.Store) // ignore error, already in an error state
|
|
||||||
p.Runtime.Log.Error("github check token validation", err)
|
|
||||||
provider.WriteError(w, "github", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
provider.WriteEmpty(w)
|
|
||||||
|
|
||||||
default:
|
|
||||||
|
|
||||||
if listFailed(p.Runtime, method, config, client, w) {
|
|
||||||
|
|
||||||
gr := githubRender{}
|
|
||||||
for _, rep := range reports {
|
|
||||||
rep.refresh(&gr, &config, client)
|
|
||||||
}
|
|
||||||
provider.WriteJSON(w, &gr)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh ... gets the latest version
|
|
||||||
func (p *Provider) Refresh(ctx *provider.Context, configJSON, data string) string {
|
|
||||||
var c = githubConfig{}
|
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(configJSON), &c)
|
|
||||||
if err != nil {
|
|
||||||
p.Runtime.Log.Error("github.Refresh unmarshal", err)
|
|
||||||
return "internal configuration error '" + err.Error() + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Clean()
|
|
||||||
c.Token = ctx.GetSecrets("token", p.Store)
|
|
||||||
|
|
||||||
client := p.githubClient(&c)
|
|
||||||
|
|
||||||
byts, err := json.Marshal(refreshReportData(&c, client))
|
|
||||||
if err != nil {
|
|
||||||
p.Runtime.Log.Error("github.Refresh marshal", err)
|
|
||||||
return "internal configuration error '" + err.Error() + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(byts)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshReportData(c *githubConfig, client *gogithub.Client) *githubRender {
|
|
||||||
var gr = githubRender{}
|
|
||||||
for _, rep := range reports {
|
|
||||||
rep.refresh(&gr, c, client)
|
|
||||||
}
|
|
||||||
return &gr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render ... just returns the data given, suitably formatted
|
|
||||||
func (p *Provider) Render(ctx *provider.Context, config, data string) string {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
payload := githubRender{}
|
|
||||||
var c = githubConfig{}
|
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(config), &c)
|
|
||||||
if err != nil {
|
|
||||||
return "Please delete and recreate this Github section."
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Clean()
|
|
||||||
c.Token = ctx.GetSecrets("token", p.Store)
|
|
||||||
|
|
||||||
data = strings.TrimSpace(data)
|
|
||||||
if len(data) == 0 {
|
|
||||||
p.Runtime.Log.Info("GitHub connector received empty data for rendering")
|
|
||||||
// TODO review why this error occurs & if it should be reported - seems to occur for new sections
|
|
||||||
// log.ErrorString(fmt.Sprintf("Rendered empty github JSON payload as '' for owner %s repos %#v", c.Owner, c.Lists))
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(data), &payload)
|
|
||||||
if err != nil {
|
|
||||||
return "Please delete and recreate this Github section."
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.Config = c
|
|
||||||
payload.Limit = c.BranchLines
|
|
||||||
payload.List = c.Lists
|
|
||||||
|
|
||||||
ret := ""
|
|
||||||
for _, repID := range c.ReportOrder {
|
|
||||||
|
|
||||||
rep, ok := reports[repID]
|
|
||||||
if !ok {
|
|
||||||
msg := "github report not found for: " + repID
|
|
||||||
p.Runtime.Log.Info(msg)
|
|
||||||
return "Documize internal error: " + msg
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = rep.render(&payload, &c); err != nil {
|
|
||||||
p.Runtime.Log.Error("unable to render "+repID, err)
|
|
||||||
return "Documize internal github render " + repID + " error: " + err.Error() + "<BR>" + data
|
|
||||||
}
|
|
||||||
|
|
||||||
t := template.New("github")
|
|
||||||
t, err = t.Parse(rep.template)
|
|
||||||
if err != nil {
|
|
||||||
p.Runtime.Log.Error("github render template.Parse error:", err)
|
|
||||||
//for k, v := range strings.Split(rep.template, "\n") {
|
|
||||||
// fmt.Println("DEBUG", k+1, v)
|
|
||||||
//}
|
|
||||||
return "Documize internal github template.Parse error: " + err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer := new(bytes.Buffer)
|
|
||||||
err = t.Execute(buffer, payload)
|
|
||||||
if err != nil {
|
|
||||||
p.Runtime.Log.Error("github render template.Execute error:", err)
|
|
||||||
return "Documize internal github template.Execute error: " + err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
ret += buffer.String()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
|
@ -1,239 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize Community Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"html/template"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
gogithub "github.com/google/go-github/github"
|
|
||||||
)
|
|
||||||
|
|
||||||
type githubIssue struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
Date string `json:"date"`
|
|
||||||
Updated string `json:"dated"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
URL template.URL `json:"url"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Creator string `json:"creator"`
|
|
||||||
Avatar string `json:"avatar"`
|
|
||||||
Labels template.HTML `json:"labels"`
|
|
||||||
LabelNames []string `json:"labelNames"`
|
|
||||||
LabelColors []string `json:"labelColors"`
|
|
||||||
IsOpen bool `json:"isopen"`
|
|
||||||
Repo string `json:"repo"`
|
|
||||||
Private bool `json:"private"`
|
|
||||||
Milestone string `json:"milestone"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type githubSharedLabel struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Count int `json:"count"`
|
|
||||||
Color string `json:"color"`
|
|
||||||
Repos template.HTML `json:"Repos"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort issues in order that that should be presented - by date updated.
|
|
||||||
type issuesToSort []githubIssue
|
|
||||||
|
|
||||||
func (s issuesToSort) Len() int { return len(s) }
|
|
||||||
func (s issuesToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
func (s issuesToSort) Less(i, j int) bool {
|
|
||||||
if s[i].Milestone != noMilestone && s[j].Milestone == noMilestone {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if s[i].Milestone == noMilestone && s[j].Milestone != noMilestone {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s[i].Milestone != s[j].Milestone {
|
|
||||||
// TODO should this order be by milestone completion?
|
|
||||||
return s[i].Milestone < s[j].Milestone
|
|
||||||
}
|
|
||||||
if !s[i].IsOpen && s[j].IsOpen {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if s[i].IsOpen && !s[j].IsOpen {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// TODO this seems a very slow approach
|
|
||||||
iDate, _ := time.Parse(issuesTimeFormat, s[i].Updated)
|
|
||||||
jDate, _ := time.Parse(issuesTimeFormat, s[j].Updated)
|
|
||||||
return iDate.Before(jDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort shared labels alphabetically
|
|
||||||
type sharedLabelsSort []githubSharedLabel
|
|
||||||
|
|
||||||
func (s sharedLabelsSort) Len() int { return len(s) }
|
|
||||||
func (s sharedLabelsSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
func (s sharedLabelsSort) Less(i, j int) bool { return s[i].Name < s[j].Name }
|
|
||||||
|
|
||||||
const (
|
|
||||||
tagIssuesData = "issuesData"
|
|
||||||
issuesTimeFormat = "January 2 2006, 15:04"
|
|
||||||
unassignedIssue = "(unassigned)"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
reports[tagIssuesData] = report{refreshIssues, renderIssues, issuesTemplate}
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapLabels(labels []gogithub.Label) (l string, labelNames []string, labelColors []string) {
|
|
||||||
labelNames = make([]string, 0, len(labels))
|
|
||||||
labelColors = make([]string, 0, len(labels))
|
|
||||||
for _, ll := range labels {
|
|
||||||
labelNames = append(labelNames, *ll.Name)
|
|
||||||
labelColors = append(labelColors, *ll.Color)
|
|
||||||
l += `<span class="issue-label" style="background-color:#` + *ll.Color + `">` + *ll.Name + `</span> `
|
|
||||||
}
|
|
||||||
return l, labelNames, labelColors
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIssues(client *gogithub.Client, config *githubConfig) ([]githubIssue, error) {
|
|
||||||
|
|
||||||
if !config.ShowIssues {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := []githubIssue{}
|
|
||||||
|
|
||||||
hadRepo := make(map[string]bool)
|
|
||||||
|
|
||||||
for _, orb := range config.Lists {
|
|
||||||
if orb.Included {
|
|
||||||
|
|
||||||
rName := orb.Owner + "/" + orb.Repo
|
|
||||||
|
|
||||||
if !hadRepo[rName] {
|
|
||||||
|
|
||||||
for _, state := range []string{"open", "closed"} {
|
|
||||||
|
|
||||||
opts := &gogithub.IssueListByRepoOptions{
|
|
||||||
Sort: "updated",
|
|
||||||
State: state,
|
|
||||||
ListOptions: gogithub.ListOptions{PerPage: config.BranchLines}}
|
|
||||||
|
|
||||||
if config.SincePtr != nil && state == "closed" /* we want all the open ones */ {
|
|
||||||
opts.Since = *config.SincePtr
|
|
||||||
}
|
|
||||||
|
|
||||||
guff, _, err := client.Issues.ListByRepo(context.Background(), orb.Owner, orb.Repo, opts)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range guff {
|
|
||||||
n := unassignedIssue
|
|
||||||
av := githubGravatar
|
|
||||||
ptr := v.Assignee
|
|
||||||
if ptr != nil {
|
|
||||||
if ptr.Login != nil {
|
|
||||||
n = *ptr.Login
|
|
||||||
av = *ptr.AvatarURL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ms := noMilestone
|
|
||||||
if v.Milestone != nil {
|
|
||||||
if v.Milestone.Title != nil {
|
|
||||||
ms = *v.Milestone.Title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
l, ln, lc := wrapLabels(v.Labels)
|
|
||||||
ret = append(ret, githubIssue{
|
|
||||||
Name: n,
|
|
||||||
Creator: getUserName(client, config, *v.User.Login),
|
|
||||||
Avatar: av,
|
|
||||||
Message: *v.Title,
|
|
||||||
Date: v.CreatedAt.Format(issuesTimeFormat),
|
|
||||||
Updated: v.UpdatedAt.Format(issuesTimeFormat),
|
|
||||||
URL: template.URL(*v.HTMLURL),
|
|
||||||
Labels: template.HTML(l),
|
|
||||||
LabelNames: ln,
|
|
||||||
LabelColors: lc,
|
|
||||||
ID: *v.Number,
|
|
||||||
IsOpen: *v.State == "open",
|
|
||||||
Repo: repoName(rName),
|
|
||||||
Private: orb.Private,
|
|
||||||
Milestone: ms,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hadRepo[rName] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(issuesToSort(ret))
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshIssues(gr *githubRender, config *githubConfig, client *gogithub.Client) (err error) {
|
|
||||||
if !config.ShowIssues {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
gr.Issues, err = getIssues(client, config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gr.OpenIssues = 0
|
|
||||||
gr.ClosedIssues = 0
|
|
||||||
sharedLabels := make(map[string][]string)
|
|
||||||
sharedLabelColors := make(map[string]string)
|
|
||||||
for _, v := range gr.Issues {
|
|
||||||
if v.IsOpen {
|
|
||||||
gr.OpenIssues++
|
|
||||||
} else {
|
|
||||||
gr.ClosedIssues++
|
|
||||||
}
|
|
||||||
for i, lab := range v.LabelNames {
|
|
||||||
sharedLabels[lab] = append(sharedLabels[lab], v.Repo)
|
|
||||||
if _, exists := sharedLabelColors[lab]; !exists { // use the first one we see
|
|
||||||
sharedLabelColors[lab] = v.LabelColors[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gr.HasIssues = (gr.OpenIssues + gr.ClosedIssues) > 0
|
|
||||||
|
|
||||||
gr.SharedLabels = make([]githubSharedLabel, 0, len(sharedLabels)) // will usually be too big
|
|
||||||
for name, repos := range sharedLabels {
|
|
||||||
if len(repos) > 1 {
|
|
||||||
thisLab := githubSharedLabel{Name: name, Count: len(repos), Color: sharedLabelColors[name]}
|
|
||||||
show := ""
|
|
||||||
for i, r := range repos {
|
|
||||||
if i > 0 {
|
|
||||||
show += ", "
|
|
||||||
}
|
|
||||||
show += "<a href='https://github.com/" + config.Owner + "/" + r +
|
|
||||||
"/issues?q=is%3Aissue+label%3A" + name + "'>" + r + "</a>"
|
|
||||||
}
|
|
||||||
thisLab.Repos = template.HTML(show)
|
|
||||||
gr.SharedLabels = append(gr.SharedLabels, thisLab)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(sharedLabelsSort(gr.SharedLabels))
|
|
||||||
gr.HasSharedLabels = len(gr.SharedLabels) > 0
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderIssues(payload *githubRender, c *githubConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize Community Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
package github
|
|
||||||
|
|
||||||
const (
|
|
||||||
openIsvg = `
|
|
||||||
<span class="issue-state" title="Open Issue">
|
|
||||||
<svg height="16" version="1.1" viewBox="0 0 14 16" width="14" class="color:#6cc644;">
|
|
||||||
<path d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
`
|
|
||||||
closedIsvg = `
|
|
||||||
<span class="issue-state" title="Closed Issue">
|
|
||||||
<svg height="16" version="1.1" viewBox="0 0 16 16" width="16" class="color:#bd2c00;">
|
|
||||||
<path d="M7 10h2v2H7v-2zm2-6H7v5h2V4zm1.5 1.5l-1 1L12 9l4-4.5-1-1L12 7l-1.5-1.5zM8 13.7A5.71 5.71 0 0 1 2.3 8c0-3.14 2.56-5.7 5.7-5.7 1.83 0 3.45.88 4.5 2.2l.92-.92A6.947 6.947 0 0 0 8 1C4.14 1 1 4.14 1 8s3.14 7 7 7 7-3.14 7-7l-1.52 1.52c-.66 2.41-2.86 4.19-5.48 4.19v-.01z"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
`
|
|
||||||
issuesTemplate = `
|
|
||||||
<div class="section-github-render">
|
|
||||||
{{if .HasIssues}}
|
|
||||||
<table class="github-table" style="width: 100%;">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="title">
|
|
||||||
Issues <span>· {{.ClosedIssues}} closed {{if eq 1 .ClosedIssues}}{{else}}issues{{end}} and {{.OpenIssues}} open
|
|
||||||
{{if eq 1 .OpenIssues}}issue{{else}}{{end}}</span>
|
|
||||||
</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{{range $data := .Issues}}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{{if $data.IsOpen}}
|
|
||||||
` + openIsvg + `
|
|
||||||
{{else}}
|
|
||||||
` + closedIsvg + `
|
|
||||||
{{end}}
|
|
||||||
<a href="{{$data.URL}}">{{$data.Message}}</a> <span class="data">#{{$data.ID}}</span>
|
|
||||||
{{$data.Labels}}
|
|
||||||
</td>
|
|
||||||
<td class="right-column">
|
|
||||||
<div class="milestone-meta">
|
|
||||||
<span class="meta-milestone">{{$data.Milestone}}</span> ·
|
|
||||||
<span class="meta-creator">{{$data.Creator}}</span> · <span class="meta-date">{{$data.Date}}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)
|
|
|
@ -1,106 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize Community Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/documize/community/core/env"
|
|
||||||
"github.com/documize/community/domain/section/provider"
|
|
||||||
gogithub "github.com/google/go-github/github"
|
|
||||||
)
|
|
||||||
|
|
||||||
func listFailed(rt *env.Runtime, method string, config githubConfig, client *gogithub.Client, w http.ResponseWriter) (failed bool) {
|
|
||||||
switch method { // which list to choose?
|
|
||||||
|
|
||||||
case "owners":
|
|
||||||
|
|
||||||
me, _, err := client.Users.Get(context.Background(), "")
|
|
||||||
if err != nil {
|
|
||||||
rt.Log.Error("github get user details:", err)
|
|
||||||
provider.WriteError(w, "github", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
orgs, _, err := client.Organizations.List(context.Background(), "", nil)
|
|
||||||
if err != nil {
|
|
||||||
rt.Log.Error("github get user's organisations:", err)
|
|
||||||
provider.WriteError(w, "github", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
owners := make([]githubOwner, 1+len(orgs))
|
|
||||||
owners[0] = githubOwner{ID: *me.Login, Name: *me.Login}
|
|
||||||
for ko, vo := range orgs {
|
|
||||||
id := 1 + ko
|
|
||||||
owners[id].ID = *vo.Login
|
|
||||||
owners[id].Name = *vo.Login
|
|
||||||
}
|
|
||||||
|
|
||||||
owners = sortOwners(owners)
|
|
||||||
|
|
||||||
provider.WriteJSON(w, owners)
|
|
||||||
|
|
||||||
case "orgrepos":
|
|
||||||
|
|
||||||
var render []githubBranch
|
|
||||||
if config.Owner != "" {
|
|
||||||
|
|
||||||
me, _, err := client.Users.Get(context.Background(), "")
|
|
||||||
if err != nil {
|
|
||||||
rt.Log.Error("github get user details:", err)
|
|
||||||
provider.WriteError(w, "github", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var repos []*gogithub.Repository
|
|
||||||
if config.Owner == *me.Login {
|
|
||||||
repos, _, err = client.Repositories.List(context.Background(), config.Owner, nil)
|
|
||||||
} else {
|
|
||||||
opt := &gogithub.RepositoryListByOrgOptions{
|
|
||||||
ListOptions: gogithub.ListOptions{PerPage: 100},
|
|
||||||
}
|
|
||||||
repos, _, err = client.Repositories.ListByOrg(context.Background(), config.Owner, opt)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
rt.Log.Error("github get user/org repositories:", err)
|
|
||||||
provider.WriteError(w, "github", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, vr := range repos {
|
|
||||||
render = append(render,
|
|
||||||
githubBranch{
|
|
||||||
Name: "master",
|
|
||||||
ID: fmt.Sprintf("%s:%s", config.Owner, *vr.Name),
|
|
||||||
Owner: config.Owner,
|
|
||||||
Repo: *vr.Name,
|
|
||||||
Private: *vr.Private,
|
|
||||||
Included: false,
|
|
||||||
URL: *vr.HTMLURL,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render = sortBranches(render)
|
|
||||||
|
|
||||||
provider.WriteJSON(w, render)
|
|
||||||
|
|
||||||
case "content":
|
|
||||||
|
|
||||||
provider.WriteJSON(w, refreshReportData(&config, client))
|
|
||||||
|
|
||||||
default:
|
|
||||||
return true // failed to get a list
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,225 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize Community Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
package github
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
gogithub "github.com/google/go-github/github"
|
|
||||||
)
|
|
||||||
|
|
||||||
type githubMilestone struct {
|
|
||||||
Repo string `json:"repo"`
|
|
||||||
Private bool `json:"private"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
URL template.URL `json:"url"`
|
|
||||||
IsOpen bool `json:"isopen"`
|
|
||||||
OpenIssues int `json:"openIssues"`
|
|
||||||
ClosedIssues int `json:"closedIssues"`
|
|
||||||
CompleteMsg string `json:"completeMsg"`
|
|
||||||
DueDate string `json:"dueDate"`
|
|
||||||
UpdatedAt string `json:"updatedAt"`
|
|
||||||
Progress uint `json:"progress"`
|
|
||||||
IsMilestone bool `json:"isMilestone"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort milestones in order that that should be presented.
|
|
||||||
|
|
||||||
type milestonesToSort []githubMilestone
|
|
||||||
|
|
||||||
func (s milestonesToSort) Len() int { return len(s) }
|
|
||||||
func (s milestonesToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
func (s milestonesToSort) Less(i, j int) bool {
|
|
||||||
if s[i].Repo < s[j].Repo {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if s[i].Repo > s[j].Repo {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !s[i].IsOpen && s[j].IsOpen {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if s[i].IsOpen && !s[j].IsOpen {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s[i].Name != noMilestone && s[j].Name == noMilestone {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if s[i].Name == noMilestone && s[j].Name != noMilestone {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s[i].Progress == s[j].Progress { // order equal progress milestones
|
|
||||||
return s[i].Name < s[j].Name
|
|
||||||
}
|
|
||||||
return s[i].Progress >= s[j].Progress // put more complete milestones first
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
tagMilestonesData = "milestonesData"
|
|
||||||
milestonesTimeFormat = "January 2 2006"
|
|
||||||
noMilestone = "no milestone"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
reports[tagMilestonesData] = report{refreshMilestones, renderMilestones, milestonesTemplate}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMilestones(client *gogithub.Client, config *githubConfig) ([]githubMilestone, error) {
|
|
||||||
|
|
||||||
if !config.ShowMilestones {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := []githubMilestone{}
|
|
||||||
|
|
||||||
hadRepo := make(map[string]bool)
|
|
||||||
|
|
||||||
for _, orb := range config.Lists {
|
|
||||||
if orb.Included {
|
|
||||||
rName := orb.Owner + "/" + orb.Repo
|
|
||||||
|
|
||||||
if !hadRepo[rName] {
|
|
||||||
|
|
||||||
for _, state := range []string{"open", "closed"} {
|
|
||||||
|
|
||||||
opts := &gogithub.MilestoneListOptions{
|
|
||||||
Sort: "updated",
|
|
||||||
State: state,
|
|
||||||
ListOptions: gogithub.ListOptions{PerPage: config.BranchLines}}
|
|
||||||
|
|
||||||
guff, _, err := client.Issues.ListMilestones(context.Background(), orb.Owner, orb.Repo, opts)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range guff {
|
|
||||||
include := true
|
|
||||||
if state == "closed" {
|
|
||||||
if config.SincePtr != nil {
|
|
||||||
if (*config.SincePtr).After(*v.ClosedAt) {
|
|
||||||
include = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if include {
|
|
||||||
dd := "no due date"
|
|
||||||
if v.DueOn != nil {
|
|
||||||
// TODO refactor to add message in red if the milestone is overdue
|
|
||||||
dd = "due " + (*v.DueOn).Format(milestonesTimeFormat) + ""
|
|
||||||
}
|
|
||||||
up := ""
|
|
||||||
if v.UpdatedAt != nil {
|
|
||||||
up = (*v.UpdatedAt).Format(milestonesTimeFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
progress := float64(*v.ClosedIssues*100) / float64(*v.OpenIssues+*v.ClosedIssues)
|
|
||||||
|
|
||||||
ret = append(ret, githubMilestone{
|
|
||||||
Repo: repoName(rName),
|
|
||||||
Private: orb.Private,
|
|
||||||
Name: *v.Title,
|
|
||||||
URL: template.URL(fmt.Sprintf(
|
|
||||||
"https://github.com/%s/%s/milestone/%d",
|
|
||||||
orb.Owner, orb.Repo, *v.Number)), // *v.HTMLURL does not give the correct value
|
|
||||||
IsOpen: *v.State == "open",
|
|
||||||
OpenIssues: *v.OpenIssues,
|
|
||||||
ClosedIssues: *v.ClosedIssues,
|
|
||||||
CompleteMsg: fmt.Sprintf("%2.0f%%", progress),
|
|
||||||
DueDate: dd,
|
|
||||||
UpdatedAt: up,
|
|
||||||
Progress: uint(progress),
|
|
||||||
IsMilestone: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hadRepo[rName] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshMilestones(gr *githubRender, config *githubConfig, client *gogithub.Client) (err error) {
|
|
||||||
|
|
||||||
if !config.ShowMilestones {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
gr.Milestones, err = getMilestones(client, config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gr.OpenMS = 0
|
|
||||||
gr.ClosedMS = 0
|
|
||||||
for _, v := range gr.Milestones {
|
|
||||||
if v.IsOpen {
|
|
||||||
gr.OpenMS++
|
|
||||||
} else {
|
|
||||||
gr.ClosedMS++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gr.HasMilestones = (gr.OpenMS + gr.ClosedMS) > 0
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderMilestones(payload *githubRender, c *githubConfig) error {
|
|
||||||
|
|
||||||
if !c.ShowMilestones {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
hadRepo := make(map[string]bool)
|
|
||||||
payload.RepoCount = 0
|
|
||||||
for _, orb := range payload.List {
|
|
||||||
rName := orb.Owner + "/" + orb.Repo
|
|
||||||
if !hadRepo[rName] {
|
|
||||||
if orb.Included {
|
|
||||||
|
|
||||||
payload.RepoCount++
|
|
||||||
issuesOpen, issuesClosed := 0, 0
|
|
||||||
for _, iss := range payload.Issues {
|
|
||||||
if iss.Repo == repoName(rName) {
|
|
||||||
if iss.Milestone == noMilestone {
|
|
||||||
if iss.IsOpen {
|
|
||||||
issuesOpen++
|
|
||||||
} else {
|
|
||||||
issuesClosed++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if issuesClosed+issuesOpen > 0 {
|
|
||||||
//payload.Milestones = append(payload.Milestones, githubMilestone{
|
|
||||||
// Repo: orb.Repo, Private: orb.Private, Name: noMilestone, IsOpen: true,
|
|
||||||
// OpenIssues: issuesOpen, ClosedIssues: issuesClosed, URL: template.URL(orb.URL),
|
|
||||||
//})
|
|
||||||
}
|
|
||||||
|
|
||||||
hadRepo[rName] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(milestonesToSort(payload.Milestones))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue