mirror of
https://github.com/documize/community.git
synced 2025-08-07 06:25:23 +02:00
Compare commits
350 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 | ||
|
6629d76453 | ||
|
74300b009b | ||
|
5004e5a85e | ||
|
0524a0c74c | ||
|
b826852137 | ||
|
2c164a135a | ||
|
44febcc25c | ||
|
66e11cefbc | ||
|
5e9eeb5bf9 | ||
|
5b7610d726 | ||
|
0419f3b7b3 | ||
|
5b72da037c | ||
|
d14e8a3ff6 | ||
|
9a3d2c3c28 | ||
|
3b76e10ee0 | ||
|
29d7307537 | ||
|
96e5812fc0 | ||
|
c35eb16fc5 | ||
|
9dd78ca9be | ||
|
891ba07db8 | ||
|
2ee9a9ff46 | ||
|
399c36611f | ||
|
fbb73560c0 | ||
|
15e687841f | ||
|
0a10087160 | ||
|
9d0d4a7861 | ||
|
fc60a5917e | ||
|
285a01508b | ||
|
4f248bf018 | ||
|
32dbab826d | ||
|
b6e1543b7f | ||
|
f8bb879a70 | ||
|
20366e6776 | ||
|
ffacf17c5f | ||
|
bfe4c5d768 | ||
|
0f3a618140 | ||
|
826f6d96a6 | ||
|
6a9fa0140a | ||
|
24619c6a58 | ||
|
ebc8214049 | ||
|
71c1def5c7 | ||
|
fded0014a3 | ||
|
041091504f | ||
|
8c2df6178d | ||
|
02d478c6dd | ||
|
8c99977fc9 | ||
|
9d6b6fec23 | ||
|
4e0e3b5101 | ||
|
e219c97a6b | ||
|
7485f2cef7 | ||
|
627195aae7 | ||
|
f39be2a594 | ||
|
444b4fd1f7 | ||
|
b31f330c41 | ||
|
69077ce419 | ||
|
201d2a339c | ||
|
326019d655 | ||
|
264c25cfe0 | ||
|
595301db64 | ||
|
d6432afdad | ||
|
9c36241b58 | ||
|
c538fc9eb1 | ||
|
f3df43efe0 | ||
|
d04becc1a3 | ||
|
3621e2fb79 | ||
|
411f64c359 | ||
|
ae923e7df1 | ||
|
bfe5262cb5 | ||
|
80f0876b51 | ||
|
b2cd375936 | ||
|
243a170071 | ||
|
fb3f2cc24b | ||
|
946c433018 | ||
|
4d2f30711c |
1471 changed files with 224649 additions and 159999 deletions
|
@ -1,3 +1,6 @@
|
|||
.DS_Store
|
||||
.git
|
||||
bin
|
||||
.idea
|
||||
selfcert
|
||||
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/*
|
||||
|
||||
|
|
32
Dockerfile
Normal file
32
Dockerfile
Normal file
|
@ -0,0 +1,32 @@
|
|||
FROM node:16-alpine as frontbuilder
|
||||
WORKDIR /go/src/github.com/documize/community/gui
|
||||
COPY ./gui /go/src/github.com/documize/community/gui
|
||||
RUN npm --network-timeout=100000 install
|
||||
RUN npm run build -- --environment=production --output-path dist-prod --suppress-sizes true
|
||||
|
||||
FROM golang:1.21-alpine as builder
|
||||
WORKDIR /go/src/github.com/documize/community
|
||||
COPY . /go/src/github.com/documize/community
|
||||
COPY --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.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" ]
|
340
Gopkg.lock
generated
340
Gopkg.lock
generated
|
@ -1,340 +0,0 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b62a3c5b37db602bf1158e921da1a762315a4c37855fd418a14498aa87a342d5"
|
||||
name = "cloud.google.com/go"
|
||||
packages = ["civil"]
|
||||
pruneopts = "UT"
|
||||
revision = "2fa99f4c25c422525316dcb1fd3d5b94e1944cfd"
|
||||
version = "v0.37.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9f3b30d9f8e0d7040f729b82dcbc8f0dead820a133b3147ce355fc451f32d761"
|
||||
name = "github.com/BurntSushi/toml"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005"
|
||||
version = "v0.3.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:606d068450c82b9ddaa21de992f73563754077f0f411235cdfe71d0903a268c3"
|
||||
name = "github.com/codegangsta/negroni"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "5dbbc83f748fc3ad38585842b0aedab546d0ea1e"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:cf0505eb7d4704d5ab445353dc64122acf56d13937e20dac5570b995cb21232c"
|
||||
name = "github.com/denisenkom/go-mssqldb"
|
||||
packages = [
|
||||
".",
|
||||
"internal/cp",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "a8ed825ac8537383ef814eaf5a16174751796a2b"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:217f778e19b8d206112c21d21a7cc72ca3cb493b67631680a2324bc50335d432"
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "dbeaa9332f19a944acb5736b4456cfcc02140e29"
|
||||
version = "v3.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:39c2113f3a89585666e6f973650cff186b2d06deb4aa202c88addb87b0a201db"
|
||||
name = "github.com/documize/blackfriday"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "cadec560ec52d93835bf2f15bd794700d3a2473b"
|
||||
version = "v2.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:04bfeb11ea882e0a0867828e54374c066a1368f8da53bb1bbc16a9886967303a"
|
||||
name = "github.com/documize/glick"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a8ccbef88237fcafe9cef3c9aee7ad83d0e132f9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:2405d7a1e936e015b07c1c88acccc30d7f2e917b1b5acea08d06d116b8657a5c"
|
||||
name = "github.com/documize/html-diff"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "f61c192c7796644259832ef705c49259797e7fff"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0ae2e1b2d4cdff4834aa28ce2e33a7b6de91e10150e3647fe1b9fd63a51b39ce"
|
||||
name = "github.com/documize/slug"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "e9f42fa127660e552d0ad2b589868d403a9be7c6"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f4f6279cb37479954644babd8f8ef00584ff9fa63555d2c6718c1c3517170202"
|
||||
name = "github.com/elazarl/go-bindata-assetfs"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "30f82fa23fd844bd5bb1e5f216db87fd77b5eb43"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ca82a3b99694824c627573c2a76d0e49719b4a9c02d1d85a2ac91f1c1f52ab9b"
|
||||
name = "github.com/fatih/structs"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a720dfa8df582c51dee1b36feabb906bde1588bd"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:adea5a94903eb4384abef30f3d878dc9ff6b6b5b0722da25b82e5169216dfb61"
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d523deb1b23d913de5bdada721a6071e71283618"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ffc060c551980d37ee9e428ef528ee2813137249ccebb0bfc412ef83071cac91"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
pruneopts = "UT"
|
||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:51bee9f1987dcdb9f9a1b4c20745d78f6bf6f5f14ad4e64ca883eb64df4c0045"
|
||||
name = "github.com/google/go-github"
|
||||
packages = ["github"]
|
||||
pruneopts = "UT"
|
||||
revision = "e48060a28fac52d0f1cb758bc8b87c07bac4a87d"
|
||||
version = "v15.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a63cff6b5d8b95638bfe300385d93b2a6d9d687734b863da8e09dc834510a690"
|
||||
name = "github.com/google/go-querystring"
|
||||
packages = ["query"]
|
||||
pruneopts = "UT"
|
||||
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:160eabf7a69910fd74f29c692718bc2437c1c1c7d4c9dea9712357752a70e5df"
|
||||
name = "github.com/gorilla/context"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:664d37ea261f0fc73dd17f4a1f5f46d01fbb0b0d75f6375af064824424109b7d"
|
||||
name = "github.com/gorilla/handlers"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7e0847f9db758cdebd26c149d0ae9d5d0b9c98ce"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:88aa9e326e2bd6045a46e00a922954b3e1a9ac5787109f49ac85366df370e1e5"
|
||||
name = "github.com/gorilla/mux"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "53c1911da2b537f792e7cafcb446b05ffe33b996"
|
||||
version = "v1.6.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6c41d4f998a03b6604227ccad36edaed6126c397e5d78709ef4814a1145a6757"
|
||||
name = "github.com/jmoiron/sqlx"
|
||||
packages = [
|
||||
".",
|
||||
"reflectx",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d161d7a76b5661016ad0b085869f77fd410f3e6a"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8ef506fc2bb9ced9b151dafa592d4046063d744c646c1bbe801982ce87e4bc24"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [
|
||||
".",
|
||||
"oid",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4ded0e9383f75c197b3a2aaa6d590ac52df6fd79"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8213aea9ec57afac7c765f9127bb3a5677866e03c0d3815f236045f16d5bc468"
|
||||
name = "github.com/mb0/diff"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d8d9a906c24d7b0ee77287e0463e5ca7f026032e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:0e1e5f960c58fdc677212fcc70e55042a0084d367623e51afbdb568963832f5d"
|
||||
name = "github.com/nu7hatch/gouuid"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "179d4d0c4d8d407a32af483c2354df1d2c91e6c3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e6a29574542c00bb18adb1bfbe629ff88c468c2af2e2e953d3e58eda07165086"
|
||||
name = "github.com/rainycape/unidecode"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "cb7f23ec59bec0d61b19c56cd88cee3d0cc1870c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:def689e73e9252f6f7fe66834a76751a41b767e03daab299e607e7226c58a855"
|
||||
name = "github.com/shurcooL/sanitized_anchor_name"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:821c90494c34add2aa5f7c3b894f55dd08741acbb390901663050449b777c39a"
|
||||
name = "github.com/trivago/tgo"
|
||||
packages = [
|
||||
"tcontainer",
|
||||
"treflect",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "e4d1ddd28c17dd89ed26327cf69fded22060671b"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:68344dbfaa4179bb50a583eb8172ace3f1edaf3aebc24e68c03f549f6e6b60dc"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"bcrypt",
|
||||
"blowfish",
|
||||
"md4",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "650f4a345ab4e5b245a3034b110ebc7299e68186"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ac7eaa5f1179480f517d32831225215cc20940152d66be29f3d5204ea15d425f"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp",
|
||||
"html",
|
||||
"html/atom",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f5dfe339be1d06f81b22525fe34671ee7d2c8904"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:fe84cb4abe7f53047ac44cf10d917d707a718711e146c9239700e4c8cc94a891"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "543e37812f10c46c622c9575afd7ad22f22a12ba"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f40806967647e80fc51b941a586afefea6058592692c0bbfb3be7ea6b2b2a82d"
|
||||
name = "google.golang.org/appengine"
|
||||
packages = [
|
||||
"cloudsql",
|
||||
"internal",
|
||||
"internal/base",
|
||||
"internal/datastore",
|
||||
"internal/log",
|
||||
"internal/remote_api",
|
||||
"internal/urlfetch",
|
||||
"urlfetch",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "v3"
|
||||
digest = "1:7388652e2215a3f45d341d58766ed58317971030eb1cbd75f005f96ace8e9196"
|
||||
name = "gopkg.in/alexcesaro/quotedprintable.v3"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2caba252f4dc53eaf6b553000885530023f54623"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ec2b97c119fc66f96b421f8798deb2f87cb4a5ee81cafeaf9b55420d035f8fea"
|
||||
name = "gopkg.in/andygrunwald/go-jira.v1"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0298784c4606cdf01e99644da115863c052a737c"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:81e1c5cee195fca5de06e2540cb63eea727a850b7e5c213548e7f81521c97a57"
|
||||
name = "gopkg.in/asn1-ber.v1"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "379148ca0225df7a432012b8df0355c2a2063ac0"
|
||||
version = "v1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e9a0fa7c2dfc90e0fae16be5825ad98074d8704f5fcebfdc289a8e8fb0f8e4b5"
|
||||
name = "gopkg.in/ldap.v3"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "9f0d712775a0973b7824a1585a86a4ea1d5263d9"
|
||||
version = "v3.0.3"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/BurntSushi/toml",
|
||||
"github.com/codegangsta/negroni",
|
||||
"github.com/denisenkom/go-mssqldb",
|
||||
"github.com/dgrijalva/jwt-go",
|
||||
"github.com/documize/blackfriday",
|
||||
"github.com/documize/glick",
|
||||
"github.com/documize/html-diff",
|
||||
"github.com/documize/slug",
|
||||
"github.com/elazarl/go-bindata-assetfs",
|
||||
"github.com/go-sql-driver/mysql",
|
||||
"github.com/google/go-github/github",
|
||||
"github.com/gorilla/handlers",
|
||||
"github.com/gorilla/mux",
|
||||
"github.com/jmoiron/sqlx",
|
||||
"github.com/lib/pq",
|
||||
"github.com/nu7hatch/gouuid",
|
||||
"github.com/pkg/errors",
|
||||
"golang.org/x/crypto/bcrypt",
|
||||
"golang.org/x/net/context",
|
||||
"golang.org/x/net/html",
|
||||
"golang.org/x/net/html/atom",
|
||||
"golang.org/x/oauth2",
|
||||
"gopkg.in/alexcesaro/quotedprintable.v3",
|
||||
"gopkg.in/andygrunwald/go-jira.v1",
|
||||
"gopkg.in/ldap.v3",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
102
Gopkg.toml
102
Gopkg.toml
|
@ -1,102 +0,0 @@
|
|||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/codegangsta/negroni"
|
||||
version = "0.3.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
version = "3.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/documize/blackfriday"
|
||||
version = "2.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/documize/glick"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/documize/html-diff"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/elazarl/go-bindata-assetfs"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
version = "1.3.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/google/go-github"
|
||||
version = "15.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/mux"
|
||||
version = "1.6.1"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/jmoiron/sqlx"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/nu7hatch/gouuid"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/documize/slug"
|
||||
version = "1.1.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/andygrunwald/go-jira.v1"
|
||||
version = "1.5.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/denisenkom/go-mssqldb"
|
90
README.md
90
README.md
|
@ -1,66 +1,110 @@
|
|||
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.0.0](https://github.com/documize/community/releases)
|
||||
[Community edition: v5.13.0](https://github.com/documize/community/releases)
|
||||
|
||||
[Enterprise Edition: v3.0.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 (FTS) support is mandatory.
|
||||
|
||||
- PostgreSQL (v9.6+)
|
||||
- Microsoft SQL Server (2016+)
|
||||
- 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.12.6)
|
||||
- Ember JS (v3.10.0)
|
||||
- Go (v1.23.4)
|
||||
- Ember JS (v3.12.0)
|
||||
|
||||
## Authentication Options
|
||||
|
||||
Besides email/password login, you can also connect to LDAP/Active Directory or Red Hat Keycloak server.
|
||||
Besides email/password login, you can also authenticate via:
|
||||
|
||||
Dual authentication of LDAP and email/password is also supported.
|
||||
* LDAP
|
||||
* Active Directory
|
||||
* Red Hat Keycloak
|
||||
* Central Authentication Service (CAS)
|
||||
|
||||
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)
|
||||
|
|
81
build.bat
81
build.bat
|
@ -7,60 +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
|
||||
|
||||
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
|
||||
|
|
87
build.sh
87
build.sh
|
@ -8,53 +8,62 @@ 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
|
||||
|
||||
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/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
|
||||
|
||||
cd ..
|
||||
echo "Compiling for Linux..."
|
||||
env GOOS=linux GOARCH=amd64 GODEBUG=tls13=1 go build -gcflags="all=-trimpath=$GOPATH" -o bin/documize-community-linux-amd64 ./edition/community.go
|
||||
echo "Compiling for macOS..."
|
||||
env GOOS=darwin GOARCH=amd64 GODEBUG=tls13=1 go build -gcflags="all=-trimpath=$GOPATH" -o bin/documize-community-darwin-amd64 ./edition/community.go
|
||||
echo "Compiling for Windows..."
|
||||
env GOOS=windows GOARCH=amd64 GODEBUG=tls13=1 go build -gcflags="all=-trimpath=$GOPATH" -o bin/documize-community-windows-amd64.exe ./edition/community.go
|
||||
echo "Compiling for ARM..."
|
||||
env GOOS=linux GOARCH=arm GODEBUG=tls13=1 go build -gcflags="all=-trimpath=$GOPATH" -o bin/documize-community-linux-arm ./edition/community.go
|
||||
echo "Compiling for ARM64..."
|
||||
env GOOS=linux GOARCH=arm64 GODEBUG=tls13=1 go build -gcflags="all=-trimpath=$GOPATH" -o bin/documize-community-linux-arm64 ./edition/community.go
|
||||
rm -rf edition/static/onboard
|
||||
mkdir -p edition/static/onboard
|
||||
cp -r domain/onboard/*.json edition/static/onboard
|
||||
|
||||
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."
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,155 +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 convert_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/documize/community/core/api/convert"
|
||||
"github.com/documize/community/core/api/plugins"
|
||||
api "github.com/documize/community/core/convapi"
|
||||
"github.com/documize/community/core/log"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
|
||||
plugins.PluginFile = "" // no file as html is built-in
|
||||
if lerr := plugins.LibSetup(); lerr == nil {
|
||||
//t.Error("did not error on plugin.Libsetup() with no plugin.json file")
|
||||
//return
|
||||
}
|
||||
defer log.IfErr(plugins.Lib.KillSubProcs())
|
||||
|
||||
ctx := context.Background()
|
||||
xtn := "html"
|
||||
fileRequest := new(api.DocumentConversionRequest)
|
||||
fileRequest.Filedata = []byte(yorkweb)
|
||||
resp, err := convert.Convert(ctx, xtn, fileRequest)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if len(resp.Pages) != 3 ||
|
||||
!strings.HasPrefix(resp.Pages[1].Title, "STARTING") ||
|
||||
!strings.HasPrefix(resp.Pages[2].Title, "EXERCISE") {
|
||||
for p, pg := range resp.Pages {
|
||||
t.Error(p, pg.Level, len(pg.Body), pg.Title)
|
||||
}
|
||||
}
|
||||
exp := "There are lots of ways to create web pages using already coded programmes. … HTML isn' t computer code, but is a language that uses US English to enable texts( words, images, sounds) to be inserted and formatting such as colo( u) r and centre/ erin…"
|
||||
if resp.Excerpt != exp {
|
||||
t.Errorf("unexpected excerpt wanted: `%s` got: `%s`", exp, resp.Excerpt)
|
||||
}
|
||||
|
||||
// check errors are caught
|
||||
resp, err = convert.Convert(ctx, "unknown", fileRequest)
|
||||
if err == nil {
|
||||
t.Error("does not error on unknown extension")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// www.york.ac.uk/teaching/cws/wws/webpage1.html
|
||||
const yorkweb = `
|
||||
|
||||
<HMTL>
|
||||
<HEAD>
|
||||
<TITLE>webpage1</TITLE>
|
||||
</HEAD>
|
||||
<BODY BGCOLOR="FFFFFf" LINK="006666" ALINK="8B4513" VLINK="006666">
|
||||
<TABLE WIDTH="75%" ALIGN="center">
|
||||
<TR>
|
||||
<TD>
|
||||
<DIV ALIGN="center"><H1>STARTING . . . </H1></DIV>
|
||||
|
||||
|
||||
<DIV ALIGN="justify"><P>There are lots of ways to create web pages using already coded programmes. These lessons will teach you how to use the underlying HyperText Markup Language - HTML.
|
||||
<BR>
|
||||
<P>HTML isn't computer code, but is a language that uses US English to enable texts (words, images, sounds) to be inserted and formatting such as colo(u)r and centre/ering to be written in. The process is fairly simple; the main difficulties often lie in small mistakes - if you slip up while word processing your reader may pick up your typos, but the page will still be legible. However, if your HTML is inaccurate the page may not appear - writing web pages is, at the least, very good practice for proof reading!</P>
|
||||
|
||||
<P>Learning HTML will enable you to:
|
||||
<UL>
|
||||
<LI>create your own simple pages
|
||||
<LI>read and appreciate pages created by others
|
||||
<LI>develop an understanding of the creative and literary implications of web-texts
|
||||
<LI>have the confidence to branch out into more complex web design
|
||||
</UL></P>
|
||||
|
||||
<P>A HTML web page is made up of tags. Tags are placed in brackets like this <B>< tag > </B>. A tag tells the browser how to display information. Most tags need to be opened < tag > and closed < /tag >.
|
||||
|
||||
<P> To make a simple web page you need to know only four tags:
|
||||
<UL>
|
||||
<LI>< HTML > tells the browser your page is written in HTML format
|
||||
<LI>< HEAD > this is a kind of preface of vital information that doesn't appear on the screen.
|
||||
<LI>< TITLE >Write the title of the web page here - this is the information that viewers see on the upper bar of their screen. (I've given this page the title 'webpage1').
|
||||
<LI>< BODY >This is where you put the content of your page, the words and pictures that people read on the screen.
|
||||
</UL>
|
||||
<P>All these tags need to be closed.
|
||||
|
||||
<H4>EXERCISE</H4>
|
||||
|
||||
<P>Write a simple web page.</P>
|
||||
<P> Copy out exactly the HTML below, using a WP program such as Notepad.<BR>
|
||||
Information in <I>italics</I> indicates where you can insert your own text, other information is HTML and needs to be exact. However, make sure there are no spaces between the tag brackets and the text inside.<BR>
|
||||
(Find Notepad by going to the START menu\ PROGRAMS\ ACCESSORIES\ NOTEPAD).
|
||||
<P>
|
||||
< HTML ><BR>
|
||||
< HEAD ><BR>
|
||||
< TITLE ><I> title of page</I>< /TITLE ><BR>
|
||||
< /HEAD ><BR>
|
||||
< BODY><BR>
|
||||
<I> write what you like here: 'my first web page', or a piece about what you are reading, or a few thoughts on the course, or copy out a few words from a book or cornflake packet. Just type in your words using no extras such as bold, or italics, as these have special HTML tags, although you may use upper and lower case letters and single spaces. </I><BR>
|
||||
|
||||
< /BODY ><BR>
|
||||
< /HTML ><BR>
|
||||
|
||||
<P>Save the file as 'first.html' (ie. call the file anything at all) It's useful if you start a folder - just as you would for word-processing - and call it something like WEBPAGES, and put your first.html file in the folder.
|
||||
|
||||
<P>NOW - open your browser.<BR>
|
||||
On Netscape the process is: <BR>
|
||||
Top menu; FILE\ OPEN PAGE\ CHOOSE FILE<BR>
|
||||
Click on your WEBPAGES folder\ FIRST file<BR>
|
||||
Click 'open' and your page should appear.
|
||||
<P>On Internet Explorer: <BR>
|
||||
Top menu; FILE\ OPEN\ BROWSE <BR>
|
||||
Click on your WEBPAGES folder\ FIRST file<BR>
|
||||
Click 'open' and your page should appear.<BR>
|
||||
|
||||
|
||||
<P>If the page doesn't open, go back over your notepad typing and make sure that all the HTML tags are correct. Check there are no spaces between tags and internal text; check that all tags are closed; check that you haven't written < HTLM > or < BDDY >. Your page will work eventually.
|
||||
<P>
|
||||
Make another page. Call it somethingdifferent.html and place it in the same WEBPAGES folder as detailed above.
|
||||
<P>start formatting in <A HREF="webpage2.html">lesson two</A>
|
||||
<BR><A HREF="col3.html">back to wws index</A> </P>
|
||||
</P>
|
||||
|
||||
|
||||
</DIV>
|
||||
|
||||
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
</BODY>
|
||||
</HTML>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
|
@ -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
|
||||
|
@ -77,35 +77,42 @@ func InstallUpgrade(runtime *env.Runtime, existingDB bool) (err error) {
|
|||
runtime.Log.Info(fmt.Sprintf("Database: legacy schema has %d scripts to process", len(toProcess)))
|
||||
}
|
||||
|
||||
tx, err := runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = runScripts(runtime, tx, toProcess)
|
||||
err = runScripts(runtime, toProcess)
|
||||
if err != nil {
|
||||
runtime.Log.Error("Database: error processing SQL scripts", err)
|
||||
tx.Rollback()
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run SQL scripts to instal or upgrade this database.
|
||||
func runScripts(runtime *env.Runtime, tx *sqlx.Tx, scripts []Script) (err error) {
|
||||
// We do not use transactions for Microsoft SQL Server because
|
||||
// CREATE FULLTEXT CATALOG statement cannot be used inside a user transaction.
|
||||
func runScripts(runtime *env.Runtime, scripts []Script) (err error) {
|
||||
tx, err := runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We can have multiple scripts as each Documize database change has it's own SQL script.
|
||||
for _, script := range scripts {
|
||||
runtime.Log.Info(fmt.Sprintf("Database: processing SQL script %d", script.Version))
|
||||
|
||||
err = executeSQL(tx, runtime.StoreProvider.Type(), runtime.StoreProvider.TypeVariant(), script.Script)
|
||||
err = executeSQL(tx, runtime, script.Script)
|
||||
if err != nil {
|
||||
runtime.Log.Error(fmt.Sprintf("error executing SQL script %d", script.Version), err)
|
||||
if runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||
tx.Rollback()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Record the fact we have processed this database script version.
|
||||
_, err = tx.Exec(runtime.StoreProvider.QueryRecordVersionUpgrade(script.Version))
|
||||
if runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||
_, err = tx.Exec(runtime.StoreProvider.QueryRecordVersionUpgrade(script.Version))
|
||||
} else {
|
||||
_, err = runtime.Db.Exec(runtime.StoreProvider.QueryRecordVersionUpgrade(script.Version))
|
||||
}
|
||||
if err != nil {
|
||||
// For MySQL we try the legacy DB schema.
|
||||
if runtime.StoreProvider.Type() == env.StoreTypeMySQL {
|
||||
|
@ -114,31 +121,45 @@ func runScripts(runtime *env.Runtime, tx *sqlx.Tx, scripts []Script) (err error)
|
|||
_, err = tx.Exec(runtime.StoreProvider.QueryRecordVersionUpgradeLegacy(script.Version))
|
||||
if err != nil {
|
||||
runtime.Log.Error(fmt.Sprintf("error recording execution of SQL script %d", script.Version), err)
|
||||
if runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||
tx.Rollback()
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Unknown issue running script on non-MySQL database.
|
||||
runtime.Log.Error(fmt.Sprintf("error executing SQL script %d", script.Version), err)
|
||||
if runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||
tx.Rollback()
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeSQL runs specified SQL commands.
|
||||
func executeSQL(tx *sqlx.Tx, st env.StoreType, variant env.StoreType, SQLfile []byte) error {
|
||||
func executeSQL(tx *sqlx.Tx, runtime *env.Runtime, SQLfile []byte) error {
|
||||
// Turn SQL file contents into runnable SQL statements.
|
||||
stmts := getStatements(SQLfile)
|
||||
|
||||
for _, stmt := range stmts {
|
||||
// MariaDB has no specific JSON column type (but has JSON queries)
|
||||
if st == env.StoreTypeMySQL && variant == env.StoreTypeMariaDB {
|
||||
if runtime.StoreProvider.Type() == env.StoreTypeMySQL &&
|
||||
runtime.StoreProvider.TypeVariant() == env.StoreTypeMariaDB {
|
||||
stmt = strings.Replace(stmt, "` JSON", "` TEXT", -1)
|
||||
}
|
||||
|
||||
_, err := tx.Exec(stmt)
|
||||
var err error
|
||||
if runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||
_, err = tx.Exec(stmt)
|
||||
} else {
|
||||
_, err = runtime.Db.Exec(stmt)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("sql statement error:", stmt)
|
||||
return err
|
||||
|
|
|
@ -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',
|
||||
|
|
5
core/database/scripts/mysql/db_00031.sql
Normal file
5
core/database/scripts/mysql/db_00031.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
/* Community edition */
|
||||
|
||||
-- Indexes to improve performance
|
||||
CREATE UNIQUE INDEX idx_doc_4 ON dmz_doc(c_orgid,c_refid);
|
||||
CREATE UNIQUE INDEX idx_section_4 ON dmz_section(c_orgid,c_refid);
|
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',
|
||||
|
|
5
core/database/scripts/postgresql/db_00007.sql
Normal file
5
core/database/scripts/postgresql/db_00007.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
/* Community edition */
|
||||
|
||||
-- Indexes to improve performance
|
||||
CREATE UNIQUE INDEX idx_doc_4 ON dmz_doc (c_orgid,c_refid);
|
||||
CREATE UNIQUE INDEX idx_section_4 ON dmz_section (c_orgid,c_refid);
|
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',
|
||||
|
|
16
core/database/scripts/sqlserver/db_00004.sql
Normal file
16
core/database/scripts/sqlserver/db_00004.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* Community edition */
|
||||
|
||||
-- Fulltext search support
|
||||
IF EXISTS (SELECT * FROM sysfulltextcatalogs ftc WHERE ftc.name = N'dmz_search_catalog')
|
||||
DROP FULLTEXT CATALOG dmz_search_catalog;
|
||||
|
||||
CREATE FULLTEXT CATALOG dmz_search_catalog;
|
||||
|
||||
CREATE UNIQUE INDEX idx_doc_4 ON dmz_doc(c_refid);
|
||||
CREATE UNIQUE INDEX idx_section_4 ON dmz_section(c_refid);
|
||||
|
||||
CREATE FULLTEXT INDEX ON dmz_doc (c_name, c_desc) KEY INDEX idx_doc_4 ON dmz_search_catalog
|
||||
WITH CHANGE_TRACKING AUTO;
|
||||
|
||||
CREATE FULLTEXT INDEX ON dmz_section (c_name, c_body) KEY INDEX idx_section_4 ON dmz_search_catalog
|
||||
WITH CHANGE_TRACKING AUTO;
|
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);
|
|
@ -12,6 +12,8 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
@ -21,6 +23,7 @@ import (
|
|||
"github.com/documize/community/core/secrets"
|
||||
"github.com/documize/community/core/stringutil"
|
||||
"github.com/documize/community/core/uniqueid"
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/domain/store"
|
||||
"github.com/documize/community/server/web"
|
||||
)
|
||||
|
@ -65,15 +68,16 @@ func (h *Handler) Setup(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
details := onboardRequest{
|
||||
URL: "",
|
||||
Company: r.Form.Get("title"),
|
||||
CompanyLong: r.Form.Get("title"),
|
||||
Message: r.Form.Get("message"),
|
||||
Email: r.Form.Get("email"),
|
||||
Password: r.Form.Get("password"),
|
||||
Firstname: r.Form.Get("firstname"),
|
||||
Lastname: r.Form.Get("lastname"),
|
||||
Revised: time.Now().UTC(),
|
||||
URL: "",
|
||||
Company: r.Form.Get("title"),
|
||||
CompanyLong: r.Form.Get("title"),
|
||||
Message: r.Form.Get("message"),
|
||||
Email: r.Form.Get("email"),
|
||||
Password: r.Form.Get("password"),
|
||||
Firstname: r.Form.Get("firstname"),
|
||||
Lastname: r.Form.Get("lastname"),
|
||||
ActivationKey: r.Form.Get("activationKey"),
|
||||
Revised: time.Now().UTC(),
|
||||
}
|
||||
|
||||
if details.Company == "" ||
|
||||
|
@ -108,15 +112,16 @@ func (h *Handler) Setup(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// The result of completing the onboarding process.
|
||||
type onboardRequest struct {
|
||||
URL string
|
||||
Company string
|
||||
CompanyLong string
|
||||
Message string
|
||||
Email string
|
||||
Password string
|
||||
Firstname string
|
||||
Lastname string
|
||||
Revised time.Time
|
||||
URL string
|
||||
Company string
|
||||
CompanyLong string
|
||||
Message string
|
||||
Email string
|
||||
Password string
|
||||
Firstname string
|
||||
Lastname string
|
||||
ActivationKey string
|
||||
Revised time.Time
|
||||
}
|
||||
|
||||
// setupAccount prepares the database for a newly onboard customer.
|
||||
|
@ -128,17 +133,20 @@ func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (er
|
|||
return
|
||||
}
|
||||
|
||||
//accountTitle := "This is where you will find documentation for your all projects. You can customize this message from the settings screen."
|
||||
salt := secrets.GenerateSalt()
|
||||
password := secrets.GeneratePassword(completion.Password, salt)
|
||||
|
||||
// Process activation key if we have one.
|
||||
activationKey := processActivationKey(rt, completion)
|
||||
|
||||
// Allocate organization to the user.
|
||||
orgID := uniqueid.Generate()
|
||||
_, err = tx.Exec(RebindParams("INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, c_domain, c_email, c_serial) VALUES (?, ?, ?, ?, ?, ?, ?)", rt.StoreProvider.Type()),
|
||||
orgID, completion.Company, completion.CompanyLong, completion.Message, completion.URL, completion.Email, serial)
|
||||
_, err = tx.Exec(RebindParams("INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, c_domain, c_email, c_serial, c_sub) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
rt.StoreProvider.Type()),
|
||||
orgID, completion.Company, completion.CompanyLong, completion.Message, completion.URL, completion.Email, serial, activationKey)
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_org failed", err)
|
||||
tx.Rollback()
|
||||
rt.Rollback(tx)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -148,7 +156,7 @@ func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (er
|
|||
userID, completion.Firstname, completion.Lastname, completion.Email, stringutil.MakeInitials(completion.Firstname, completion.Lastname), salt, password, true)
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_user failed", err)
|
||||
tx.Rollback()
|
||||
rt.Rollback(tx)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -158,80 +166,7 @@ func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (er
|
|||
accountID, userID, orgID, true, true, true, true)
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_user_account failed", err)
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// Create space.
|
||||
spaceID := uniqueid.Generate()
|
||||
_, err = tx.Exec(RebindParams("INSERT INTO dmz_space (c_refid, c_orgid, c_userid, c_name, c_type) VALUES (?, ?, ?, ?, ?)", rt.StoreProvider.Type()),
|
||||
spaceID, orgID, userID, "Welcome", 2)
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_space failed", err)
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// Assign permissions to space.
|
||||
perms := []string{"view", "manage", "own", "doc-add", "doc-edit", "doc-delete", "doc-move", "doc-copy", "doc-template", "doc-approve", "doc-version", "doc-lifecycle"}
|
||||
for _, p := range perms {
|
||||
_, err = tx.Exec(RebindParams("INSERT INTO dmz_permission (c_orgid, c_who, c_whoid, c_action, c_scope, c_location, c_refid) VALUES (?, ?, ?, ?, ?, ?, ?)", rt.StoreProvider.Type()),
|
||||
orgID, "user", userID, p, "object", "space", spaceID)
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_permission failed", err)
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create some user groups.
|
||||
groupDevID := uniqueid.Generate()
|
||||
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group (c_refid, c_orgid, c_name, c_desc) VALUES (?, ?, ?, ?)", rt.StoreProvider.Type()),
|
||||
groupDevID, orgID, "Technology", "On-site and remote development teams")
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_group failed", err)
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
groupProjectID := uniqueid.Generate()
|
||||
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group (c_refid, c_orgid, c_name, c_desc) VALUES (?, ?, ?, ?)", rt.StoreProvider.Type()),
|
||||
groupProjectID, orgID, "Project Management", "HQ PMO and Account Management departments")
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_group failed", err)
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
groupBackofficeID := uniqueid.Generate()
|
||||
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group (c_refid, c_orgid, c_name, c_desc) VALUES (?, ?, ?, ?)", rt.StoreProvider.Type()),
|
||||
groupBackofficeID, orgID, "Back Office", "Finance and HR people")
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_group failed", err)
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// Join the user groups.
|
||||
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group_member (c_orgid, c_groupid, c_userid) VALUES (?, ?, ?)", rt.StoreProvider.Type()),
|
||||
orgID, groupDevID, userID)
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_group_member failed", err)
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group_member (c_orgid, c_groupid, c_userid) VALUES (?, ?, ?)", rt.StoreProvider.Type()),
|
||||
orgID, groupProjectID, userID)
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_group_member failed", err)
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group_member (c_orgid, c_groupid, c_userid) VALUES (?, ?, ?)", rt.StoreProvider.Type()),
|
||||
orgID, groupBackofficeID, userID)
|
||||
if err != nil {
|
||||
rt.Log.Error("INSERT INTO dmz_group_member failed", err)
|
||||
tx.Rollback()
|
||||
rt.Rollback(tx)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -243,3 +178,30 @@ func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (er
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func processActivationKey(rt *env.Runtime, or onboardRequest) (key string) {
|
||||
key = "{}"
|
||||
if len(or.ActivationKey) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
j := domain.SubscriptionData{}
|
||||
x := domain.SubscriptionXML{Key: "", Signature: ""}
|
||||
|
||||
err1 := xml.Unmarshal([]byte(or.ActivationKey), &x)
|
||||
if err1 == nil {
|
||||
j.Key = x.Key
|
||||
j.Signature = x.Signature
|
||||
} else {
|
||||
rt.Log.Error("failed to XML unmarshal subscription XML", err1)
|
||||
}
|
||||
|
||||
d, err2 := json.Marshal(j)
|
||||
if err2 == nil {
|
||||
key = string(d)
|
||||
} else {
|
||||
rt.Log.Error("failed to JSON marshal subscription XML", err2)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
html {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
background-color: #1b75bb;
|
||||
|
@ -22,54 +22,54 @@
|
|||
color: #ffffff;
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.logo {
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
|
||||
.content > div {
|
||||
margin: 50px 0;
|
||||
}
|
||||
|
||||
|
||||
.content h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
margin: 0 0 30px;
|
||||
}
|
||||
|
||||
|
||||
.content p {
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
margin: 30px 0 0 0;
|
||||
}
|
||||
|
||||
|
||||
.content .image {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.clearfix {
|
||||
overflow: auto;
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
|
||||
.btn-main {
|
||||
border: 1px solid #ffffff;
|
||||
padding: 12px 20px;
|
||||
border-radius: 5px;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 768px) {
|
||||
body {
|
||||
margin-top: 100px;
|
||||
|
@ -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,11 +110,11 @@
|
|||
</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>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
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
|
||||
}
|
||||
|
||||
|
|
5
core/env/runtime.go
vendored
5
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,10 +43,10 @@ type Runtime struct {
|
|||
StoreProvider StoreProvider
|
||||
Log Logger
|
||||
Product domain.Product
|
||||
Assets embed.FS
|
||||
}
|
||||
|
||||
// StartTx beings database transaction with application defined
|
||||
// database transaction isolation level.
|
||||
// StartTx begins database transaction with given transaction isolation level.
|
||||
// Any error encountered during this operation is logged to runtime logger.
|
||||
func (r *Runtime) StartTx(i sql.IsolationLevel) (tx *sqlx.Tx, ok bool) {
|
||||
tx, err := r.Db.BeginTxx(context.Background(), &sql.TxOptions{Isolation: i})
|
||||
|
|
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
|
||||
}
|
50
docker-compose.yaml
Normal file
50
docker-compose.yaml
Normal file
|
@ -0,0 +1,50 @@
|
|||
# This Docker Compose file will start up Documize with PostgreSQL.
|
||||
#
|
||||
# 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
|
||||
# because of the common database schema.
|
||||
#
|
||||
# The latest product release executable will be pulled down
|
||||
# from a public Amazon S3 bucket.
|
||||
#
|
||||
# Use 'docker-compose up|down' to start or stop containers.
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:12
|
||||
restart: always
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
POSTGRES_USER: documize
|
||||
POSTGRES_PASSWORD: Passw0rd
|
||||
POSTGRES_DB: documize
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- documizenet
|
||||
|
||||
app:
|
||||
image: debian:latest
|
||||
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:
|
||||
- 5001:5001
|
||||
environment:
|
||||
DOCUMIZEPORT: 5001
|
||||
DOCUMIZEDB: host=db port=5432 dbname=documize user=documize password=Passw0rd sslmode=disable
|
||||
DOCUMIZEDBTYPE: postgresql
|
||||
DOCUMIZESALT: hsk3Acndky8cdTNx3
|
||||
DOCUMIZELOCATION: selfhost
|
||||
networks:
|
||||
- documizenet
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
|
||||
networks:
|
||||
documizenet:
|
|
@ -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 {
|
||||
|
|
25
domain/auth/cas/README.md
Normal file
25
domain/auth/cas/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Authenticating with Aperao CAS
|
||||
|
||||
## Introduction
|
||||
|
||||
Documize can delegate user authentication to aperao CAS integration.
|
||||
|
||||
This document assumes that the Documize administrator has installed and is familiar with CAS server.
|
||||
|
||||
https://www.apereo.org/projects/cas
|
||||
|
||||
Documize is tested against the CAS version 5.3.x.
|
||||
|
||||
## Run a CAS server
|
||||
|
||||
Refer to the following like [https://apereo.github.io/cas/5.0.x/installation/Docker-Installation.html](https://apereo.github.io/cas/5.0.x/installation/Docker-Installation.html) to run CAS server. Usually the server address is `https://localhost:8443/cas".
|
||||
|
||||
## Configuring Documize
|
||||
|
||||
CAS authentication is configured and enabled from Settings.
|
||||
|
||||
Type in the CAS Server URL, Redirect URL.
|
||||
|
||||
* **CAS Server URL**: The CAS host address, eg: `https://localhost:8443/cas`
|
||||
* **Redirect URL**: The CAS authorize callback URL. If your documize URL is `https://example.documize.com,` then redirect URL is `https://example.documize.com/auth/cas`.
|
||||
|
177
domain/auth/cas/endpoint.go
Normal file
177
domain/auth/cas/endpoint.go
Normal file
|
@ -0,0 +1,177 @@
|
|||
package cas
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/response"
|
||||
"github.com/documize/community/core/secrets"
|
||||
"github.com/documize/community/core/streamutil"
|
||||
"github.com/documize/community/core/stringutil"
|
||||
"github.com/documize/community/domain"
|
||||
"github.com/documize/community/domain/auth"
|
||||
"github.com/documize/community/domain/store"
|
||||
usr "github.com/documize/community/domain/user"
|
||||
ath "github.com/documize/community/model/auth"
|
||||
"github.com/documize/community/model/user"
|
||||
casv2 "gopkg.in/cas.v2"
|
||||
)
|
||||
|
||||
// Handler contains the runtime information such as logging and database.
|
||||
type Handler struct {
|
||||
Runtime *env.Runtime
|
||||
Store *store.Store
|
||||
}
|
||||
|
||||
// Authenticate checks CAS authentication credentials.
|
||||
func (h *Handler) Authenticate(w http.ResponseWriter, r *http.Request) {
|
||||
method := "authenticate"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
defer streamutil.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, "Bad payload")
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
a := ath.CASAuthRequest{}
|
||||
err = json.Unmarshal(body, &a)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, err.Error())
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
a.Ticket = strings.TrimSpace(a.Ticket)
|
||||
|
||||
org, err := h.Store.Organization.GetOrganizationByDomain("")
|
||||
if err != nil {
|
||||
response.WriteUnauthorizedError(w)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.OrgID = org.RefID
|
||||
// Fetch CAS auth provider config
|
||||
ac := ath.CASConfig{}
|
||||
err = json.Unmarshal([]byte(org.AuthConfig), &ac)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, "Unable to unmarshal CAS configuration")
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
service := url.QueryEscape(ac.RedirectURL)
|
||||
|
||||
validateURL := ac.URL + "/serviceValidate?ticket=" + a.Ticket + "&service=" + service
|
||||
|
||||
resp, err := http.Get(validateURL)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, "Unable to get service validate url")
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
defer streamutil.Close(resp.Body)
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, "Unable to verify CAS ticket: "+a.Ticket)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
userInfo, err := casv2.ParseServiceResponse(data)
|
||||
if err != nil {
|
||||
response.WriteBadRequestError(w, method, "Unable to get user information")
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Runtime.Log.Info("cas logon attempt " + userInfo.User)
|
||||
|
||||
u, err := h.Store.User.GetByDomain(ctx, a.Domain, userInfo.User)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create user account if not found
|
||||
if err == sql.ErrNoRows {
|
||||
h.Runtime.Log.Info("cas add user " + userInfo.User + " @ " + a.Domain)
|
||||
|
||||
u = user.User{}
|
||||
|
||||
u.Active = true
|
||||
u.ViewUsers = false
|
||||
u.Analytics = false
|
||||
u.Admin = false
|
||||
u.GlobalAdmin = false
|
||||
u.Email = userInfo.User
|
||||
|
||||
fn := userInfo.Attributes.Get("first_name")
|
||||
ln := userInfo.Attributes.Get("last_name")
|
||||
if len(fn) > 0 || len(ln) > 0 {
|
||||
u.Initials = stringutil.MakeInitials(fn, ln)
|
||||
u.Firstname = fn
|
||||
u.Lastname = ln
|
||||
} else {
|
||||
u.Initials = stringutil.MakeInitials(userInfo.User, "")
|
||||
u.Firstname = userInfo.User
|
||||
u.Lastname = ""
|
||||
}
|
||||
|
||||
u.Salt = secrets.GenerateSalt()
|
||||
u.Password = secrets.GeneratePassword(secrets.GenerateRandomPassword(), u.Salt)
|
||||
|
||||
u, err = auth.AddExternalUser(ctx, h.Runtime, h.Store, u, true)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Password correct and active user
|
||||
if userInfo.User != strings.TrimSpace(strings.ToLower(u.Email)) {
|
||||
response.WriteUnauthorizedError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Attach user accounts and work out permissions.
|
||||
usr.AttachUserAccounts(ctx, *h.Store, org.RefID, &u)
|
||||
|
||||
// No accounts signals data integrity problem
|
||||
// so we reject login request.
|
||||
if len(u.Accounts) == 0 {
|
||||
response.WriteUnauthorizedError(w)
|
||||
err = fmt.Errorf("no user accounts found for %s", u.Email)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Abort login request if account is disabled.
|
||||
for _, ac := range u.Accounts {
|
||||
if ac.OrgID == org.RefID {
|
||||
if ac.Active == false {
|
||||
response.WriteUnauthorizedError(w)
|
||||
err = fmt.Errorf("no ACTIVE user account found for %s", u.Email)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
authModel := ath.AuthenticationModel{}
|
||||
authModel.Token = auth.GenerateJWT(h.Runtime, u.RefID, org.RefID, a.Domain)
|
||||
authModel.User = u
|
||||
|
||||
response.WriteJSON(w, authModel)
|
||||
return
|
||||
}
|
|
@ -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.
|
||||
|
@ -206,5 +205,8 @@ func (h *Handler) Restore(w http.ResponseWriter, r *http.Request) {
|
|||
h.Runtime.Log.Infof("Restore remapped %d UserID values", len(rh.MapUserID))
|
||||
h.Runtime.Log.Info("Restore completed")
|
||||
|
||||
h.Runtime.Log.Info("Building search index")
|
||||
go h.Indexer.Rebuild(ctx)
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
@ -1531,6 +1534,7 @@ func (r *restoreHandler) dmzDocAttachment() (err error) {
|
|||
if err != nil {
|
||||
r.Context.Transaction.Rollback()
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert %s %s", filename, at[i].RefID))
|
||||
r.Runtime.Log.Error("warning", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -1733,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()
|
||||
|
|
|
@ -97,7 +97,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Get returns requested reusable content block.
|
||||
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
method := "block.add"
|
||||
method := "block.get"
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
blockID := request.Param(r, "blockID")
|
||||
|
@ -135,7 +135,6 @@ func (h *Handler) GetBySpace(w http.ResponseWriter, r *http.Request) {
|
|||
if len(b) == 0 {
|
||||
b = []block.Block{}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
|
|
|
@ -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.
|
||||
|
@ -262,16 +285,15 @@ 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) {
|
||||
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_refid IN (SELECT c_categoryid FROM dmz_category_member WHERE c_orgid=? AND c_docid=?)`),
|
||||
ctx.OrgID, ctx.OrgID, documentID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
c = []category.Category{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute select categories for document %s", documentID))
|
||||
|
@ -280,7 +302,8 @@ func (s Store) GetDocumentCategoryMembership(ctx domain.RequestContext, document
|
|||
return
|
||||
}
|
||||
|
||||
// GetSpaceCategoryMembership returns category/document associations within space.
|
||||
// GetSpaceCategoryMembership returns category/document associations within space,
|
||||
// for specified user.
|
||||
func (s Store) GetSpaceCategoryMembership(ctx domain.RequestContext, spaceID string) (c []category.Member, 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_categoryid AS categoryid, c_docid AS documentid, c_created AS created, c_revised AS revised
|
||||
|
|
|
@ -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"
|
||||
|
@ -33,6 +34,7 @@ import (
|
|||
"github.com/documize/community/model/activity"
|
||||
"github.com/documize/community/model/attachment"
|
||||
"github.com/documize/community/model/audit"
|
||||
"github.com/documize/community/model/category"
|
||||
"github.com/documize/community/model/doc"
|
||||
"github.com/documize/community/model/link"
|
||||
"github.com/documize/community/model/page"
|
||||
|
@ -62,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)
|
||||
|
@ -80,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)
|
||||
|
@ -189,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
|
||||
|
@ -230,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
|
||||
|
@ -271,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
|
||||
|
@ -308,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)
|
||||
|
||||
|
@ -339,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)
|
||||
|
@ -375,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()
|
||||
|
@ -410,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)
|
||||
|
@ -480,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()
|
||||
}
|
||||
}
|
||||
|
@ -526,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
|
||||
}
|
||||
}
|
||||
|
@ -556,7 +567,13 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// document
|
||||
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)
|
||||
|
@ -573,7 +590,7 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Don't serve archived document
|
||||
// Don't serve archived document.
|
||||
if document.Lifecycle == workflow.LifecycleArchived {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
|
@ -585,6 +602,37 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// If document has been assigned one or more categories,
|
||||
// we check to see if user can view this document.
|
||||
cat, err := h.Store.Category.GetDocumentCategoryMembership(ctx, document.RefID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
perm, err := h.Store.Permission.GetUserCategoryPermissions(ctx, ctx.UserID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
see := []category.Category{}
|
||||
for _, c := range cat {
|
||||
for _, p := range perm {
|
||||
if p.RefID == c.RefID {
|
||||
see = append(see, c)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User cannot view document if document has categories assigned
|
||||
// but user cannot see any of them.
|
||||
if len(cat) > 0 && len(see) == 0 {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// permissions
|
||||
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, document.SpaceID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
|
@ -633,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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -675,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()
|
||||
|
@ -756,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.
|
||||
|
@ -795,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
|
||||
}
|
||||
|
||||
|
@ -984,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)
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ func (s Store) Get(ctx domain.RequestContext) (l []label.Label, err error) {
|
|||
c_name AS name, c_color AS color,
|
||||
c_created AS created, c_revised AS revised
|
||||
FROM dmz_space_label
|
||||
WHERE c_orgid=?`),
|
||||
WHERE c_orgid=? ORDER BY c_name`),
|
||||
ctx.OrgID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
|
|
|
@ -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
|
@ -13,6 +13,8 @@ package meta
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/documize/community/model/doc"
|
||||
|
||||
"github.com/documize/community/domain"
|
||||
|
@ -29,9 +31,14 @@ type Store struct {
|
|||
}
|
||||
|
||||
// Documents returns every document ID value stored.
|
||||
// The query runs at the instance level across all tenants.
|
||||
// For global admins, the query runs at the instance level across all tenants.
|
||||
// For tenant admins, the query is restricted to the tenant.
|
||||
func (s Store) Documents(ctx domain.RequestContext) (documents []string, err error) {
|
||||
err = s.Runtime.Db.Select(&documents, `SELECT c_refid FROM dmz_doc WHERE c_lifecycle=1`)
|
||||
qry := "SELECT c_refid FROM dmz_doc WHERE c_lifecycle=1"
|
||||
if !ctx.GlobalAdmin {
|
||||
qry = fmt.Sprintf("%s AND c_orgid='%s'", qry, ctx.OrgID)
|
||||
}
|
||||
err = s.Runtime.Db.Select(&documents, qry)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
|
|
1
domain/onboard/dmz_category.json
Executable file
1
domain/onboard/dmz_category.json
Executable file
|
@ -0,0 +1 @@
|
|||
[{"id":"bh2sku21b54as00dsbk0","created":"2019-01-21T13:34:49Z","revised":"2019-01-21T13:34:49Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WNEpptWJ9AABRnha","category":"Maintenance"},{"id":"bh2skra1b54as00dsbjg","created":"2019-01-21T13:34:37Z","revised":"2019-01-21T13:34:37Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WNEpptWJ9AABRnha","category":"Setup"},{"id":"bh2s7ja1b54as00dsbj0","created":"2019-01-21T13:06:21Z","revised":"2019-01-21T13:06:21Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WNEpptWJ9AABRnha","category":"Users \u0026 Groups"},{"id":"bh2s7hi1b54as00dsbig","created":"2019-01-21T13:06:14Z","revised":"2019-01-21T13:06:14Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WNEpptWJ9AABRnha","category":"Integrations"},{"id":"bh2r4sq1b54as00dsb3g","created":"2019-01-21T11:52:20Z","revised":"2019-01-21T11:52:20Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WNEpptWJ9AABRnha","category":"Authentication"},{"id":"WtXOU7dMOwABe2UO","created":"2018-04-17T10:37:07Z","revised":"2018-04-17T10:37:07Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WtXNJ7dMOwABe2UK","category":"Enterprise"},{"id":"bgtmocgjkjf0hvnjli30","created":"2019-01-13T16:49:54Z","revised":"2019-01-13T16:49:54Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"betnq7venbkq5cjhoc0g","category":"Breaking Changes"},{"id":"bgtmo7ojkjf0hvnjli2g","created":"2019-01-13T16:49:36Z","revised":"2019-01-13T16:49:36Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"betnq7venbkq5cjhoc0g","category":"New Features"},{"id":"bgtmo50jkjf0hvnjli20","created":"2019-01-13T16:49:25Z","revised":"2019-01-13T16:49:25Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"betnq7venbkq5cjhoc0g","category":"Enhancements"},{"id":"bgtmo3gjkjf0hvnjli1g","created":"2019-01-13T16:49:18Z","revised":"2019-01-13T16:49:18Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"betnq7venbkq5cjhoc0g","category":"Bug Fixes"},{"id":"bgtmo1ojkjf0hvnjli10","created":"2019-01-13T16:49:11Z","revised":"2019-01-13T16:49:11Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"betnq7venbkq5cjhoc0g","category":"Community Edition"},{"id":"bgtmnvojkjf0hvnjli0g","created":"2019-01-13T16:49:03Z","revised":"2019-01-13T16:49:03Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"betnq7venbkq5cjhoc0g","category":"Enterprise Edition"}]
|
1
domain/onboard/dmz_category_member.json
Executable file
1
domain/onboard/dmz_category_member.json
Executable file
File diff suppressed because one or more lines are too long
1
domain/onboard/dmz_doc.json
Executable file
1
domain/onboard/dmz_doc.json
Executable file
File diff suppressed because one or more lines are too long
1
domain/onboard/dmz_doc_attachment.json
Executable file
1
domain/onboard/dmz_doc_attachment.json
Executable file
File diff suppressed because one or more lines are too long
1
domain/onboard/dmz_doc_link.json
Executable file
1
domain/onboard/dmz_doc_link.json
Executable file
|
@ -0,0 +1 @@
|
|||
[{"id":"WQnJXPMKrwABT4Nf","created":"2018-03-27T16:56:30Z","revised":"2018-03-27T16:56:30Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"VzO9ZqMOCgABGyfW","userId":"w4Nm8Fgw","linkType":"document","sourceDocumentId":"V16L08ucxwABhZF6","sourcePageId":"WQnJMvMKrwABT4Na","targetDocumentId":"VzSL8cVZ4QAB2B4Y","targetId":"","externalId":"","orphan":false},{"id":"Wt9Cf3cHWQABMuQa","created":"2018-04-24T14:43:20Z","revised":"2018-04-24T14:43:20Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WtXNJ7dMOwABe2UK","userId":"w4Nm8Fgw","linkType":"file","sourceDocumentId":"WtWbRbdMOwABe2SK","sourcePageId":"WtW317dMOwABe2TF","targetDocumentId":"WtWbRbdMOwABe2SK","targetId":"Wt9CaXcHWQABMuQM","externalId":"","orphan":false},{"id":"TtwPpzAUe4YeHYkC","created":"2018-07-11T00:17:14Z","revised":"2018-07-11T00:17:14Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"Wr5izCXOmgABcntP","userId":"w4Nm8Fgw","linkType":"network","sourceDocumentId":"W0VMb2zXqwABN4rn","sourcePageId":"W0VMdWzXqwABN4ro","targetDocumentId":"W0VMb2zXqwABN4rn","targetId":"","externalId":"","orphan":false},{"id":"W316i7J3xwAB8UmW","created":"2018-08-22T15:00:36Z","revised":"2018-08-22T15:00:36Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"VzO9ZqMOCgABGyfW","userId":"w4Nm8Fgw","linkType":"document","sourceDocumentId":"W31Sv7J3xwAB8Ult","sourcePageId":"W314jLJ3xwAB8UmL","targetDocumentId":"W316KbJ3xwAB8UmO","targetId":"","externalId":"","orphan":false},{"id":"bh7hek8cm9o1pnrmkf80","created":"2019-01-28T14:58:27Z","revised":"2019-01-28T14:58:27Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WOzFU_MXigAB6sIH","userId":"w4Nm8Fgw","linkType":"document","sourceDocumentId":"bh70rt0cm9o1pnrmk8o0","sourcePageId":"bh7hce0cm9o1pnrmkf5g","targetDocumentId":"bh5ikpa1b54as00dt4o0","targetId":"","externalId":"","orphan":false},{"id":"bh7lj28cm9o1pnrmkit0","created":"2019-01-28T19:35:44Z","revised":"2019-01-28T19:35:44Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WOzFU_MXigAB6sIH","userId":"w4Nm8Fgw","linkType":"section","sourceDocumentId":"WOvEC_MXigAB6sE1","sourcePageId":"WOyuEvMXigAB6sF_","targetDocumentId":"WOvEC_MXigAB6sE1","targetId":"WOytzvMXigAB6sFl","externalId":"","orphan":false},{"id":"bh7lq58cm9o1pnrmkj50","created":"2019-01-28T19:50:55Z","revised":"2019-01-28T19:50:55Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WtXNJ7dMOwABe2UK","userId":"w4Nm8Fgw","linkType":"document","sourceDocumentId":"bh7lnugcm9o1pnrmkj1g","sourcePageId":"bh7lp0gcm9o1pnrmkj3g","targetDocumentId":"WtWbRbdMOwABe2SK","targetId":"","externalId":"","orphan":false},{"id":"bh8337gcm9o1pnrmknm0","created":"2019-01-29T11:11:07Z","revised":"2019-01-29T11:11:07Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"WOzFU_MXigAB6sIH","userId":"w4Nm8Fgw","linkType":"section","sourceDocumentId":"bh82vl8cm9o1pnrmkngg","sourcePageId":"bh8307ocm9o1pnrmknk0","targetDocumentId":"WOvEC_MXigAB6sE1","targetId":"bh7ll9gcm9o1pnrmkj00","externalId":"","orphan":false},{"id":"W317bLJ3xwAB8Umf","created":"2019-03-05T09:01:06Z","revised":"2019-03-05T09:01:06Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"VzO9ZqMOCgABGyfW","userId":"w4Nm8Fgw","linkType":"document","sourceDocumentId":"W31Sv7J3xwAB8Ult","sourcePageId":"W317UrJ3xwAB8Uma","targetDocumentId":"V16L08ucxwABhZF6","targetId":"","externalId":"","orphan":false},{"id":"bj3gka67m02fu20qv23g","created":"2019-04-29T14:34:03Z","revised":"2019-04-29T14:34:03Z","orgId":"bk54k53c1osmepq14jq0","spaceId":"VzO9ZqMOCgABGyfW","userId":"w4Nm8Fgw","linkType":"document","sourceDocumentId":"V16L08ucxwABhZF6","sourcePageId":"WQnJtPMKrwABT4Ni","targetDocumentId":"VzSL8cVZ4QAB2B4Y","targetId":"","externalId":"","orphan":false}]
|
1
domain/onboard/dmz_section.json
Executable file
1
domain/onboard/dmz_section.json
Executable file
File diff suppressed because one or more lines are too long
1
domain/onboard/dmz_section_meta.json
Executable file
1
domain/onboard/dmz_section_meta.json
Executable file
File diff suppressed because one or more lines are too long
1
domain/onboard/dmz_space.json
Executable file
1
domain/onboard/dmz_space.json
Executable file
|
@ -0,0 +1 @@
|
|||
[{"id":"leFaXwlU","created":"2016-05-11T23:03:47Z","revised":"2019-03-14T15:39:39Z","name":"Extending, Compiling \u0026 Architecture","desc":"Building integrations, compiling from source and understanding product architecture","orgId":"bk54k53c1osmepq14jq0","userId":"w4Nm8Fgw","labelId":"bgt3n9bgt73a1fb2llp0","spaceType":1,"countCategory":0,"countContent":4,"icon":"dmeta-meta-devops","lifecycle":0,"likes":"Did this help you?"},{"id":"betnq7venbkq5cjhoc0g","created":"2018-10-08T15:51:59Z","revised":"2019-06-11T10:33:44Z","name":"Release Notes","desc":"Documentation for what has changed within Documize including new features, enhancements, fixes and underlying technology stack improvements","orgId":"bk54k53c1osmepq14jq0","userId":"w4Nm8Fgw","labelId":"bgt3l3rgt73a1fb2llog","spaceType":1,"countCategory":6,"countContent":6,"icon":"dmeta-meta-announce","lifecycle":0,"likes":""},{"id":"WtXNJ7dMOwABe2UK","created":"2018-04-17T10:32:07Z","revised":"2019-01-12T19:10:37Z","name":"API","desc":"Programmatically access and manipulate Documize user data and configuration options","orgId":"bk54k53c1osmepq14jq0","userId":"w4Nm8Fgw","labelId":"bgt3n9bgt73a1fb2llp0","spaceType":1,"countCategory":1,"countContent":6,"icon":"dmeta-meta-flow","lifecycle":0,"likes":"Did this help you?"},{"id":"WNEpptWJ9AABRnha","created":"2017-03-21T13:24:55Z","revised":"2019-01-22T14:03:30Z","name":"Administration Guides","desc":"Managing all aspects of your Documize instance","orgId":"bk54k53c1osmepq14jq0","userId":"w4Nm8Fgw","labelId":"bgt3l3rgt73a1fb2llog","spaceType":1,"countCategory":5,"countContent":13,"icon":"dmeta-meta-tune","lifecycle":0,"likes":"Did this help you?"}]
|
1
domain/onboard/dmz_space_label.json
Executable file
1
domain/onboard/dmz_space_label.json
Executable file
|
@ -0,0 +1 @@
|
|||
[{"id":"bgt3n9bgt73a1fb2llp0","created":"2019-01-12T19:10:29Z","revised":"2019-01-12T19:12:53Z","orgId":"bk54k53c1osmepq14jq0","name":"Developers","color":"#880e4f"},{"id":"bgt3l3rgt73a1fb2llog","created":"2019-01-12T19:05:52Z","revised":"2019-01-12T19:12:50Z","orgId":"bk54k53c1osmepq14jq0","name":"Administration","color":"#ef6c00"},{"id":"bgt3kubgt73a1fb2llo0","created":"2019-01-12T19:05:30Z","revised":"2019-01-12T19:05:30Z","orgId":"bk54k53c1osmepq14jq0","name":"Getting Started","color":"#2e7d32"}]
|
457
domain/onboard/endpoint.go
Normal file
457
domain/onboard/endpoint.go
Normal file
|
@ -0,0 +1,457 @@
|
|||
// 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 onboard handles the setup of sample data for a new Documize instance.
|
||||
package onboard
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"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"
|
||||
"github.com/documize/community/domain"
|
||||
indexer "github.com/documize/community/domain/search"
|
||||
"github.com/documize/community/domain/store"
|
||||
om "github.com/documize/community/model/onboard"
|
||||
"github.com/documize/community/model/permission"
|
||||
)
|
||||
|
||||
// Handler contains the runtime information such as logging and database.
|
||||
type Handler struct {
|
||||
Runtime *env.Runtime
|
||||
Store *store.Store
|
||||
Indexer indexer.Indexer
|
||||
MappedID map[string]string
|
||||
}
|
||||
|
||||
// InstallSample inserts sample data into database.
|
||||
func (h *Handler) InstallSample(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := domain.GetRequestContext(r)
|
||||
|
||||
// Only proceed if we are in good standing.
|
||||
if !h.Runtime.Product.IsValid(ctx) {
|
||||
response.WriteBadLicense(w)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Administrator {
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Only proceed if we have no spaces and documents.
|
||||
// This prevents sample data restore inside existing live instance.
|
||||
spaces, docs := h.Store.Onboard.ContentCounts(ctx.OrgID)
|
||||
if spaces > 0 || docs > 0 {
|
||||
h.Runtime.Log.Info("Unable to install sample data when database contains spaces/docs")
|
||||
response.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Load sample data from embedded assets.
|
||||
data := h.loadSampleData()
|
||||
if data.LoadFailure {
|
||||
response.WriteError(w, "Unable to unpack sample data")
|
||||
h.Runtime.Log.Info("Unable to unpack sample data")
|
||||
return
|
||||
}
|
||||
|
||||
data.Context = ctx
|
||||
|
||||
err := h.processSampleData(data)
|
||||
if err != nil {
|
||||
response.WriteError(w, "Unable to process sample data")
|
||||
h.Runtime.Log.Error("Unable to process sample data", err)
|
||||
return
|
||||
}
|
||||
|
||||
h.Runtime.Log.Info("Onboarding complete")
|
||||
|
||||
h.Runtime.Log.Info("Building search index")
|
||||
go h.Indexer.Rebuild(ctx)
|
||||
|
||||
response.WriteEmpty(w)
|
||||
}
|
||||
|
||||
// Read sample data that is stored as embedded asset.
|
||||
func (h *Handler) loadSampleData() (data om.SampleData) {
|
||||
h.loadFile(data, "dmz_category.json", &data.Category)
|
||||
h.loadFile(data, "dmz_category_member.json", &data.CategoryMember)
|
||||
h.loadFile(data, "dmz_doc.json", &data.Document)
|
||||
h.loadFile(data, "dmz_doc_attachment.json", &data.DocumentAttachment)
|
||||
h.loadFile(data, "dmz_doc_link.json", &data.DocumentLink)
|
||||
h.loadFile(data, "dmz_section.json", &data.Section)
|
||||
h.loadFile(data, "dmz_section_meta.json", &data.SectionMeta)
|
||||
h.loadFile(data, "dmz_space.json", &data.Space)
|
||||
h.loadFile(data, "dmz_space_label.json", &data.SpaceLabel)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Handler) loadFile(data om.SampleData, filename string, v interface{}) {
|
||||
err := h.unpackFile(filename, &v)
|
||||
if err != nil {
|
||||
data.LoadFailure = true
|
||||
}
|
||||
}
|
||||
|
||||
// Reads file and unmarshals content as JSON.
|
||||
func (h *Handler) unpackFile(filename string, v interface{}) (err error) {
|
||||
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([]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)
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns new ID based on old ID.
|
||||
func (h *Handler) getMappedID(table, old string) string {
|
||||
// Return mapped ID if we have one.
|
||||
key := table + "_" + old
|
||||
if n, ok := h.MappedID[key]; ok {
|
||||
return n
|
||||
}
|
||||
|
||||
// Generate new ID and send back.
|
||||
newID := uniqueid.Generate()
|
||||
h.MappedID[table+"_"+old] = newID
|
||||
return newID
|
||||
}
|
||||
|
||||
// Insert data into database using sample data loaded from embedded assets.
|
||||
func (h *Handler) processSampleData(data om.SampleData) (err error) {
|
||||
data.Context.Transaction, _ = h.Runtime.StartTx(sql.LevelReadUncommitted)
|
||||
|
||||
h.MappedID = make(map[string]string)
|
||||
|
||||
// Space Label.
|
||||
h.Runtime.Log.Info(fmt.Sprintf("Installing (%d) space labels", len(data.SpaceLabel)))
|
||||
for i := range data.SpaceLabel {
|
||||
_, err = data.Context.Transaction.Exec(h.Runtime.Db.Rebind(`
|
||||
INSERT INTO dmz_space_label
|
||||
(c_refid, c_orgid, c_name, c_color, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`),
|
||||
h.getMappedID("label", data.SpaceLabel[i].RefID),
|
||||
data.Context.OrgID,
|
||||
data.SpaceLabel[i].Name,
|
||||
data.SpaceLabel[i].Color,
|
||||
data.SpaceLabel[i].Created,
|
||||
data.SpaceLabel[i].Revised)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert space label %s", data.SpaceLabel[i].RefID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Space.
|
||||
h.Runtime.Log.Info(fmt.Sprintf("Installing (%d) spaces", len(data.Space)))
|
||||
for i := range data.Space {
|
||||
_, err = data.Context.Transaction.Exec(h.Runtime.Db.Rebind(`
|
||||
INSERT INTO dmz_space
|
||||
(c_refid, c_name, c_orgid, c_userid, c_type, c_lifecycle,
|
||||
c_likes, c_icon, c_desc, c_count_category, c_count_content,
|
||||
c_labelid, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
h.getMappedID("space", data.Space[i].RefID),
|
||||
data.Space[i].Name,
|
||||
data.Context.OrgID,
|
||||
data.Context.UserID,
|
||||
data.Space[i].Type,
|
||||
data.Space[i].Lifecycle,
|
||||
data.Space[i].Likes,
|
||||
data.Space[i].Icon,
|
||||
data.Space[i].Description,
|
||||
data.Space[i].CountCategory,
|
||||
data.Space[i].CountContent,
|
||||
h.getMappedID("label", data.Space[i].LabelID),
|
||||
data.Space[i].Created, data.Space[i].Revised)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert space record %s", data.Space[i].RefID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Category.
|
||||
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_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)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert category %s", data.Category[i].RefID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Category Member.
|
||||
h.Runtime.Log.Info(fmt.Sprintf("Installing category member (%d)", len(data.CategoryMember)))
|
||||
for i := range data.CategoryMember {
|
||||
_, err = data.Context.Transaction.Exec(h.Runtime.Db.Rebind(`
|
||||
INSERT INTO dmz_category_member
|
||||
(c_refid, c_orgid, c_categoryid, c_spaceid, c_docid, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`),
|
||||
h.getMappedID("category_member", data.CategoryMember[i].RefID),
|
||||
data.Context.OrgID,
|
||||
h.getMappedID("category", data.CategoryMember[i].CategoryID),
|
||||
h.getMappedID("space", data.CategoryMember[i].SpaceID),
|
||||
h.getMappedID("document", data.CategoryMember[i].DocumentID),
|
||||
data.CategoryMember[i].Created,
|
||||
data.CategoryMember[i].Revised)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert category %s", data.Category[i].RefID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Assign permissions per space space.
|
||||
perm := permission.Permission{}
|
||||
perm.OrgID = data.Context.OrgID
|
||||
perm.Who = permission.UserPermission
|
||||
perm.WhoID = data.Context.UserID
|
||||
perm.Scope = permission.ScopeRow
|
||||
perm.Location = permission.LocationSpace
|
||||
|
||||
for i := range data.Space {
|
||||
perm.RefID = h.getMappedID("space", data.Space[i].RefID)
|
||||
perm.Action = "" // we send array for actions below
|
||||
|
||||
err = h.Store.Permission.AddPermissions(data.Context, perm,
|
||||
permission.SpaceOwner, permission.SpaceManage, permission.SpaceView,
|
||||
permission.DocumentAdd, permission.DocumentCopy, permission.DocumentDelete,
|
||||
permission.DocumentEdit, permission.DocumentMove,
|
||||
permission.DocumentTemplate, permission.DocumentApprove,
|
||||
permission.DocumentVersion, permission.DocumentLifecycle)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert space permission %s", data.Space[i].RefID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Assign permissions per category.
|
||||
for i := range data.Category {
|
||||
pc := permission.Permission{}
|
||||
pc.OrgID = data.Context.OrgID
|
||||
pc.Who = permission.UserPermission
|
||||
pc.WhoID = data.Context.UserID
|
||||
pc.Scope = permission.ScopeRow
|
||||
pc.Location = permission.LocationCategory
|
||||
pc.RefID = h.getMappedID("category", data.Category[i].RefID)
|
||||
pc.Action = permission.CategoryView
|
||||
|
||||
err = h.Store.Permission.AddPermission(data.Context, pc)
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert category permission %s", data.Category[i].RefID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Document.
|
||||
h.Runtime.Log.Info(fmt.Sprintf("Installing document (%d)", len(data.Document)))
|
||||
for i := range data.Document {
|
||||
_, err = data.Context.Transaction.Exec(h.Runtime.Db.Rebind(`
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
h.getMappedID("document", data.Document[i].RefID),
|
||||
data.Context.OrgID,
|
||||
h.getMappedID("space", data.Document[i].SpaceID),
|
||||
data.Context.UserID,
|
||||
data.Document[i].Job,
|
||||
data.Document[i].Location,
|
||||
data.Document[i].Name,
|
||||
data.Document[i].Excerpt,
|
||||
data.Document[i].Slug,
|
||||
data.Document[i].Tags,
|
||||
data.Document[i].Template,
|
||||
data.Document[i].Protection,
|
||||
data.Document[i].Approval,
|
||||
data.Document[i].Lifecycle,
|
||||
data.Document[i].Versioned,
|
||||
data.Document[i].VersionID,
|
||||
data.Document[i].VersionOrder,
|
||||
data.Document[i].GroupID,
|
||||
data.Document[i].Created,
|
||||
data.Document[i].Revised)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert document %s", data.Document[i].RefID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Document Attachment.
|
||||
h.Runtime.Log.Info(fmt.Sprintf("Installing document attachment (%d)", len(data.DocumentAttachment)))
|
||||
for i := range data.DocumentAttachment {
|
||||
_, err = data.Context.Transaction.Exec(h.Runtime.Db.Rebind(`
|
||||
INSERT INTO dmz_doc_attachment
|
||||
(c_refid, c_orgid, c_docid, c_sectionid, c_job, c_fileid,
|
||||
c_filename, c_data, c_extension, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
h.getMappedID("document_attachment", data.DocumentAttachment[i].RefID),
|
||||
data.Context.OrgID,
|
||||
h.getMappedID("document", data.DocumentAttachment[i].DocumentID),
|
||||
h.getMappedID("section", data.DocumentAttachment[i].SectionID),
|
||||
data.DocumentAttachment[i].Job,
|
||||
data.DocumentAttachment[i].FileID,
|
||||
data.DocumentAttachment[i].Filename,
|
||||
data.DocumentAttachment[i].Data,
|
||||
data.DocumentAttachment[i].Extension,
|
||||
data.DocumentAttachment[i].Created,
|
||||
data.DocumentAttachment[i].Revised)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert document attachment %s", data.DocumentAttachment[i].RefID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Document Link.
|
||||
h.Runtime.Log.Info(fmt.Sprintf("Installing document link (%d)", len(data.DocumentLink)))
|
||||
for i := range data.DocumentLink {
|
||||
targetID := ""
|
||||
if data.DocumentLink[i].LinkType == "file" {
|
||||
targetID = h.getMappedID("document_attachment", data.DocumentLink[i].TargetID)
|
||||
} else if data.DocumentLink[i].LinkType == "document" {
|
||||
targetID = h.getMappedID("document", data.DocumentLink[i].TargetID)
|
||||
} else {
|
||||
targetID = h.getMappedID("section", data.DocumentLink[i].TargetID)
|
||||
}
|
||||
|
||||
_, err = data.Context.Transaction.Exec(h.Runtime.Db.Rebind(`
|
||||
INSERT INTO dmz_doc_link
|
||||
(c_refid, c_orgid, c_spaceid, c_userid, c_sourcedocid, c_sourcesectionid,
|
||||
c_targetdocid, c_targetid, c_externalid, c_type, c_orphan, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
h.getMappedID("document_link", data.DocumentLink[i].RefID),
|
||||
data.Context.OrgID,
|
||||
h.getMappedID("space", data.DocumentLink[i].SpaceID),
|
||||
data.Context.UserID,
|
||||
h.getMappedID("document", data.DocumentLink[i].SourceDocumentID),
|
||||
h.getMappedID("section", data.DocumentLink[i].SourceSectionID),
|
||||
h.getMappedID("document", data.DocumentLink[i].TargetDocumentID),
|
||||
targetID,
|
||||
data.DocumentLink[i].ExternalID,
|
||||
data.DocumentLink[i].LinkType,
|
||||
data.DocumentLink[i].Orphan,
|
||||
data.DocumentLink[i].Created,
|
||||
data.DocumentLink[i].Revised)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert document link %s", data.DocumentLink[i].RefID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Document Section.
|
||||
h.Runtime.Log.Info(fmt.Sprintf("Installing section (%d)", len(data.Section)))
|
||||
for i := range data.Section {
|
||||
_, err = data.Context.Transaction.Exec(h.Runtime.Db.Rebind(`
|
||||
INSERT INTO dmz_section
|
||||
(c_refid, c_orgid, c_docid, c_userid, c_contenttype, c_type, c_level, c_name, c_body,
|
||||
c_revisions, c_sequence, c_templateid, c_status, c_relativeid, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
h.getMappedID("section", data.Section[i].RefID),
|
||||
data.Context.OrgID,
|
||||
h.getMappedID("document", data.Section[i].DocumentID),
|
||||
data.Context.UserID,
|
||||
data.Section[i].ContentType,
|
||||
data.Section[i].Type,
|
||||
data.Section[i].Level,
|
||||
data.Section[i].Name,
|
||||
data.Section[i].Body,
|
||||
data.Section[i].Revisions,
|
||||
data.Section[i].Sequence,
|
||||
h.getMappedID("section", data.Section[i].TemplateID),
|
||||
data.Section[i].Status,
|
||||
h.getMappedID("section", data.Section[i].RelativeID),
|
||||
data.Section[i].Created,
|
||||
data.Section[i].Revised)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert section %s", data.Section[i].RefID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Document Section Meta.
|
||||
h.Runtime.Log.Info(fmt.Sprintf("Installing section meta (%d)", len(data.SectionMeta)))
|
||||
for i := range data.SectionMeta {
|
||||
_, err = data.Context.Transaction.Exec(h.Runtime.Db.Rebind(`
|
||||
INSERT INTO dmz_section_meta
|
||||
(c_sectionid, c_orgid, c_userid, c_docid, c_rawbody,
|
||||
c_config, c_external, c_created, c_revised)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
||||
h.getMappedID("section", data.SectionMeta[i].SectionID),
|
||||
data.Context.OrgID,
|
||||
data.Context.UserID,
|
||||
h.getMappedID("document", data.SectionMeta[i].DocumentID),
|
||||
data.SectionMeta[i].RawBody,
|
||||
data.SectionMeta[i].Config,
|
||||
data.SectionMeta[i].ExternalSource,
|
||||
data.SectionMeta[i].Created,
|
||||
data.SectionMeta[i].Revised)
|
||||
|
||||
if err != nil {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to insert section meta %s", data.SectionMeta[i].SectionID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ok := h.Runtime.Commit(data.Context.Transaction)
|
||||
if !ok {
|
||||
h.Runtime.Rollback(data.Context.Transaction)
|
||||
return
|
||||
}
|
||||
|
||||
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