mirror of
https://github.com/documize/community.git
synced 2025-08-08 06:55: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
|
||||
.idea
|
||||
selfcert
|
||||
gui
|
||||
gui/dist-prod
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -18,6 +18,7 @@ _convert
|
|||
bin/*
|
||||
dist/*
|
||||
embed/bindata/*
|
||||
edition/static/*
|
||||
gui/dist/*
|
||||
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
|
||||
RUN cd /go/src/github.com/documize/community
|
||||
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/assets /go/src/github.com/documize/community/edition/static/public/assets
|
||||
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
|
||||
FROM alpine:3.10
|
||||
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
|
||||
COPY --from=builder /go/src/github.com/documize/community/bin/documize-community-linux-amd64 /documize
|
||||
FROM alpine:3.16
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=builder /go/src/github.com/documize/community/bin/documize-community /documize
|
||||
EXPOSE 5001
|
||||
ENTRYPOINT [ "/documize" ]
|
||||
|
|
79
README.md
79
README.md
|
@ -1,57 +1,71 @@
|
|||
Documize is an open source modern, lightweight and comprehensive alternative to Confluence.
|
||||
|
||||
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.
|
||||
Documize Community is an open source, modern, self-hosted, enterprise-grade knowledge management solution.
|
||||
|
||||
- Built for technical and non-technical users
|
||||
- Designed to unify both customer-facing and internal documentation
|
||||
- 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
|
||||
|
||||
[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.*
|
||||
>
|
||||
> **Harvey Kandola, CEO/Founder @ Documize**
|
||||
The Community+ edition is the "enterprise" offering with advanced capabilities and customer support:
|
||||
|
||||
- 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
|
||||
|
||||
- Linux
|
||||
- Windows
|
||||
- macOS
|
||||
- Raspberry Pi (using the ARM builds)
|
||||
- Raspberry Pi (ARM build)
|
||||
|
||||
Support for AMD and ARM 64 bit architectures.
|
||||
|
||||
## 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+)
|
||||
- Microsoft SQL Server (2016+ with FTS)
|
||||
- Microsoft SQL Azure (v12+)
|
||||
- MySQL (v5.7.10+ and v8.0.12+)
|
||||
- Percona (v5.7.16-10+)
|
||||
- MariaDB (10.3.0+)
|
||||
|
||||
## Browser Support
|
||||
|
||||
- Chrome
|
||||
- Firefox
|
||||
- Chrome
|
||||
- Safari
|
||||
- Microsoft Edge
|
||||
- Brave
|
||||
- Vivaldi
|
||||
- Opera
|
||||
- Microsoft Edge (v42+)
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- Go (v1.13.0)
|
||||
- EmberJS (v3.12.0)
|
||||
- Go (v1.23.4)
|
||||
- Ember JS (v3.12.0)
|
||||
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
|
||||
<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
|
||||
call ember b -o dist-prod/ --environment=production
|
||||
::Call allows the rest of the file to run
|
||||
|
||||
echo "Copying Ember assets..."
|
||||
cd ..
|
||||
|
||||
rd /s /q embed\bindata\public
|
||||
mkdir embed\bindata\public
|
||||
rd /s /q edition\static\public
|
||||
mkdir edition\static\public
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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
|
||||
copy gui\dist-prod\favicon.ico embed\bindata\public
|
||||
copy gui\dist-prod\manifest.json embed\bindata\public
|
||||
echo "Copying static files"
|
||||
copy gui\dist-prod\*.* edition\static
|
||||
|
||||
rd /s /q embed\bindata\mail
|
||||
mkdir embed\bindata\mail
|
||||
copy domain\mail\*.html embed\bindata\mail
|
||||
copy core\database\templates\*.html embed\bindata
|
||||
echo "Copying favicon.ico"
|
||||
copy gui\dist-prod\favicon.ico edition\static\public
|
||||
|
||||
rd /s /q embed\bindata\scripts
|
||||
mkdir embed\bindata\scripts
|
||||
mkdir embed\bindata\scripts\mysql
|
||||
mkdir embed\bindata\scripts\postgresql
|
||||
mkdir embed\bindata\scripts\sqlserver
|
||||
echo "Copying manifest.json"
|
||||
copy gui\dist-prod\manifest.json edition\static\public
|
||||
|
||||
echo "Copying mail templates"
|
||||
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"
|
||||
robocopy /e /NFL /NDL /NJH core\database\scripts\mysql embed\bindata\scripts\mysql
|
||||
robocopy /e /NFL /NDL /NJH core\database\scripts\postgresql embed\bindata\scripts\postgresql
|
||||
robocopy /e /NFL /NDL /NJH core\database\scripts\sqlserver embed\bindata\scripts\sqlserver
|
||||
robocopy /e /NFL /NDL /NJH core\database\scripts\mysql edition\static\scripts\mysql
|
||||
robocopy /e /NFL /NDL /NJH core\database\scripts\postgresql edition\static\scripts\postgresql
|
||||
robocopy /e /NFL /NDL /NJH core\database\scripts\sqlserver edition\static\scripts\sqlserver
|
||||
|
||||
rd /s /q embed\bindata\onboard
|
||||
mkdir embed\bindata\onboard
|
||||
robocopy /e /NFL /NDL /NJH domain\onboard\*.json embed\bindata\onboard
|
||||
|
||||
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 ..
|
||||
rd /s /q edition\static\onboard
|
||||
mkdir edition\static\onboard
|
||||
robocopy /e /NFL /NDL /NJH domain\onboard edition\static\onboard *.json
|
||||
|
||||
echo "Compiling Windows"
|
||||
set GOOS=windows
|
||||
go build -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
|
||||
go build -mod=vendor -trimpath -gcflags="all=-trimpath=$GOPATH" -o bin/documize-community-windows-amd64.exe edition/community.go
|
||||
|
|
91
build.sh
91
build.sh
|
@ -8,62 +8,65 @@ echo "Build process started $NOW"
|
|||
|
||||
echo "Building Ember assets..."
|
||||
cd gui
|
||||
# export NODE_OPTIONS=--openssl-legacy-provider
|
||||
ember build ---environment=production --output-path dist-prod --suppress-sizes true
|
||||
cd ..
|
||||
|
||||
echo "Copying Ember assets..."
|
||||
rm -rf embed/bindata/public
|
||||
mkdir -p embed/bindata/public
|
||||
cp -r gui/dist-prod/assets embed/bindata/public
|
||||
cp -r gui/dist-prod/codemirror embed/bindata/public/codemirror
|
||||
cp -r gui/dist-prod/prism embed/bindata/public/prism
|
||||
cp -r gui/dist-prod/sections embed/bindata/public/sections
|
||||
cp -r gui/dist-prod/tinymce embed/bindata/public/tinymce
|
||||
cp -r gui/dist-prod/pdfjs embed/bindata/public/pdfjs
|
||||
cp gui/dist-prod/*.* embed/bindata
|
||||
cp gui/dist-prod/favicon.ico embed/bindata/public
|
||||
cp gui/dist-prod/manifest.json embed/bindata/public
|
||||
rm -rf edition/static/public
|
||||
mkdir -p edition/static/public
|
||||
cp -r gui/dist-prod/assets edition/static/public
|
||||
cp -r gui/dist-prod/codemirror edition/static/public/codemirror
|
||||
cp -r gui/dist-prod/prism edition/static/public/prism
|
||||
cp -r gui/dist-prod/sections edition/static/public/sections
|
||||
cp -r gui/dist-prod/tinymce edition/static/public/tinymce
|
||||
cp -r gui/dist-prod/pdfjs edition/static/public/pdfjs
|
||||
cp -r gui/dist-prod/i18n edition/static/public/i18n
|
||||
cp gui/dist-prod/*.* edition/static
|
||||
cp gui/dist-prod/favicon.ico edition/static/public
|
||||
cp gui/dist-prod/manifest.json edition/static/public
|
||||
|
||||
rm -rf embed/bindata/mail
|
||||
mkdir -p embed/bindata/mail
|
||||
cp domain/mail/*.html embed/bindata/mail
|
||||
cp core/database/templates/*.html embed/bindata
|
||||
rm -rf edition/static/mail
|
||||
mkdir -p edition/static/mail
|
||||
cp domain/mail/*.html edition/static/mail
|
||||
cp core/database/templates/*.html edition/static
|
||||
|
||||
rm -rf embed/bindata/scripts
|
||||
mkdir -p embed/bindata/scripts
|
||||
mkdir -p embed/bindata/scripts/mysql
|
||||
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 edition/static/i18n
|
||||
mkdir -p edition/static/i18n
|
||||
cp -r gui/dist-prod/i18n/*.json edition/static/i18n
|
||||
|
||||
rm -rf embed/bindata/onboard
|
||||
mkdir -p embed/bindata/onboard
|
||||
cp -r domain/onboard/*.json embed/bindata/onboard
|
||||
rm -rf edition/static/scripts
|
||||
mkdir -p edition/static/scripts
|
||||
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..."
|
||||
# go get -u github.com/jteeuwen/go-bindata/...
|
||||
# go get -u github.com/elazarl/go-bindata-assetfs/...
|
||||
cd embed
|
||||
go generate
|
||||
rm -rf edition/static/onboard
|
||||
mkdir -p edition/static/onboard
|
||||
cp -r domain/onboard/*.json edition/static/onboard
|
||||
|
||||
cd ..
|
||||
echo "Compiling for Linux..."
|
||||
env GOOS=linux GOARCH=amd64 go build -trimpath -o bin/documize-community-linux-amd64 ./edition/community.go
|
||||
echo "Compiling for macOS..."
|
||||
env GOOS=darwin GOARCH=amd64 go build -trimpath -o bin/documize-community-darwin-amd64 ./edition/community.go
|
||||
echo "Compiling for Windows..."
|
||||
env GOOS=windows GOARCH=amd64 go build -trimpath -o bin/documize-community-windows-amd64.exe ./edition/community.go
|
||||
echo "Compiling for ARM..."
|
||||
env GOOS=linux GOARCH=arm go build -trimpath -o bin/documize-community-linux-arm ./edition/community.go
|
||||
echo "Compiling for ARM64..."
|
||||
env GOOS=linux GOARCH=arm64 go build -trimpath -o bin/documize-community-linux-arm64 ./edition/community.go
|
||||
echo "Compiling for macOS Intel..."
|
||||
env GOOS=darwin GOARCH=amd64 go build -mod=vendor -trimpath -o bin/documize-community-darwin-amd64 ./edition/community.go
|
||||
echo "Compiling for macOS ARM..."
|
||||
env GOOS=darwin GOARCH=arm64 go build -mod=vendor -trimpath -o bin/documize-community-darwin-arm64 ./edition/community.go
|
||||
echo "Compiling for Windows AMD..."
|
||||
env GOOS=windows GOARCH=amd64 go build -mod=vendor -trimpath -o bin/documize-community-windows-amd64.exe ./edition/community.go
|
||||
echo "Compiling for Linux AMD..."
|
||||
env GOOS=linux GOARCH=amd64 go build -mod=vendor -trimpath -o bin/documize-community-linux-amd64 ./edition/community.go
|
||||
echo "Compiling for Linux ARM..."
|
||||
env GOOS=linux GOARCH=arm go build -mod=vendor -trimpath -o bin/documize-community-linux-arm ./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."
|
||||
|
||||
# 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
|
||||
# ldd test
|
||||
|
||||
# https://stackoverflow.com/questions/55664630/how-do-i-migrate-from-dep-to-go-modules
|
||||
|
|
|
@ -19,8 +19,8 @@ import (
|
|||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"context"
|
||||
api "github.com/documize/community/core/convapi"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Msword type provides a peg to hang the Convert method on.
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"github.com/documize/community/core/api/plugins"
|
||||
api "github.com/documize/community/core/convapi"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"context"
|
||||
)
|
||||
|
||||
// Convert provides the entry-point into the document conversion process.
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
|
||||
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.
|
||||
|
|
|
@ -16,9 +16,9 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"context"
|
||||
api "github.com/documize/community/core/convapi"
|
||||
"github.com/documize/community/core/stringutil"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/html"
|
||||
"golang.org/x/net/html/atom"
|
||||
)
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
|
||||
"github.com/documize/blackfriday"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"context"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
if len(flds) == 0 {
|
||||
if len(flds) <= 5 {
|
||||
runtime.Log.Info("Database: starting setup mode for empty database")
|
||||
runtime.Flags.SiteMode = env.SiteModeSetup
|
||||
return false
|
||||
|
|
|
@ -26,7 +26,7 @@ func InstallUpgrade(runtime *env.Runtime, existingDB bool) (err error) {
|
|||
// amLeader := false
|
||||
|
||||
// Get all SQL scripts.
|
||||
scripts, err := LoadScripts()
|
||||
scripts, err := LoadScripts(runtime)
|
||||
if err != nil {
|
||||
runtime.Log.Error("Database: unable to load scripts", err)
|
||||
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
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/documize/community/core/asset"
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/server/web"
|
||||
)
|
||||
|
||||
// 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.
|
||||
func LoadScripts() (s Scripts, err error) {
|
||||
assetDir := "bindata/scripts"
|
||||
|
||||
func LoadScripts(runtime *env.Runtime) (s Scripts, err error) {
|
||||
// MySQL
|
||||
s.MySQL, err = loadFiles(fmt.Sprintf("%s/mysql", assetDir))
|
||||
s.MySQL, err = loadFiles(runtime.Assets, "scripts/mysql")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// PostgreSQL
|
||||
s.PostgreSQL, err = loadFiles(fmt.Sprintf("%s/postgresql", assetDir))
|
||||
s.PostgreSQL, err = loadFiles(runtime.Assets, "scripts/postgresql")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// PostgreSQL
|
||||
s.SQLServer, err = loadFiles(fmt.Sprintf("%s/sqlserver", assetDir))
|
||||
s.SQLServer, err = loadFiles(runtime.Assets, "scripts/sqlserver")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -70,20 +69,22 @@ func SpecificScripts(runtime *env.Runtime, all Scripts) (s []Script) {
|
|||
}
|
||||
|
||||
// loadFiles returns all SQL scripts in specified folder as [][]byte.
|
||||
func loadFiles(path string) (b []Script, err error) {
|
||||
buf := []byte{}
|
||||
scripts, err := web.AssetDir(path)
|
||||
func loadFiles(fs embed.FS, path string) (b []Script, err error) {
|
||||
scripts, err := asset.FetchStaticDir(fs, path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
/* 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 `message` `c_message` VARCHAR(500) NOT NULL,
|
||||
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 `allowanonymousaccess` `c_anonaccess` BOOL NOT NULL DEFAULT 0,
|
||||
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_message varchar(500) COLLATE ucs_basic NOT NULL,
|
||||
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_anonaccess bool NOT NULL DEFAULT '0',
|
||||
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_message NVARCHAR(500) COLLATE Latin1_General_CS_AS NOT NULL,
|
||||
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_anonaccess BIT NOT NULL DEFAULT '0',
|
||||
c_authprovider NVARCHAR(20) COLLATE Latin1_General_CS_AS NOT NULL DEFAULT 'documize',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Enterprise edition */
|
||||
/* Community edition */
|
||||
|
||||
-- Fulltext search support
|
||||
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>
|
||||
<div class="container">
|
||||
<div class="logo">
|
||||
<img src="/assets/img/setup/logo.png" alt="Documize">
|
||||
<img src="/assets/img/setup/logo.png" alt="Documize Community">
|
||||
</div>
|
||||
<div class="content clearfix">
|
||||
<div class="image">
|
||||
|
@ -110,7 +110,7 @@
|
|||
</div>
|
||||
<div class="text">
|
||||
<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>
|
||||
</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
|
||||
SSLCertFile string // (optional) name of SSL certificate 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
|
||||
Location string // reserved
|
||||
ConfigSource string // tells us if configuration info was obtained from command line or config file
|
||||
|
@ -43,6 +44,7 @@ type httpConfig struct {
|
|||
ForceSSLPort int
|
||||
Cert string
|
||||
Key string
|
||||
TLSVersion string
|
||||
}
|
||||
|
||||
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.SSLCertFile = ct.HTTP.Cert
|
||||
f.SSLKeyFile = ct.HTTP.Key
|
||||
f.TLSVersion = ct.HTTP.TLSVersion
|
||||
f.Location = strings.ToLower(ct.Install.Location)
|
||||
|
||||
if len(f.TLSVersion) == 0 {
|
||||
f.TLSVersion = "1.3"
|
||||
}
|
||||
|
||||
ok = true
|
||||
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.
|
||||
func commandLineEnv() (f Flags, ok bool) {
|
||||
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(&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(&port, "port", false, "http/https port number")
|
||||
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(&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"`)
|
||||
|
@ -118,9 +124,14 @@ func commandLineEnv() (f Flags, ok bool) {
|
|||
f.SiteMode = siteMode
|
||||
f.SSLCertFile = certFile
|
||||
f.SSLKeyFile = keyFile
|
||||
f.TLSVersion = TLSVersion
|
||||
f.Location = strings.ToLower(location)
|
||||
f.ConfigSource = "flags/environment"
|
||||
|
||||
if len(f.TLSVersion) == 0 {
|
||||
f.TLSVersion = "1.3"
|
||||
}
|
||||
|
||||
return f, ok
|
||||
}
|
||||
|
||||
|
|
2
core/env/runtime.go
vendored
2
core/env/runtime.go
vendored
|
@ -15,6 +15,7 @@ package env
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
@ -42,6 +43,7 @@ type Runtime struct {
|
|||
StoreProvider StoreProvider
|
||||
Log Logger
|
||||
Product domain.Product
|
||||
Assets embed.FS
|
||||
}
|
||||
|
||||
// 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 {
|
||||
case <-time.After(timeout):
|
||||
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
|
||||
//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.
|
||||
func WriteError(w http.ResponseWriter, method string) {
|
||||
writeStatus(w, http.StatusBadRequest)
|
||||
w.Write([]byte("{Error: 'Internal server error'}"))
|
||||
writeStatus(w, http.StatusBadRequest)
|
||||
w.Write([]byte("{Error: 'Internal server error'}"))
|
||||
}
|
||||
|
||||
// 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)
|
||||
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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# You can move between editions anytime without any data loss
|
||||
|
@ -14,7 +14,7 @@ version: "3"
|
|||
|
||||
services:
|
||||
db:
|
||||
image: postgres:latest
|
||||
image: postgres:12
|
||||
restart: always
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
@ -29,7 +29,7 @@ services:
|
|||
|
||||
app:
|
||||
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:
|
||||
- db
|
||||
ports:
|
||||
|
|
|
@ -13,7 +13,6 @@ package activity
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/documize/community/domain"
|
||||
|
@ -29,16 +28,16 @@ type Store struct {
|
|||
}
|
||||
|
||||
// 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.UserID = ctx.UserID
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute record user activity")
|
||||
s.Runtime.Log.Error("execute record user activity", err)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -46,7 +45,7 @@ func (s Store) RecordUserActivity(ctx domain.RequestContext, activity activity.U
|
|||
|
||||
// GetDocumentActivity returns the metadata for a specified document.
|
||||
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,
|
||||
a.c_docid AS documentid, a.c_sectionid AS sectionid, a.c_activitytype AS activitytype,
|
||||
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).
|
||||
func (s Store) DeleteDocumentChangeActivity(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
||||
rows, err = s.DeleteWhere(ctx.Transaction,
|
||||
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))
|
||||
_, 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)
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -180,6 +180,7 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
dataSize := len(a.Data)
|
||||
|
||||
// w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", typ)
|
||||
w.Header().Set("Content-Disposition", `Attachment; filename="`+a.Filename+`" ; `+`filename*="`+a.Filename+`"`)
|
||||
if dataSize != 0 {
|
||||
|
@ -191,7 +192,6 @@ func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
|
|||
h.Runtime.Log.Error("write attachment", err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeAttachmentDownload)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ package attachment
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"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.
|
||||
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'",
|
||||
ctx.OrgID, sectionID))
|
||||
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_doc_attachment WHERE c_orgid=? AND c_sectionid=?"), ctx.OrgID, sectionID)
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ package audit
|
|||
|
||||
import (
|
||||
"time"
|
||||
"database/sql"
|
||||
|
||||
"github.com/documize/community/domain"
|
||||
"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.Type = string(t)
|
||||
|
||||
tx, err := s.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
s.Runtime.Log.Error("transaction", err)
|
||||
tx, ok := s.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||
if !ok {
|
||||
s.Runtime.Log.Info("unable to start transaction")
|
||||
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)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
s.Runtime.Rollback(tx)
|
||||
s.Runtime.Log.Error("prepare audit insert", err)
|
||||
return
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
s.Runtime.Commit(tx)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ func AddExternalUser(ctx domain.RequestContext, rt *env.Runtime, store *store.St
|
|||
if addUser {
|
||||
userID = uniqueid.Generate()
|
||||
u.RefID = userID
|
||||
u.Locale = ctx.OrgLocale
|
||||
|
||||
err = store.User.Add(ctx, u)
|
||||
if err != nil {
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/i18n"
|
||||
"github.com/documize/community/core/response"
|
||||
"github.com/documize/community/core/secrets"
|
||||
"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, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID)
|
||||
if err != nil {
|
||||
result.Message = "Error: unable to get organization record"
|
||||
result.Message = i18n.Localize(ctx.Locale, "server_err_org")
|
||||
result.IsError = true
|
||||
response.WriteJSON(w, result)
|
||||
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
|
||||
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
|
||||
response.WriteJSON(w, result)
|
||||
h.Runtime.Log.Info(result.Message)
|
||||
|
@ -77,7 +78,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
|||
c := ath.KeycloakConfig{}
|
||||
err = json.Unmarshal([]byte(org.AuthConfig), &c)
|
||||
if err != nil {
|
||||
result.Message = "Error: unable read Keycloak configuration data"
|
||||
result.Message = i18n.Localize(ctx.Locale, "server_keycloak_error2")
|
||||
result.IsError = true
|
||||
response.WriteJSON(w, result)
|
||||
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
|
||||
kcUsers, err := Fetch(c)
|
||||
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
|
||||
response.WriteJSON(w, result)
|
||||
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
|
||||
dmzUsers, err := h.Store.User.GetUsersForOrganization(ctx, "", 99999)
|
||||
if err != nil {
|
||||
result.Message = "Error: unable to fetch Documize users"
|
||||
result.Message = i18n.Localize(ctx.Locale, "server_error_user")
|
||||
result.IsError = true
|
||||
response.WriteJSON(w, result)
|
||||
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",
|
||||
len(kcUsers), len(insert), missing)
|
||||
result.Message = i18n.Localize(ctx.Locale, "server_keycloak_summary",
|
||||
fmt.Sprintf("%d", len(kcUsers)), fmt.Sprintf("%d", len(insert)), fmt.Sprintf("%d", missing))
|
||||
|
||||
response.WriteJSON(w, result)
|
||||
h.Runtime.Log.Info(result.Message)
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/i18n"
|
||||
"github.com/documize/community/core/response"
|
||||
"github.com/documize/community/core/secrets"
|
||||
"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, err := h.Store.Organization.GetOrganization(ctx, ctx.OrgID)
|
||||
if err != nil {
|
||||
result.Message = "Error: unable to get organization record"
|
||||
result.Message = i18n.Localize(ctx.Locale, "server_error_org")
|
||||
result.IsError = true
|
||||
response.WriteJSON(w, result)
|
||||
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
|
||||
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
|
||||
response.WriteJSON(w, result)
|
||||
h.Runtime.Log.Info(result.Message)
|
||||
|
@ -166,7 +167,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
|||
c := lm.LDAPConfig{}
|
||||
err = json.Unmarshal([]byte(org.AuthConfig), &c)
|
||||
if err != nil {
|
||||
result.Message = "Error: unable read LDAP configuration data"
|
||||
result.Message = i18n.Localize(ctx.Locale, "server_ldap_error2")
|
||||
result.IsError = true
|
||||
response.WriteJSON(w, result)
|
||||
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.
|
||||
ldapUsers, err := fetchUsers(c)
|
||||
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
|
||||
response.WriteJSON(w, result)
|
||||
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
|
||||
dmzUsers, err := h.Store.User.GetUsersForOrganization(ctx, "", 99999)
|
||||
if err != nil {
|
||||
result.Message = "Error: unable to fetch Documize users"
|
||||
result.Message = i18n.Localize(ctx.Locale, "server_error_user")
|
||||
result.IsError = true
|
||||
response.WriteJSON(w, result)
|
||||
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.Message = "Sync complete with LDAP server"
|
||||
result.Message = fmt.Sprintf(
|
||||
"LDAP sync found %d users, %d new users added, %d users with missing data ignored",
|
||||
len(ldapUsers), len(insert), missing)
|
||||
result.Message = i18n.Localize(ctx.Locale, "server_ldap_complete")
|
||||
result.Message = i18n.Localize(ctx.Locale, "server_ldap_summary", fmt.Sprintf("%d", len(ldapUsers)), fmt.Sprintf("%d", len(insert)), fmt.Sprintf("%d", missing))
|
||||
|
||||
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)
|
||||
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 {
|
||||
h.Runtime.Log.Info("Adding new LDAP user " + lu.Email + " @ " + dom)
|
||||
|
||||
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
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@ import (
|
|||
"github.com/documize/community/core/stringutil"
|
||||
lm "github.com/documize/community/model/auth"
|
||||
"github.com/documize/community/model/user"
|
||||
ld "github.com/go-ldap/ldap/v3"
|
||||
"github.com/pkg/errors"
|
||||
ld "gopkg.in/ldap.v3"
|
||||
)
|
||||
|
||||
// Connect establishes connection to LDAP server.
|
||||
|
@ -172,9 +172,9 @@ func executeGroupFilter(c lm.LDAPConfig) (u []lm.LDAPUser, err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
// Get CN element from DN.
|
||||
for _, entry := range rawMembers {
|
||||
// get CN element from DN
|
||||
parts := strings.Split(entry, ",")
|
||||
parts := splitDN(entry)
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
@ -204,6 +204,44 @@ func executeGroupFilter(c lm.LDAPConfig) (u []lm.LDAPUser, err error) {
|
|||
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.
|
||||
func extractUser(c lm.LDAPConfig, e *ld.Entry) (u lm.LDAPUser) {
|
||||
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,
|
||||
coalesce(c_sub,`+b.Runtime.StoreProvider.JSONEmpty()+`) AS subscription,
|
||||
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)
|
||||
if err != nil {
|
||||
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,
|
||||
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_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
|
||||
FROM dmz_user u`+w)
|
||||
if err != nil {
|
||||
|
@ -545,7 +545,8 @@ func (b backerHandler) dmzCategory(files *[]backupItem) (err error) {
|
|||
err = b.Runtime.Db.Select(&cat, `
|
||||
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
|
||||
c_name AS name, c_default AS isdefault,
|
||||
c_created AS created, c_revised AS revised
|
||||
FROM dmz_category`+w)
|
||||
if err != nil {
|
||||
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_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_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)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "select.document")
|
||||
|
|
|
@ -89,7 +89,7 @@ func (h *Handler) Backup(w http.ResponseWriter, r *http.Request) {
|
|||
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}
|
||||
|
||||
|
@ -113,7 +113,7 @@ func (h *Handler) Backup(w http.ResponseWriter, r *http.Request) {
|
|||
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.
|
||||
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.
|
||||
// This HTTP header is CORS white-listed.
|
||||
w.Header().Set("x-documize-filename", filename)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// Write backup to response stream.
|
||||
x, err := w.Write(bk)
|
||||
|
@ -140,8 +141,6 @@ func (h *Handler) Backup(w http.ResponseWriter, r *http.Request) {
|
|||
if !spec.Retain {
|
||||
os.Remove(filename)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// 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,
|
||||
c_domain, c_service, c_email, c_anonaccess, c_authprovider, c_authconfig,
|
||||
c_maxtags, c_verified, c_serial, c_sub, c_active,
|
||||
c_theme, c_logo, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
c_theme, c_logo, c_locale, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
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),
|
||||
org[i].AllowAnonymousAccess, org[i].AuthProvider, org[i].AuthConfig,
|
||||
org[i].MaxTags, r.Runtime.StoreProvider.IsTrue(), org[i].Serial,
|
||||
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)
|
||||
if err != nil {
|
||||
r.Context.Transaction.Rollback()
|
||||
|
@ -412,6 +412,7 @@ func (r *restoreHandler) dmzOrg() (err error) {
|
|||
org[0].Title = r.Spec.Org.Title
|
||||
org[0].Subscription = r.Spec.Org.Subscription
|
||||
org[0].Theme = r.Spec.Org.Theme
|
||||
org[0].Locale = r.Spec.Org.Locale
|
||||
}
|
||||
|
||||
_, err = r.Context.Transaction.NamedExec(`UPDATE dmz_org SET
|
||||
|
@ -425,7 +426,8 @@ func (r *restoreHandler) dmzOrg() (err error) {
|
|||
c_message=:message,
|
||||
c_title=:title,
|
||||
c_serial=:serial,
|
||||
c_sub=:subscription
|
||||
c_sub=:subscription,
|
||||
c_locale=:locale
|
||||
WHERE c_refid=:refid`, &org[0])
|
||||
if err != nil {
|
||||
r.Context.Transaction.Rollback()
|
||||
|
@ -743,9 +745,9 @@ func (r *restoreHandler) dmzCategory() (err error) {
|
|||
|
||||
for i := range ct {
|
||||
_, 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)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`),
|
||||
ct[i].RefID, r.remapOrg(ct[i].OrgID), ct[i].SpaceID, ct[i].Name, ct[i].Created, ct[i].Revised)
|
||||
INSERT INTO dmz_category (c_refid, c_orgid, c_spaceid, c_name, c_default, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`),
|
||||
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 {
|
||||
r.Context.Transaction.Rollback()
|
||||
|
@ -1334,12 +1336,13 @@ func (r *restoreHandler) dmzDoc() (err error) {
|
|||
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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
c_lifecycle, c_versioned, c_versionid, c_versionorder, c_seq, c_groupid,
|
||||
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].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].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)
|
||||
|
||||
if err != nil {
|
||||
|
@ -1734,11 +1737,11 @@ func (r *restoreHandler) dmzUser() (err error) {
|
|||
_, err = r.Context.Transaction.Exec(r.Runtime.Db.Rebind(`
|
||||
INSERT INTO dmz_user
|
||||
(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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
c_password, c_salt, c_reset, c_active, c_lastversion, c_locale, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
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].LastVersion, u[i].Created, u[i].Revised)
|
||||
u[i].LastVersion, u[i].Locale, u[i].Created, u[i].Revised)
|
||||
|
||||
if err != nil {
|
||||
r.Context.Transaction.Rollback()
|
||||
|
|
|
@ -74,10 +74,10 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Category max length 30.
|
||||
// Category max length 50.
|
||||
cat.Name = strings.TrimSpace(cat.Name)
|
||||
if len(cat.Name) > 30 {
|
||||
cat.Name = cat.Name[:30]
|
||||
if len(cat.Name) > 50 {
|
||||
cat.Name = cat.Name[:50]
|
||||
}
|
||||
|
||||
err = h.Store.Category.Add(ctx, cat)
|
||||
|
@ -105,14 +105,6 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
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()
|
||||
|
||||
cat, err = h.Store.Category.Get(ctx, cat.RefID)
|
||||
|
@ -122,6 +114,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
h.Store.Space.SetStats(ctx, cat.SpaceID)
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeCategoryAdd)
|
||||
|
||||
response.WriteJSON(w, cat)
|
||||
|
@ -303,16 +296,9 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
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()
|
||||
|
||||
h.Store.Space.SetStats(ctx, cat.SpaceID)
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeCategoryDelete)
|
||||
|
||||
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.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 (?, ?, ?, ?, ?, ?)"),
|
||||
c.RefID, c.OrgID, c.SpaceID, c.Name, c.Created, c.Revised)
|
||||
_, 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.IsDefault, c.Created, c.Revised)
|
||||
|
||||
if err != nil {
|
||||
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.
|
||||
func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (c []category.Category, err error) {
|
||||
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
|
||||
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{}
|
||||
|
||||
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
|
||||
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.
|
||||
func (s Store) GetByOrg(ctx domain.RequestContext, userID string) (c []category.Category, err error) {
|
||||
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
|
||||
WHERE c_orgid=? 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) {
|
||||
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 {
|
||||
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
|
||||
func (s Store) Get(ctx domain.RequestContext, id string) (c category.Category, err error) {
|
||||
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
|
||||
WHERE c_orgid=? AND c_refid=?`),
|
||||
ctx.OrgID, id)
|
||||
|
@ -176,46 +176,69 @@ func (s Store) AssociateDocument(ctx domain.RequestContext, m category.Member) (
|
|||
|
||||
// DisassociateDocument removes document associatation from category.
|
||||
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)
|
||||
|
||||
return s.DeleteWhere(ctx.Transaction, sql)
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveCategoryMembership removes all category associations from the store.
|
||||
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)
|
||||
|
||||
return s.DeleteWhere(ctx.Transaction, sql)
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveSpaceCategoryMemberships removes all category associations from the store for the space.
|
||||
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)
|
||||
|
||||
return s.DeleteWhere(ctx.Transaction, sql)
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveDocumentCategories removes all document category associations from the store.
|
||||
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)
|
||||
|
||||
return s.DeleteWhere(ctx.Transaction, sql)
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteBySpace removes all category and category associations for given space.
|
||||
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 = s.DeleteWhere(ctx.Transaction, s1)
|
||||
if err != nil {
|
||||
return
|
||||
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_category_member WHERE c_orgid=? AND c_spaceid=?"),
|
||||
ctx.OrgID, spaceID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
|
||||
s2 := fmt.Sprintf("DELETE FROM dmz_category WHERE c_orgid='%s' AND c_spaceid='%s'", ctx.OrgID, spaceID)
|
||||
return s.DeleteWhere(ctx.Transaction, s2)
|
||||
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_category WHERE c_orgid=? AND c_spaceid=?"),
|
||||
ctx.OrgID, spaceID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s Store) GetDocumentCategoryMembership(ctx domain.RequestContext, documentID string) (c []category.Category, err error) {
|
||||
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
|
||||
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)
|
||||
|
|
|
@ -44,6 +44,8 @@ type RequestContext struct {
|
|||
GlobalAdmin bool
|
||||
ViewUsers bool
|
||||
Subscription Subscription
|
||||
Locale string
|
||||
OrgLocale string
|
||||
}
|
||||
|
||||
//GetAppURL returns full HTTP url for the app
|
||||
|
|
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/documize/community/model/activity"
|
||||
"github.com/documize/community/model/attachment"
|
||||
"github.com/documize/community/model/audit"
|
||||
cm "github.com/documize/community/model/category"
|
||||
"github.com/documize/community/model/doc"
|
||||
"github.com/documize/community/model/page"
|
||||
"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)
|
||||
}
|
||||
|
||||
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
|
||||
document := convertFileResult(filename, fileResult)
|
||||
document.Job = job
|
||||
|
@ -174,6 +176,7 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Sto
|
|||
document.UserID = ctx.UserID
|
||||
documentID := uniqueid.Generate()
|
||||
document.RefID = documentID
|
||||
document.Sequence = doc.Unsequenced
|
||||
|
||||
if r.Product.Edition == domain.CommunityEdition {
|
||||
document.Lifecycle = workflow.LifecycleLive
|
||||
|
@ -243,18 +246,33 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Sto
|
|||
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{
|
||||
SpaceID: newDocument.SpaceID,
|
||||
DocumentID: newDocument.RefID,
|
||||
SpaceID: document.SpaceID,
|
||||
DocumentID: document.RefID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
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()
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
store.Space.SetStats(ctx, newDocument.SpaceID)
|
||||
store.Audit.Record(ctx, audit.EventTypeDocumentUpload)
|
||||
|
||||
return
|
||||
|
|
|
@ -66,6 +66,8 @@ func FilterCategoryProtected(docs []doc.Document, cats []category.Category, memb
|
|||
|
||||
// CopyDocument clones an existing document
|
||||
func CopyDocument(ctx domain.RequestContext, s store.Store, documentID string) (newDocumentID string, err error) {
|
||||
unseq := doc.Unsequenced
|
||||
|
||||
doc, err := s.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
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.GroupID = ""
|
||||
doc.Template = false
|
||||
doc.Sequence = unseq
|
||||
|
||||
// Duplicate pages and associated meta
|
||||
pages, err := s.Page.GetPages(ctx, documentID)
|
||||
|
|
|
@ -14,6 +14,7 @@ package document
|
|||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
@ -63,6 +64,13 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|||
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)
|
||||
if err == sql.ErrNoRows {
|
||||
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
|
||||
if document.Lifecycle == workflow.LifecycleLive {
|
||||
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.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: document.SpaceID,
|
||||
DocumentID: document.RefID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
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)
|
||||
|
||||
response.WriteJSON(w, document)
|
||||
|
@ -190,10 +192,26 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// Sort document list by title.
|
||||
sort.Sort(doc.ByName(filtered))
|
||||
sortedDocs := doc.SortedDocs{}
|
||||
|
||||
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
|
||||
|
@ -231,36 +249,40 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
d.RefID = documentID
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
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, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// If space changed for document, remove document categories.
|
||||
oldDoc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Rollback(ctx.Transaction)
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Rollback(ctx.Transaction)
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// d.Name = bluemonday.StrictPolicy().Sanitize(d.Name)
|
||||
// d.Excerpt = bluemonday.StrictPolicy().Sanitize(d.Excerpt)
|
||||
|
||||
err = h.Store.Document.Update(ctx, d)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Rollback(ctx.Transaction)
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
|
@ -272,7 +294,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
if len(d.GroupID) > 0 {
|
||||
err = h.Store.Document.UpdateGroup(ctx, d)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Rollback(ctx.Transaction)
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
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)
|
||||
|
||||
|
@ -340,6 +367,13 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
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)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -376,13 +410,6 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
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)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
|
@ -411,16 +438,9 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
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()
|
||||
|
||||
h.Store.Space.SetStats(ctx, doc.SpaceID)
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeDocumentDelete)
|
||||
|
||||
go h.Indexer.DeleteDocument(ctx, documentID)
|
||||
|
@ -481,18 +501,13 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: "",
|
||||
DocumentID: "",
|
||||
Metadata: options.Keywords,
|
||||
SourceType: activity.SourceTypeSearch,
|
||||
ActivityType: activity.TypeSearched})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
}
|
||||
}
|
||||
|
@ -527,18 +542,13 @@ func (h *Handler) recordSearchActivity(ctx domain.RequestContext, q []search.Que
|
|||
}
|
||||
|
||||
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,
|
||||
DocumentID: q[i].DocumentID,
|
||||
Metadata: keywords,
|
||||
SourceType: activity.SourceTypeSearch,
|
||||
ActivityType: activity.TypeSearched})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
prev[q[i].DocumentID] = true
|
||||
}
|
||||
}
|
||||
|
@ -557,6 +567,13 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
|||
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)
|
||||
if err == sql.ErrNoRows {
|
||||
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.
|
||||
v := []doc.Version{}
|
||||
if len(document.GroupID) > 0 {
|
||||
// Get versions.
|
||||
// Get versions
|
||||
vt, err := h.Store.Document.GetVersions(ctx, document.GroupID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
// What about draft document versions?
|
||||
if record.DocumentLifecycle {
|
||||
// We can see and manage document lifecycle so take all versions.
|
||||
v = vt
|
||||
} else {
|
||||
// Only send back LIVE content because user cannot drafts.
|
||||
for i := range vt {
|
||||
if vt[i].Lifecycle == workflow.LifecycleLive {
|
||||
v = append(v, vt[i])
|
||||
}
|
||||
// Determine which document versions user can see.
|
||||
for i := range vt {
|
||||
// Everyone can see live documents
|
||||
if vt[i].Lifecycle == workflow.LifecycleLive {
|
||||
v = append(v, vt[i])
|
||||
}
|
||||
// Only lifecycle admins can see draft documents
|
||||
if vt[i].Lifecycle == workflow.LifecycleDraft && record.DocumentLifecycle {
|
||||
v = append(v, vt[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -706,24 +722,12 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
|||
data.Versions = v
|
||||
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 {
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: document.SpaceID,
|
||||
DocumentID: document.RefID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
}
|
||||
|
||||
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.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(export))
|
||||
_, _ = w.Write([]byte(export))
|
||||
}
|
||||
|
||||
// Duplicate makes a copy of a document.
|
||||
|
@ -826,10 +830,10 @@ func (h *Handler) Duplicate(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
var ok bool
|
||||
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||
if !ok {
|
||||
h.Runtime.Log.Info("unable to start transaction " + method)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1015,3 +1019,225 @@ func (h *Handler) Duplicate(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
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 http-equiv="X-UA-Compatible" content="IE=edge">`)
|
||||
export.WriteString("<title>")
|
||||
export.WriteString("Documize Export")
|
||||
export.WriteString("Documize Community Export")
|
||||
export.WriteString("</title>")
|
||||
export.WriteString("<style>")
|
||||
export.WriteString(baseCSS)
|
||||
|
@ -96,7 +96,7 @@ func BuildExport(ctx domain.RequestContext, s store.Store, spec exportSpec) (htm
|
|||
|
||||
// Show title and timestamp.
|
||||
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))
|
||||
|
||||
// Spit out table of contents.
|
||||
|
|
|
@ -16,10 +16,11 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/domain/store"
|
||||
"github.com/documize/community/model/doc"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// 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(`
|
||||
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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
c_template, c_protection, c_approval, c_lifecycle, c_versioned, c_versionid, c_versionorder, c_seq, c_groupid,
|
||||
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.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 {
|
||||
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_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_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
|
||||
WHERE c_orgid=? AND c_refid=?`),
|
||||
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_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_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
|
||||
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
|
||||
|
@ -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_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_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
|
||||
WHERE c_orgid=? AND c_spaceid=? AND c_template=`+s.IsTrue()+` AND c_lifecycle=1
|
||||
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_desc=:excerpt, c_slug=:slug, c_tags=:tags, c_template=:template,
|
||||
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
|
||||
WHERE c_orgid=:orgid AND c_refid=:refid`),
|
||||
&document)
|
||||
|
@ -250,31 +254,11 @@ func (s Store) MoveActivity(ctx domain.RequestContext, documentID, oldSpaceID, n
|
|||
// Delete removes the specified document.
|
||||
// Remove document pages, revisions, attachments, updates the search subsystem.
|
||||
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))
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, 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
|
||||
}
|
||||
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)
|
||||
ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_doc_attachment WHERE c_orgid=? AND c_docid=?"), ctx.OrgID, documentID)
|
||||
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)
|
||||
|
||||
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.
|
||||
// Remove document pages, revisions, attachments, updates the search subsystem.
|
||||
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))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, 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
|
||||
}
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/documize/community/domain"
|
||||
|
@ -104,7 +103,10 @@ func (s Store) Delete(ctx domain.RequestContext, refID string) (rows int64, err
|
|||
if err != nil {
|
||||
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.
|
||||
|
@ -143,15 +145,8 @@ func (s Store) JoinGroup(ctx domain.RequestContext, groupID, userID string) (err
|
|||
|
||||
// LeaveGroup removes user from group.
|
||||
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'",
|
||||
ctx.OrgID, groupID, userID))
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "clear group member")
|
||||
}
|
||||
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_group_member WHERE c_orgid=? AND c_groupid=? AND c_userid=?"),
|
||||
ctx.OrgID, groupID, userID)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -182,16 +177,8 @@ func (s Store) GetMembers(ctx domain.RequestContext) (r []group.Record, err erro
|
|||
|
||||
// RemoveUserGroups remove user from all group.
|
||||
func (s Store) RemoveUserGroups(ctx domain.RequestContext, userID string) (err error) {
|
||||
_, err = s.DeleteWhere(ctx.Transaction,
|
||||
fmt.Sprintf("DELETE FROM dmz_group_member WHERE c_orgid='%s' AND c_userid='%s'",
|
||||
ctx.OrgID, userID))
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "RemoveUserGroups")
|
||||
}
|
||||
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_group_member WHERE c_orgid=? AND c_userid=?"),
|
||||
ctx.OrgID, userID)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ package link
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -156,12 +155,18 @@ func (s Store) MarkOrphanAttachmentLink(ctx domain.RequestContext, attachmentID
|
|||
|
||||
// DeleteSourcePageLinks removes saved links for given source.
|
||||
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.
|
||||
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.
|
||||
|
|
|
@ -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;">
|
||||
<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">
|
||||
Document Approval Role Granted
|
||||
{{.Subject}}
|
||||
</td>
|
||||
</tr>
|
||||
<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;">
|
||||
<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">
|
||||
<p>You are requested to approve all changes to the following document:</p>
|
||||
<p>{{.ActionText}}</p>
|
||||
<p style="font-weight: bold;">{{.Document}}</p>
|
||||
<p>{{.Inviter}}</p>
|
||||
</td>
|
||||
</td>1
|
||||
</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;">View document</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>
|
||||
<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>
|
||||
</table>
|
||||
|
|
|
@ -16,6 +16,7 @@ package mail
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/documize/community/core/i18n"
|
||||
"github.com/documize/community/domain/smtp"
|
||||
)
|
||||
|
||||
|
@ -26,32 +27,32 @@ func (m *Mailer) DocumentApprover(recipient, inviterName, inviterEmail, url, doc
|
|||
|
||||
// check inviter name
|
||||
if inviterName == "Hello You" || len(inviterName) == 0 {
|
||||
inviterName = "Your colleague"
|
||||
inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender")
|
||||
}
|
||||
|
||||
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.ToName = recipient
|
||||
em.ReplyTo = inviterEmail
|
||||
em.ReplyName = inviterName
|
||||
|
||||
if IsBlockedEmailDomain(em.ToEmail) {
|
||||
return
|
||||
}
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
URL string
|
||||
Document string
|
||||
SenderEmail string
|
||||
ActionText string
|
||||
ClickHere string
|
||||
}{
|
||||
em.Subject,
|
||||
inviterName,
|
||||
url,
|
||||
document,
|
||||
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)
|
||||
|
|
|
@ -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;">
|
||||
<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 their Documize account
|
||||
{{.Subject}}
|
||||
</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 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;">
|
||||
<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>
|
||||
<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>
|
||||
</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;">
|
||||
<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 Documize
|
||||
{{.Subject}}
|
||||
</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 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;">
|
||||
<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">
|
||||
Your temporary password: {{.Password}}
|
||||
{{.Password}}
|
||||
</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>
|
||||
<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>
|
||||
</table>
|
||||
|
|
|
@ -13,15 +13,17 @@ package mail
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"github.com/documize/community/core/asset"
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/mail"
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/domain/setting"
|
||||
ds "github.com/documize/community/domain/smtp"
|
||||
"github.com/documize/community/domain/store"
|
||||
"github.com/documize/community/server/web"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Mailer provides emailing facilities
|
||||
|
@ -43,15 +45,15 @@ func (m *Mailer) Initialize() {
|
|||
func (m *Mailer) ParseTemplate(filename string, params interface{}) (html string, err error) {
|
||||
html = ""
|
||||
|
||||
file, err := web.ReadFile(filename)
|
||||
content, _, err := asset.FetchStatic(m.Runtime.Assets, filename)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("missing %s", filename))
|
||||
m.Runtime.Log.Error("failed to load mail template", err)
|
||||
return
|
||||
}
|
||||
|
||||
emailTemplate := string(file)
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
|
||||
t := template.Must(template.New("emailTemplate").Parse(content))
|
||||
t.Execute(buffer, ¶ms)
|
||||
|
||||
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;">
|
||||
<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">
|
||||
Your Documize password reset request
|
||||
{{.Subject}}
|
||||
</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">
|
||||
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;">
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</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;">
|
||||
<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 shared {{.Folder}} with you
|
||||
{{.Subject}}
|
||||
</td>
|
||||
</tr>
|
||||
<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 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;">Login to 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>
|
||||
<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>
|
||||
</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;">
|
||||
<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 shared {{.Folder}} with you on Documize
|
||||
{{.Subject}}
|
||||
</td>
|
||||
</tr>
|
||||
<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>
|
||||
</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: 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;">
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</table>
|
||||
|
|
|
@ -14,6 +14,7 @@ package mail
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/documize/community/core/i18n"
|
||||
"github.com/documize/community/domain/smtp"
|
||||
)
|
||||
|
||||
|
@ -24,20 +25,16 @@ func (m *Mailer) ShareSpaceExistingUser(recipient, inviterName, inviterEmail, ur
|
|||
|
||||
// check inviter name
|
||||
if inviterName == "Hello You" || len(inviterName) == 0 {
|
||||
inviterName = "Your colleague"
|
||||
inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender")
|
||||
}
|
||||
|
||||
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.ToName = recipient
|
||||
em.ReplyTo = inviterEmail
|
||||
em.ReplyName = inviterName
|
||||
|
||||
if IsBlockedEmailDomain(em.ToEmail) {
|
||||
return
|
||||
}
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
|
@ -45,6 +42,7 @@ func (m *Mailer) ShareSpaceExistingUser(recipient, inviterName, inviterEmail, ur
|
|||
Folder string
|
||||
Intro string
|
||||
SenderEmail string
|
||||
ClickHere string
|
||||
}{
|
||||
em.Subject,
|
||||
inviterName,
|
||||
|
@ -52,6 +50,7 @@ func (m *Mailer) ShareSpaceExistingUser(recipient, inviterName, inviterEmail, ur
|
|||
folder,
|
||||
intro,
|
||||
m.Config.SenderEmail,
|
||||
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||
}
|
||||
|
||||
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
|
||||
if inviterName == "Hello You" || len(inviterName) == 0 {
|
||||
inviterName = "Your colleague"
|
||||
inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender")
|
||||
}
|
||||
|
||||
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.ToName = recipient
|
||||
em.ReplyTo = inviterEmail
|
||||
em.ReplyName = inviterName
|
||||
|
||||
if IsBlockedEmailDomain(em.ToEmail) {
|
||||
return
|
||||
}
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
|
@ -98,6 +93,7 @@ func (m *Mailer) ShareSpaceNewUser(recipient, inviterName, inviterEmail, url, sp
|
|||
Invitation string
|
||||
Folder string
|
||||
SenderEmail string
|
||||
ClickHere string
|
||||
}{
|
||||
em.Subject,
|
||||
inviterName,
|
||||
|
@ -105,6 +101,7 @@ func (m *Mailer) ShareSpaceNewUser(recipient, inviterName, inviterEmail, url, sp
|
|||
invitationMessage,
|
||||
space,
|
||||
m.Config.SenderEmail,
|
||||
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||
}
|
||||
|
||||
html, err := m.ParseTemplate("mail/share-space-new-user.html", parameters)
|
||||
|
|
|
@ -14,6 +14,7 @@ package mail
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/documize/community/core/i18n"
|
||||
"github.com/documize/community/domain/smtp"
|
||||
)
|
||||
|
||||
|
@ -24,20 +25,16 @@ func (m *Mailer) InviteNewUser(recipient, inviterName, inviterEmail, url, userna
|
|||
|
||||
// check inviter name
|
||||
if inviterName == "Hello You" || len(inviterName) == 0 {
|
||||
inviterName = "Your colleague"
|
||||
inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender")
|
||||
}
|
||||
|
||||
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.ToName = recipient
|
||||
em.ReplyTo = inviterEmail
|
||||
em.ReplyName = inviterName
|
||||
|
||||
if IsBlockedEmailDomain(em.ToEmail) {
|
||||
return
|
||||
}
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
|
@ -45,13 +42,15 @@ func (m *Mailer) InviteNewUser(recipient, inviterName, inviterEmail, url, userna
|
|||
Username string
|
||||
Password string
|
||||
SenderEmail string
|
||||
ClickHere string
|
||||
}{
|
||||
em.Subject,
|
||||
inviterName,
|
||||
url,
|
||||
recipient,
|
||||
password,
|
||||
i18n.Localize(m.Context.Locale, "mail_template_password") + " " + password,
|
||||
m.Config.SenderEmail,
|
||||
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||
}
|
||||
|
||||
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
|
||||
if inviterName == "Hello You" || len(inviterName) == 0 {
|
||||
inviterName = "Your colleague"
|
||||
inviterName = i18n.Localize(m.Context.Locale, "mail_template_sender")
|
||||
}
|
||||
|
||||
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.ToName = recipient
|
||||
em.ReplyTo = inviterEmail
|
||||
em.ReplyName = inviterName
|
||||
|
||||
if IsBlockedEmailDomain(em.ToEmail) {
|
||||
return
|
||||
}
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
Inviter string
|
||||
URL string
|
||||
SenderEmail string
|
||||
ClickHere string
|
||||
}{
|
||||
em.Subject,
|
||||
inviterName,
|
||||
url,
|
||||
m.Config.SenderEmail,
|
||||
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||
}
|
||||
|
||||
html, err := m.ParseTemplate("mail/invite-existing-user.html", parameters)
|
||||
|
@ -125,22 +122,20 @@ func (m *Mailer) PasswordReset(recipient, url string) {
|
|||
m.Initialize()
|
||||
|
||||
em := smtp.EmailMessage{}
|
||||
em.Subject = "Documize password reset request"
|
||||
em.Subject = i18n.Localize(m.Context.Locale, "mail_template_reset_password")
|
||||
em.ToEmail = recipient
|
||||
em.ToName = recipient
|
||||
|
||||
if IsBlockedEmailDomain(em.ToEmail) {
|
||||
return
|
||||
}
|
||||
|
||||
parameters := struct {
|
||||
Subject string
|
||||
URL string
|
||||
SenderEmail string
|
||||
ClickHere string
|
||||
}{
|
||||
em.Subject,
|
||||
url,
|
||||
m.Config.SenderEmail,
|
||||
i18n.Localize(m.Context.Locale, "mail_template_click_here"),
|
||||
}
|
||||
|
||||
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/documize/community/core/asset"
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/response"
|
||||
"github.com/documize/community/core/uniqueid"
|
||||
|
@ -28,7 +29,6 @@ import (
|
|||
"github.com/documize/community/domain/store"
|
||||
om "github.com/documize/community/model/onboard"
|
||||
"github.com/documize/community/model/permission"
|
||||
"github.com/documize/community/server/web"
|
||||
)
|
||||
|
||||
// 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.
|
||||
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 {
|
||||
err = errors.Wrap(err, fmt.Sprintf("missing %s", filename))
|
||||
h.Runtime.Log.Error("failed to load file", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &v)
|
||||
err = json.Unmarshal([]byte(content), &v)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("failed to read %s as JSON", filename))
|
||||
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)))
|
||||
for i := range data.Category {
|
||||
_, 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)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`),
|
||||
INSERT INTO dmz_category (c_refid, c_orgid, c_spaceid, c_name, c_default, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`),
|
||||
h.getMappedID("category", data.Category[i].RefID),
|
||||
data.Context.OrgID,
|
||||
h.getMappedID("space", data.Category[i].SpaceID),
|
||||
data.Category[i].Name,
|
||||
data.Category[i].IsDefault,
|
||||
data.Category[i].Created,
|
||||
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,
|
||||
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
|
||||
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
|
||||
WHERE c_refid=?`),
|
||||
id)
|
||||
|
@ -84,7 +84,7 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
|
|||
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
||||
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
|
||||
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
|
||||
WHERE c_domain=? AND c_active=`+s.IsTrue()),
|
||||
subdomain)
|
||||
|
@ -99,7 +99,7 @@ func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, er
|
|||
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
|
||||
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig,
|
||||
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
|
||||
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
|
||||
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`,
|
||||
&org)
|
||||
|
||||
|
|
|
@ -1334,6 +1334,7 @@ func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
|
|||
documentID := request.Param(r, "documentID")
|
||||
if len(documentID) == 0 {
|
||||
response.WriteMissingDataError(w, method, "documentID")
|
||||
h.Runtime.Log.Infof("Document ID missing for org %s", ctx.OrgID)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1343,7 +1344,7 @@ func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
|
|||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
h.Runtime.Log.Infof("Document not found %s", documentID)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1503,18 +1504,13 @@ func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
h.Runtime.Log.Error(method, err)
|
||||
} else {
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
SpaceID: doc.SpaceID,
|
||||
DocumentID: doc.RefID,
|
||||
Metadata: source, // deliberate
|
||||
SourceType: activity.SourceTypeSearch, // deliberate
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
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.
|
||||
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)
|
||||
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 {
|
||||
_, _ = 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.Transaction.Exec(s.Bind("DELETE FROM dmz_section_meta WHERE c_orgid=? AND c_sectionid=?"),
|
||||
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
|
||||
}
|
||||
|
@ -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.
|
||||
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'",
|
||||
ctx.OrgID, pageID))
|
||||
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_section_revision WHERE c_orgid=? AND c_sectionid=?"),
|
||||
ctx.OrgID, pageID)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ func CanViewDocument(ctx domain.RequestContext, s store.Store, documentID string
|
|||
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 {
|
||||
document, err := s.Document.Get(ctx, documentID)
|
||||
|
||||
|
@ -98,7 +98,7 @@ func CanChangeDocument(ctx domain.RequestContext, s store.Store, documentID stri
|
|||
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 {
|
||||
document, err := s.Document.Get(ctx, documentID)
|
||||
|
||||
|
@ -147,6 +147,25 @@ func CanUploadDocument(ctx domain.RequestContext, s store.Store, spaceID string)
|
|||
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.
|
||||
func CanViewSpace(ctx domain.RequestContext, s store.Store, spaceID string) bool {
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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)
|
||||
|
||||
return s.DeleteWhere(ctx.Transaction, sql)
|
||||
return
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
|
||||
return s.DeleteWhere(ctx.Transaction, sql)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteCategoryPermissions removes records from dmz_permissions table for given category ID.
|
||||
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.
|
||||
func (s Store) DeleteSpaceCategoryPermissions(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
|
||||
sql := fmt.Sprintf(`
|
||||
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')`,
|
||||
_, 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=?)"),
|
||||
ctx.OrgID, ctx.OrgID, spaceID)
|
||||
|
||||
return s.DeleteWhere(ctx.Transaction, sql)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteGroupPermissions removes all roles for the specified group
|
||||
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)
|
||||
|
||||
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.
|
||||
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.
|
||||
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"
|
||||
|
||||
// EnterpriseEdition is proprietary closed-source product.
|
||||
EnterpriseEdition Edition = "Enterprise"
|
||||
|
||||
// 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"
|
||||
EnterpriseEdition Edition = "Community+"
|
||||
|
||||
// PlanCloud represents *.documize.com hosting.
|
||||
PlanCloud Plan = "Cloud"
|
||||
|
@ -64,6 +49,9 @@ const (
|
|||
// PlanSelfHost represents privately hosted Documize instance.
|
||||
PlanSelfHost Plan = "Self-host"
|
||||
|
||||
// SeatsFree is Five free users.
|
||||
SeatsFree Seats = 5
|
||||
|
||||
// Seats0 is 0 users.
|
||||
Seats0 Seats = 0
|
||||
|
||||
|
@ -120,8 +108,8 @@ func (p *Product) IsValid(ctx RequestContext) bool {
|
|||
return true
|
||||
}
|
||||
} else {
|
||||
// First 10 is free for Enterprise edition.
|
||||
if Seats1 == ctx.Subscription.Seats && time.Now().UTC().Before(ctx.Subscription.End) {
|
||||
// First 5 is free for Enterprise edition.
|
||||
if SeatsFree == ctx.Subscription.Seats && time.Now().UTC().Before(ctx.Subscription.End) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -174,8 +162,10 @@ type SubscriptionUserAccount struct {
|
|||
// SubscriptionAsXML returns subscription data as XML document:
|
||||
//
|
||||
// <DocumizeLicense>
|
||||
// <Key>some key</Key>
|
||||
// <Signature>some signature</Signature>
|
||||
//
|
||||
// <Key>some key</Key>
|
||||
// <Signature>some signature</Signature>
|
||||
//
|
||||
// </DocumizeLicense>
|
||||
//
|
||||
// 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.UserID,
|
||||
ctx.OrgID,
|
||||
ctx.UserID)
|
||||
ctx.UserID,
|
||||
keywords)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
|
|
|
@ -13,6 +13,7 @@ package search
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/documize/community/domain"
|
||||
|
@ -84,6 +85,17 @@ func (s StoreSQLServer) Documents(ctx domain.RequestContext, q search.QueryOptio
|
|||
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 {
|
||||
results = []search.QueryResult{}
|
||||
}
|
||||
|
@ -187,6 +199,55 @@ func (s StoreSQLServer) matchSection(ctx domain.RequestContext, keywords string)
|
|||
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
|
||||
|
||||
|
|
|
@ -30,8 +30,8 @@ func (*Provider) Meta() provider.TypeMeta {
|
|||
section := provider.TypeMeta{}
|
||||
|
||||
section.ID = "d46a18f6-49fb-11e8-842f-0ed5f89f718b"
|
||||
section.Title = "Draw.io Diagram"
|
||||
section.Description = "Draw.io powered flowcharts and diagrams"
|
||||
section.Title = "Diagrams.net"
|
||||
section.Description = "Flowcharts and diagrams"
|
||||
section.ContentType = "flowchart"
|
||||
section.PageType = "tab"
|
||||
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